/* Набор стилей приложения.
 *
 * Переехал сюда из блока <style> в templates/base.html 15.08.2026. Причина
 * не в красоте: пока набор лежал в каркасе, правка стиля любого экрана
 * редактировала файл, который наследуют все, — и однажды это уронило тест
 * соседнего экрана (tests/suppliers/test_status_page.py, 10.08.2026).
 *
 * Комментарии «почему так» перенесены дословно: каждый из них объясняет
 * решение, оплаченное найденным дефектом.
 */

:root {
  --bg:        #f6f6f7;
  --panel:     #ffffff;
  --line:      #e6e6e8;   /* граница панели */
  --line-soft: #f0f0f2;   /* разделитель строк внутри списка */
  --ink:       #16171a;
  --muted:     #6b6b72;
  --chrome:    #1b1c1f;   /* полоса приложения */
  /* Главное действие зелёное. Раньше красный значил сразу и «нажми сюда»,
     и «здесь опасно»; теперь смыслы разведены: зелёный только про действие,
     красный только про опасность, янтарный только про убыток. Белое на
     #146c33 даёт контраст около 6,5:1. */
  --ok:        #146c33;
  --ok-dark:   #0f5527;
  --warn:      #b26a00;   /* маржа ушла в минус */
  --err:       #c0121f;   /* ошибка и удаление */
  --brand:     #e4061d;   /* фирменный красный: только марка */
}
* { box-sizing: border-box; }
body { font: 13px/1.35 system-ui, "Segoe UI", sans-serif; margin: 0;
       color: var(--ink); background: var(--bg); }
input, select, button { font: inherit; color: inherit; }

/* ---------- полоса приложения ---------- */
.chrome { display: flex; align-items: center; gap: 1.4rem;
          height: 38px; padding: 0 1rem; background: var(--chrome); color: #fff; }
.chrome .mark { display: block; height: 28px; }
.chrome .mark img { display: block; height: 28px; width: auto; }
.chrome a { color: #c9cacd; text-decoration: underline; text-underline-offset: 2px; }
.chrome a:hover { color: #fff; }
.chrome .here { color: #fff; font-weight: 600; }
/* margin-left: auto стоял здесь до тикетов (02.09.2026) — теперь толчок
   группы вправо держит .ticket-link, а не имя: между именем и «Выйти» не
   должно появиться никакого элемента, который сдвигал бы группу заново. */
.chrome .spacer { color: #9a9ba0; }
.chrome form { display: inline; }
.chrome .exit { background: none; border: 0; color: #c9cacd; cursor: pointer;
                padding: 0; text-decoration: underline; text-underline-offset: 2px; }
/* Без подчёркивания — правка владельца 11.09.2026: карандаш и так говорит,
   что это действие, а подчёркнутый пункт читался как раздел. */
.chrome .ticket-link { margin-left: auto; text-decoration: none; }

/* ---------- полоса «запрос не выполнен» ----------
   Поверх всего, включая окно разбора письма (z-index 11): ошибка чаще всего
   случается именно из окна, и под вуалью её не было бы видно. [hidden]
   прописан явно — display: flex иначе перебил бы атрибут. */
.net-error { position: fixed; top: 0; left: 0; right: 0; z-index: 20;
             display: flex; align-items: center; gap: 1rem;
             padding: .55rem 1rem; background: #fff8ec; color: var(--ink);
             border-bottom: 2px solid var(--warn); }
.net-error[hidden] { display: none; }
.net-error span { flex: 1; }
.net-error button { background: none; border: 0; cursor: pointer; font-size: 14px;
                    color: var(--muted); padding: 0 .3rem; }

/* ---------- кнопки ----------
   Селекторы по классу, а не по тегу: то же действие бывает и ссылкой
   (скачать файл, перейти к списку), и кнопкой формы. Привязка к button
   оставляла ссылки вовсе без вида — найдено ревью ветки 15.08.2026. */
.primary { background: var(--ok); border: 1px solid var(--ok); border-radius: 4px;
           color: #fff; font-weight: 600; padding: .25rem .7rem; cursor: pointer;
           text-decoration: none; display: inline-block; }
.primary:hover { background: var(--ok-dark); border-color: var(--ok-dark); color: #fff; }
.quiet { background: none; border: 1px solid transparent; border-radius: 4px;
         padding: .25rem .5rem; cursor: pointer; color: var(--ink);
         text-decoration: none; display: inline-block; }
.quiet:hover { border-color: var(--line); background: #fafafa; color: var(--ink); }
.danger:hover { border-color: #f0c4c8; color: var(--err); background: #fff7f7; }

/* ---------- полоса «клиент и условия» ---------- */
.meta { display: flex; align-items: flex-start; flex-wrap: wrap; gap: .5rem 1.1rem;
        padding: .5rem 1rem .6rem; background: var(--panel);
        border-bottom: 1px solid var(--line); }
.meta .group { display: flex; align-items: flex-start; flex-wrap: wrap; gap: .5rem .7rem; }
.meta .sep { align-self: stretch; width: 1px; background: #dcdce0; }
.meta .f { position: relative; display: flex; flex-direction: column; gap: .12rem; }
/* Отказ по полю не двигает полосу: он висит под своим полем, а не встаёт в
   поток. Поля узкие (скидка — 48 px), и обычный текст ошибки переносился в
   них по слову: полоса вырастала с 61 до 132 px и толкала вниз весь состав,
   так что менеджер терял из виду строку, которую правил. Замерено в браузере
   16.08.2026. */
.meta .f .error { position: absolute; top: 100%; left: 0; z-index: 4;
                  white-space: nowrap; font-size: 11px; margin-top: .15rem;
                  background: var(--panel); padding: .1rem .3rem;
                  border: 1px solid #f0c4c8; border-radius: 3px;
                  box-shadow: 0 2px 6px rgba(0,0,0,.08); }
/* Подпись не переносится: «Срок поставки, дней» вставала в две строки и
   сталкивала своё поле вниз, ломая ряд. */
.meta label { font-size: 11px; color: var(--muted); white-space: nowrap; }
.meta input, .meta select { border: 1px solid var(--line); border-radius: 4px;
                            padding: .22rem .35rem; background: #fff; height: 26px; }
.meta input:focus, .meta select:focus { outline: none; border-color: var(--ink); }
/* Ширины полей — по значению, а не по умолчанию браузера. Утверждены на
   макете 15.08.2026 и до боевого сначала не доехали: скидка занимала 160 px
   под двузначное число, срок поставки столько же под трёхзначное, а дата —
   164 px. Полоса тратила около 350 px впустую, и владелец просил сузить
   поля дважды.

   Отбор по имени поля, а не по классу: имя приходит из модели и не может
   разойтись с формой, а класс пришлось бы прописывать виджету каждого поля
   в forms.py — правило о ширине уехало бы из стилей в питон. */
#header [name="client_org"] { width: 200px; }
#header [name="client_inn"] { width: 108px; }
#header [name="client_type"] { width: 130px; }
/* Скидка и срок — числа, прижаты вправо и набраны цифрами одной ширины:
   так они читаются столбиком с числами состава, а не как текст. */
#header [name="discount_percent"] { width: 48px; text-align: right;
                                    font-variant-numeric: tabular-nums; }
#header [name="default_lead_time_days"] { width: 68px; text-align: right;
                                          font-variant-numeric: tabular-nums; }
#header [name="valid_until"] { width: 116px; }
.meta .check { flex-direction: row; align-items: center; gap: .3rem; padding-top: 17px; }
/* Поле поиска растягивается, но не на всю ширину экрана: строка ввода в
   тысячу точек выглядит как ошибка вёрстки, а не как приглашение искать. */
.meta .crm-row { display: flex; align-items: center; gap: .3rem; }
.meta .crm-row input { width: 150px; }
.meta .crm-go { color: var(--muted); text-decoration: none; font-size: 14px; }
.meta .crm-go:hover { color: var(--ink); }
.meta .grow { flex: 0 1 420px; min-width: 220px; }
.meta .grow input { width: 100%; }
.meta .check input { height: auto; }
/* Порог считан от настоящей ширины полосы, а не назначен на глаз: обе группы
   с разделителем занимают около 1100 px, значит в один ряд они помещаются
   вплоть до 1130 px окна. Ниже — раскладываем по рядам явно, а вертикальную
   черту убираем: между рядами она превращается в висящую в воздухе чёрточку. */
@media (max-width: 1130px) {
  .meta .group { flex: 1 0 100%; }
  .meta .sep { display: none; }
  .meta .group + .group { border-top: 1px solid var(--line-soft); padding-top: .45rem; }
}

/* ---------- окно разбора письма и окно тикета ---------- */
/* Пустой контейнер не должен занимать места и ловить щелчки. #note-empty —
   тот же приём для контейнера тикета (#note, templates/base.html), у него
   свой id, потому что #dialog-empty живёт только на экране сборки. */
.dialog:empty, .dialog > #dialog-empty, .dialog > #note-empty { display: none; }
.dialog .veil { position: fixed; inset: 0; background: rgba(20,20,24,.55); z-index: 10; }
.dialog .modal {
  position: fixed; z-index: 11; left: 50%; transform: translateX(-50%);
  top: 1.5rem; bottom: 1.5rem; width: min(1000px, calc(100vw - 3rem));
  background: var(--panel); border-radius: 8px;
  box-shadow: 0 24px 70px rgba(0,0,0,.35);
  display: flex; flex-direction: column; min-height: 0;
}
/* Окно тикета — по содержимому, а не во весь экран. Общее правило выше
   растягивает окно от края до края по высоте, и это верно для разбора
   письма: там поле на двенадцать строк и полсотни строк сверки. У тикета
   внутри четыре строки, и то же правило давало пустое полотно во весь
   экран ради двух кнопок (видно браузером 02.09.2026). Потолок оставлен:
   свободный текст менеджера длину имеет любую. */
.dialog .modal.ticket-modal {
  bottom: auto; max-height: calc(100vh - 3rem);
  width: min(520px, calc(100vw - 3rem));
}

/* По прямому адресу окно — обычный блок в потоке страницы, без затемнения. */
.dialog.standalone .veil { display: none; }
.dialog.standalone .modal { position: static; transform: none; width: auto;
                            margin: .75rem 1rem; box-shadow: none;
                            border: 1px solid var(--line); }
.dialog .modal > header { display: flex; align-items: center; gap: .7rem;
                          padding: .6rem .9rem; border-bottom: 1px solid var(--line); }
.dialog .modal > header .t { font-size: 15px; font-weight: 600; }
.dialog .modal > header .x { margin-left: auto; background: none;
                             border: 1px solid transparent; border-radius: 4px;
                             color: var(--muted); cursor: pointer; font-size: 16px;
                             line-height: 1; padding: .2rem .45rem; }
.dialog .modal > header .x:hover { border-color: var(--line); color: var(--ink); }
.dialog .modal > .body { overflow: auto; flex: 1; min-height: 0; }

.letter { padding: .7rem .9rem; border-bottom: 1px solid var(--line);
          display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; }
.letter label { font-size: 11px; color: var(--muted); }
.letter textarea { width: 100%; border: 1px solid var(--line); border-radius: 4px;
                   padding: .4rem .5rem; font: inherit; resize: vertical; }

/* Зона перетаскивания. Родное поле выбора файла остаётся внутри неё: путь
   «нажать и выбрать» никуда не девается, перетаскивание — второй путь. */
/* Зона в столбец и с воздухом: полоской в один ряд с полем выбора она
   читалась как подпись к нему, а не как место, куда можно бросить файл. */
.letter .drop { width: 100%; display: flex; flex-direction: column;
                align-items: center; gap: .3rem;
                border: 1px dashed var(--line); border-radius: 6px;
                padding: .9rem .7rem; background: #fbfbfc; }
/* Зелёный — цвет действия по набору правил проекта: «сюда можно отпустить». */
.letter .drop.over { border-color: var(--ok); background: #eef6f0; }
.letter .drop-hint { color: var(--ink); font-size: 13px; max-width: 100%;
                     overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Имя брошенного файла — уже не приглашение, а факт: цветом и весом. */
.letter .drop.filled .drop-hint { font-weight: 600; }
/* Кнопка «убрать» показывается только при выбранном файле: в пустой зоне
   ей нечего убирать, и она читалась бы как ещё одно действие. */
.letter .drop-clear { display: none; background: none; border: 1px solid var(--line);
                      border-radius: 4px; color: var(--muted); cursor: pointer;
                      font: inherit; font-size: 11px; padding: .1rem .4rem; }
.letter .drop-clear:hover { border-color: var(--err); color: var(--err); }
.letter .drop.filled .drop-clear { display: inline-block; }
.letter .drop-or { color: var(--muted); font-size: 11px; }

/* Полоса разбора. Показ держит класс htmx-request, который htmx вешает на
   форму на время запроса и снимает по ответу — в том числе по обрыву связи.
   Прогресс неопределённый намеренно: сколько займёт разбор, до ответа модели
   не знает никто, а полоса, ползущая к сотне процентов и замирающая на ней,
   врёт больше, чем не говорит ничего. */
/* Полоса под кнопкой и той же ширины, что текст рядом с ней: растянутая на
   всю форму, она отрывалась от кнопки и на широком экране читалась как
   элемент чужого блока, а подпись рядом с ней обрезалась многоточием. */
.letter .progress { display: none; width: min(420px, 100%);
                    flex-direction: column; align-items: stretch; gap: .3rem;
                    color: var(--muted); font-size: 12px; }
.letter.htmx-request .progress { display: flex; }
.letter .progress .bar { height: 4px; border-radius: 2px;
                         background: var(--line-soft); overflow: hidden;
                         position: relative; }
.letter .progress .bar::after { content: ""; position: absolute; inset: 0 auto 0 0;
                                width: 35%; border-radius: 2px;
                                background: var(--ok);
                                animation: kp-progress 1.1s ease-in-out infinite; }
@keyframes kp-progress {
  0%   { left: -35%; }
  100% { left: 100%; }
}
/* Если человек выключил анимацию в системе, полоса не дёргается, но остаётся
   видимой: сообщение «идёт разбор» важнее движения. */
@media (prefers-reduced-motion: reduce) {
  .letter .progress .bar::after { animation: none; width: 100%; left: 0; opacity: .5; }
}

/* Плашка фонового разбора. Живёт вне формы — разбор идёт на сервере, и окно
   можно закрыть, — поэтому свои отступы, а не форменные. */
.run-waiting { display: flex; flex-direction: column; gap: .4rem;
               padding: .9rem; border-bottom: 1px solid var(--line);
               color: var(--muted); font-size: 12px; }
.run-waiting .bar { height: 4px; border-radius: 2px; background: var(--line-soft);
                    overflow: hidden; position: relative; }
.run-waiting .bar::after { content: ""; position: absolute; inset: 0 auto 0 0;
                           width: 35%; border-radius: 2px; background: var(--ok);
                           animation: kp-progress 1.1s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .run-waiting .bar::after { animation: none; width: 100%; left: 0; opacity: .5; }
}

/* Куски, которые модель не осилила. Показываются как есть — правила на куске
   таблицы дают трубы и количество 1 у всех строк. */
.unparsed { padding: .7rem .9rem; border-bottom: 1px solid var(--line); }
.unparsed pre { margin: .4rem 0 0; padding: .5rem .6rem; background: #fbfbfc;
                border: 1px solid var(--line); border-radius: 4px;
                max-height: 12rem; overflow: auto; white-space: pre-wrap;
                font-size: 12px; }

.review .line { padding: .7rem .9rem; border-bottom: 1px solid var(--line-soft); }
.review .head { display: flex; align-items: baseline; gap: .6rem; margin-bottom: .45rem; }
/* Строка письма — цитата: она пришла от клиента, её не правят, по ней
   сверяются. Поэтому первой в строке и жирным. */
.review .raw { font-weight: 600; flex: 1; min-width: 0; }
.review .qty { display: flex; align-items: center; gap: .35rem; flex: none;
               color: var(--muted); font-size: 12px; }
.review .qty input { width: 3.6rem; text-align: right; border: 1px solid var(--line);
                     border-radius: 4px; padding: .18rem .3rem;
                     font-variant-numeric: tabular-nums; }
.review .guessed { color: var(--warn); font-size: 11px; white-space: nowrap; }
/* Вердикт разбора словами и цветом: система предлагает, а решает менеджер,
   поэтому её уверенность должна быть написана, а не угадываться. */
.verdict { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
           font-weight: 600; padding: .1rem .4rem; border-radius: 3px; flex: none; }
.verdict.sure { background: #eaf3ec; color: #256b35; }
.verdict.choose { background: #fff8ec; color: var(--warn); }
.verdict.near { background: #f2f2f4; color: var(--muted); }
.verdict.none { background: #fff2f3; color: var(--err); }
/* Бренд не тот, что просил клиент: предупреждение, а не отказ — товар-то
   нашёлся, просто другого производителя. */
.verdict.alien { background: #fff8ec; color: var(--warn); text-transform: none; }
/* Сводка покрытия. Цвета те же, что у вердиктов строк: число сверху и
   плашка на строке обязаны читаться как одно и то же, иначе сводка станет
   ещё одной серой надписью — ровно тем, от чего она избавляет. */
.coverage { margin: 0 0 .6rem; font-size: 13px; color: var(--muted); }
.coverage .c-exact { color: #256b35; font-weight: 600; }
.coverage .c-similar { color: var(--warn); font-weight: 600; }
.coverage .c-none { color: var(--err); font-weight: 600; }

/* Цена кандидата на сверке — тот же вид, что в панели подбора. */
.review .cands .money { color: var(--muted); white-space: nowrap; }
/* Акция поставщика рядом с ценой кандидата. Размер на ступень меньше цены:
   здесь, в отличие от состава КП, оба числа стоят в одну строку, и равный
   вес читался бы как две равноправные цены. Цена — то, что уйдёт клиенту;
   акция — справка. */
.review .cands .promo { font-size: 11px; color: var(--muted); white-space: nowrap; }
.notours ul { list-style: none; margin: 0 0 .5rem; padding: 0; font-size: 13px; }
.notours li { padding: .12rem 0; }
.notours .said { margin-left: .5rem; font-size: 12px; color: #256b35; }

.review .cands { list-style: none; margin: 0; padding: 0; }
.review .cands li { padding: .22rem 0; }
.review .cands label { display: flex; align-items: baseline; gap: .45rem; cursor: pointer; }
.review .already { font-size: 11px; color: var(--muted); border: 1px solid var(--line);
                   border-radius: 3px; padding: 0 .25rem; margin-left: .3rem; }
/* Служебные выборы отделены от товаров: это не кандидаты, а выход из
   ситуации, и в общем списке они читаются как ещё два товара. */
.review .cands li.service { color: var(--muted); }
.review .cands li.service:first-of-type { border-top: 1px dashed var(--line);
                                          margin-top: .3rem; padding-top: .4rem; }
.review > footer { border-top: 1px solid var(--line); background: #fafafa;
                   padding: .55rem .9rem; display: flex; align-items: center; gap: .7rem;
                   position: sticky; bottom: 0; }
.review > footer .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
                        color: var(--muted); font-weight: 600; }
.review > footer .count { font-size: 15px; font-weight: 700; }
.review > footer .note { color: var(--muted); font-size: 12px; }
.review > footer .r { margin-left: auto; }

/* ---------- окно тикета (спека 2026-09-02, §Б.3) ---------- */
/* Шапка товара и три кнопки типа получают тот же отступ, что .letter у форм
   ниже: разные состояния окна должны выглядеть одним окном, а не сшивкой
   кусков с разными полями. */
.ticket-subject, .ticket-kinds, .ticket-accepted { padding: .7rem .9rem; }
.ticket-subject { border-bottom: 1px solid var(--line); }
.ticket-subject .name { font-weight: 600; }
.ticket-subject .sub { color: var(--muted); font-size: 11px; }
.ticket-kinds { display: flex; gap: .5rem; flex-wrap: wrap; }
.ticket-price-line { margin: 0 0 .35rem; }
/* Два ответа про цену — кнопки в ряд, а не переключатели: ответ и есть
   отправка (спека §Б.3). */
.ticket-answers { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .5rem; }
.ticket-accepted { font-weight: 600; }
/* Значок у товара — тихий, в цвете подстрочника, не кнопка (спека §Б.2):
   сброс всех рамок и отступов кнопки, цвет и размер шрифта наследуются от
   подстрочника, в котором значок стоит, — строка КП от него не растёт. */
.ticket-icon {
  flex: none; background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer; line-height: 1;
}
.ticket-icon:hover { color: var(--ink); }
/* «сказать, почему» и «сообщить, что не хватает» — заметная ссылка, а не
   вопрос: бюджет вопросов M-6 (не более одного на документ) её не касается,
   ответа никто не требует. */
.ticket-icon-note {
  flex: none; background: none; border: 0; padding: 0; margin-left: .3rem;
  font: inherit; color: var(--muted); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.ticket-icon-note:hover { color: var(--ink); }

/* ---------- экран отбора прайса ---------- */
.picker-form { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.work { flex: 1; display: flex; gap: .75rem; padding: .75rem 1rem; min-height: 0; }
.work > .panel { flex: 1; min-width: 0; }
.panel > h3 .r { margin-left: auto; display: flex; gap: .3rem; font-weight: 400;
                 text-transform: none; letter-spacing: 0; font-size: 11px; }
.panel > h3 { display: flex; align-items: center; gap: .5rem; }
button.bordered { border-color: var(--line); }
.actbar { border-top: 1px solid var(--line); background: var(--panel);
          padding: .5rem 1rem; display: flex; align-items: center; gap: .7rem; }
.actbar .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
               color: var(--muted); font-weight: 600; }
.actbar .count { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.actbar .note { color: var(--muted); font-size: 12px; }
.actbar .r { margin-left: auto; }

/* Условия отбора парами: слева что отбирали, справа что отобралось. */
.terms { display: grid; grid-template-columns: 12rem 1fr; gap: .35rem 1rem;
         padding: .6rem .7rem; max-width: 900px; margin: 0; }
.terms dt { color: var(--muted); }
.terms dd { margin: 0; }
.warnbox { border-left: 3px solid var(--warn); background: #fff8ec;
           padding: .4rem .6rem; margin: .5rem .7rem; max-width: 900px; font-size: 12px; }

/* ---------- экран сделки amoCRM: шапка сделки ---------- */
/* Та же полоса, что .meta у документа: отступ 1rem в линию с .doc и реестром.
   Без неё текст висел у самого края окна — замечание владельца 05.09.2026.
   Класс не `.lead`: так называется обёртка поля срока в строке состава, и
   правило шапки с 05.09 по 28.09.2026 рисовало под каждым «В наличии»
   черту с отступами (находка владельца 28.09.2026). */
.deal-head { padding: .55rem 1rem .65rem; background: var(--panel);
             border-bottom: 1px solid var(--line); }
.deal-head .name { font-weight: 600; }
.deal-head .who { margin-top: .15rem; font-size: 12px; }

/* ---------- реестр ---------- */
.panel.registry { margin: .75rem 1rem; }
table.rows { border-collapse: collapse; width: 100%; }
table.rows thead th { position: sticky; top: 0; z-index: 2; background: #fff;
                      font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
                      color: var(--muted); font-weight: 600; text-align: left;
                      padding: .35rem .5rem; border-bottom: 1px solid var(--line); }
table.rows td { padding: .3rem .5rem; border-bottom: 1px solid var(--line-soft);
                vertical-align: middle; }
table.rows tbody tr:hover td { background: #fbfbfc; }
table.rows th.money, table.rows td.money { text-align: right; }
table.rows .num { width: 5.5rem; }
/* Состояние, сумма и маржа сужены, автор расширен и не переносится —
   правка владельца 11.09.2026: «Стародубова Мария» ломалась на две строки,
   а три числовых столбца держали пустоту. */
table.rows .st { width: 6.5rem; }
table.rows .sum { width: 8rem; }
table.rows .mg { width: 7rem; }
/* Звёздочка неполной маржи: приглушена и с курсором-вопросом — число рядом
   с ней не полное, и объяснение приходит наведением, не занимая места. */
table.rows .part { color: var(--muted); cursor: help; }
/* Счётные столбцы прайс-листов одной ширины: числа в них выстраиваются
   столбиком, а заголовки не растягивают таблицу каждый по-своему. */
table.rows .cnt { width: 6.5rem; }
table.rows .disc { width: 5.5rem; }
table.rows .who { width: 11rem; white-space: nowrap; }
table.rows .when { width: 6.5rem; }
/* «Создан» в списке прайсов несёт и дату, и время — 6,5 rem под это мало, и
   «16.08.2026 21:45» переносилось на вторую строку, растягивая каждую строку
   реестра вдвое (находка владельца 16.08.2026). nowrap здесь не лишний:
   ширина считается от шрифта, а он у пользователя может быть крупнее. */
table.rows .made { width: 9.5rem; white-space: nowrap; }
/* Строка реестра КП ведёт в документ целиком, а не одной ячейкой с номером
   (решение владельца 16.08.2026). Курсор обязателен: без него о том, что
   строка нажимается, узнать неоткуда. Переход вешает скрипт списка. */
#quote-registry tr[data-href] { cursor: pointer; }
/* Короткие колонки реестра КП — по ширине значения: 1 % таблица растягивает
   ровно до содержимого, а всё остальное место отдаёт «Клиенту», который
   иначе переносился на вторую строку. Правка владельца 28.09.2026: ширины
   table.rows выше брались с запасом, и между номером, датой и клиентом, а
   перед суммой стояло пустое место. Только здесь: те же классы носят
   реестр прайс-листов и экран сделки. nowrap не лишний — без него колонка
   ужимается до самого длинного слова и сумма рвётся по пробелам. */
#quote-registry .num, #quote-registry .when,
#quote-registry .sum, #quote-registry .mg { width: 1%; white-space: nowrap; }
table.rows a { color: var(--ink); text-decoration: none; font-weight: 600; }
table.rows a:hover { text-decoration: underline; }
/* Живые числа черновика приглушены: они ещё поменяются. */
table.rows .sum.live, table.rows .mg.live { color: var(--muted); }
/* Убыток — тем же янтарным, что в составе КП: один смысл, один цвет. */
table.rows .mg.below { color: var(--warn); font-weight: 600; }
.dim { color: var(--muted); }
.empty-state { padding: 2.5rem 1rem; text-align: center; color: var(--muted); }
.empty-state b { display: block; color: var(--ink); font-size: 14px; margin-bottom: .3rem; }

/* ---------- строка документа ---------- */
.doc { display: flex; align-items: center; gap: .7rem; padding: .45rem 1rem;
       background: var(--panel); border-bottom: 1px solid var(--line); }
.doc .num { font-size: 16px; font-weight: 600; }
.doc .by { color: var(--muted); }
.doc .acts { margin-left: auto; display: flex; align-items: center; gap: .45rem; }
.doc .acts form { display: inline; }
.doc .acts .gap { width: .9rem; }
.chip { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
        padding: .1rem .4rem; border-radius: 3px;
        background: #ececed; color: var(--muted); white-space: nowrap; }
/* Черновик обведён, а не залит: залитая плашка читается как «готово»,
   а черновик — это как раз «ещё нет». */
.chip.draft { background: none; border: 1px solid var(--line); }

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

   Отступ теперь у содержимого: у `.panels` на экране сборки, у
   `.panel.registry` в реестрах, у `.work` на отборе прайса. Страницы без
   полос (состояние поставщиков, вход) просят его сами — `body.plain`. */
main { padding: 0; }
body.plain main { padding: 1.25rem; }
h1 { font-size: 22px; margin: 0 0 .15rem; }
.subtitle { color: var(--muted); margin: 0 0 1rem; }

.card { background: #fff; border: 1px solid var(--line); border-radius: 8px;
        padding: .9rem 1.1rem; margin-bottom: 1rem; }
.card > h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
             color: var(--muted); margin: 0 0 .7rem; font-weight: 600; }

/* Fields grid: label above input, columns adapt to the window width. */
.fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
          gap: .6rem .9rem; }
.fields label { display: block; font-size: 12px; color: var(--muted); margin-bottom: .15rem; }
.fields input, .fields select { width: 100%; padding: .3rem .45rem;
                                border: 1px solid var(--line); border-radius: 4px;
                                font: inherit; background: #fff; }
.fields .check { display: flex; align-items: center; gap: .4rem; padding-top: 1.15rem; }
.fields .check input { width: auto; }
.fields .help { display: block; font-size: 12px; color: var(--muted); }

/* ---------- вход ----------
   Карточка по середине окна, а не форма, прижатая к левому верхнему углу
   (находка владельца 16.08.2026). Отступ сверху долей окна, а не половиной
   высоты через flex: страница `body.plain` прокручивается как обычная, и
   вертикальное центрирование прижало бы форму к нижнему краю на низком окне.
   Подписи и поля выравнивает общая сетка `.fields`. */
.signin { max-width: 320px; margin: 12vh auto 0; }
.signin .card { margin-bottom: 0; }
.signin h1 { font-size: 18px; margin: 0 0 .8rem; }
.signin .fields { gap: .7rem; }
.signin .error { margin: 0 0 .7rem; }
/* Кнопка во всю ширину карточки: единственное действие экрана, промахнуться
   по нему быть нечем. */
.signin .primary { width: 100%; margin-top: .9rem; padding: .4rem .7rem; }

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

   Высота берётся из остатка окна, а не долей вроде 64vh и не вычитанием
   высот шапок в calc(). Доля подбиралась под старую высокую шапку: после
   того как шапка ужалась, под панелями оставалась полоса пустоты в треть
   экрана. Вычитание в calc() пришлось бы пересчитывать при каждой правке
   шапки, а ошибка вынесла бы закреплённую строку «Итого» за нижний край.
   Остаток отдаёт flex — вычитать нечего. */
/* Высоту окна занимает только экран сборки: класс ставит его шаблон. Реестр,
   прайсы и статус поставщиков прокручиваются страницей, как обычно, — общая
   фиксация высоты обрезала бы им содержимое. */
body.fills-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
body.fills-screen main { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.panels { display: grid; grid-template-columns: 200px minmax(300px, 1fr) minmax(430px, 1.25fr);
          gap: .75rem; flex: 1; min-height: 380px; padding: .75rem 1rem 1rem; }
/* На ноутбучных 1366 составу доставалось 608 px, из них названию модели —
   183: длинные названия уходили в четыре строки, строка вырастала до 126 px,
   и в панель влезали две позиции. Забираем ширину у дерева категорий и у
   подбора: там строка переносится безболезненно, а в составе каждый перенос
   стоит видимой позиции. Замерено в браузере 15.08.2026. */
@media (max-width: 1440px) {
  .panels { grid-template-columns: 168px minmax(260px, 1fr) minmax(430px, 1.7fr);
            gap: .6rem; padding: .6rem; }
}
#catalog { display: contents; }
.panel { background: #fff; border: 1px solid var(--line); border-radius: 6px;
         display: flex; flex-direction: column; min-height: 0; }
.panel > h3 { margin: 0; padding: .4rem .6rem; border-bottom: 1px solid var(--line);
              font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
              color: var(--muted); font-weight: 600; }
.panel > .tools { padding: .5rem .7rem; border-bottom: 1px solid var(--line);
                  display: flex; gap: .4rem; }
.panel > .tools input[type=search] { flex: 1; min-width: 0; }
.panel > .foot { border-top: 1px solid var(--line); padding: .5rem .7rem;
                 display: flex; gap: .4rem; background: #fafafa; }
.panel > .foot input[type=text] { flex: 1; min-width: 0; }
.panel .body { overflow: auto; flex: 1; padding: .5rem .7rem; }
.panel .body.flush { padding: 0; }
.note { color: var(--muted); font-size: 12px; margin: 0 0 .4rem; }

.tree { list-style: none; margin: 0; padding: .35rem .5rem; }
.tree li { margin-bottom: .12rem; }
/* Ссылки цвета текста, а не браузерного синего: это разделы каталога, а не
   ссылки в интернете. Выбранный раздел подсвечен фоном — иначе на длинном
   дереве непонятно, что именно показано справа. */
.tree a { text-decoration: none; color: var(--ink); display: block;
          padding: .1rem .25rem; border-radius: 3px; }
.tree a:hover { background: #f4f4f6; }
.tree a.on { background: #f0f0f2; font-weight: 600; }
.tree .count { color: var(--muted); font-size: 11px; }
/* Ветка свёрнута, пока раздел не выбран: раскрытое дерево на 45 узлов
   не читается. Класс open ставит сервер, поэтому состояние переживает
   обмен HTMX, чего скрипт на стороне страницы не дал бы. */
.branch { list-style: none; margin: .2rem 0 .4rem; padding-left: .9rem;
          border-left: 1px solid var(--line); display: none; }
/* Без привязки к первому уровню: правило .tree > li.open > .branch
   раскрывало только второй уровень, а третий оставался невидимым
   навсегда, хотя шаблон рисует любую глубину. */
li.open > .branch { display: block; }
.tree > li > a { font-weight: 600; }
.branch a { font-weight: 400; }

/* Превью товара в подборе. Файл один и тот же на оба состояния: в
   списке он ужат до 44 px, по наведению показывается в своём размере
   (240 px). Поэтому увеличение мгновенное: браузер уже держит
   картинку, и нет ни второго запроса, ни мигания подгрузкой.

   Из потока вынимается картинка, а место под неё держит обёртка
   .thumb — она остаётся в строке своими 44 px. Раньше в absolute
   уходила сама картинка, то есть единственный элемент, занимавший
   это место: строка теряла 52 px (44 картинки и 8 отступа), текст
   уезжал влево под курсором, а все строки ниже подпрыгивали на 8 px.
   Замерено в браузере 10.08.2026.

   Правила не привязаны к панели подбора: та же миниатюра стоит в строке
   состава КП. Место для всплывающей копии даёт контейнер, объявивший
   себя точкой отсчёта, — .goods li в подборе и #items .pic в составе. */
.thumb { display: block; width: 44px; height: 44px; flex: none;
         background: #f4f4f6; border-radius: 3px; }
.thumb img { width: 100%; height: 100%; object-fit: contain; border-radius: 3px; }
.goods li { position: relative; }
.thumb:hover img { position: absolute; left: 0; top: 0; z-index: 5;
                   width: 240px; height: 240px; background: #fff;
                   border: 1px solid var(--line); border-radius: 4px;
                   box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 4px; }

.goods { list-style: none; margin: 0; padding: 0; }
.goods li { padding: .3rem .6rem; border-bottom: 1px solid var(--line-soft); display: flex;
            gap: .5rem; align-items: flex-start; }
.goods .grow { flex: 1; min-width: 0; }
.goods .name { font-weight: 600; }
.goods .sub { color: var(--muted); font-size: 11px; }
/* Акция поставщика в панели подбора — та же подпись и тот же приглушённый
   серый, что и в составе КП (#items .price .promo): менеджер видит акцию
   до того, как добавил строку. Второго состояния и здесь нет — см.
   комментарий у #items .price .promo. */
.goods .sub .promo { white-space: nowrap; }
/* Товар уже в составе документа. Приглушается название и картинка, но не
   кнопка: она в этом состоянии убирает товар, и по ней ещё нажимать. */
.goods li.in-quote .grow, .goods li.in-quote .thumb { opacity: .42; }

input, select, button { font: inherit; }
input[type=text], input[type=search], input[type=number], input[type=email],
input[type=date], select { padding: .25rem .35rem; border: 1px solid var(--line);
                           border-radius: 4px; background: #fff; }
button { padding: .2rem .55rem; border: 1px solid var(--line); border-radius: 4px;
         background: #fff; cursor: pointer; }
button:hover { border-color: var(--ink); color: var(--ink); }

table { border-collapse: collapse; width: 100%; }
th, td { padding: .35rem .5rem; border-bottom: 1px solid #f0f0f2; text-align: left;
         vertical-align: top; }
thead th { position: sticky; top: 0; background: #fff; z-index: 2;
           font-size: 12px; color: var(--muted); text-transform: uppercase;
           letter-spacing: .03em; }
/* Итоги блоком под таблицей, а не в <tfoot> внутри неё: браузер, разбирая
   кусок ответа вне таблицы, выбрасывает табличные теги, и итоги вываливались
   текстом в шапку документа. Проверено в браузере.

   Полоса в две строки вместо прежних девяти (15.08.2026): верхняя — деньги
   клиента и маржа справа, нижняя — уточнения. Прибита ко дну панели, чтобы
   «Итого» оставалось перед глазами, пока менеджер крутит скидку. */
.totals-block { border-top: 1px solid var(--line); background: #fafafa;
                padding: .4rem .6rem; display: flex; flex-direction: column; gap: .1rem; }
.totals-block .l1 { display: flex; align-items: baseline; gap: .6rem; }
.totals-block .l2 { display: flex; align-items: baseline; gap: .6rem;
                    font-size: 11px; color: var(--muted); }
.totals-block .l2 .r { margin-left: auto; }
.totals-block .l2 b { color: var(--ink); font-weight: 600;
                      font-variant-numeric: tabular-nums; }
.totals-block .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
                     color: var(--muted); font-weight: 600; }
.totals-block .total { font-size: 15px; font-weight: 700; }
/* Из чего сложилось «Итого» — рядом и мелко: это проверка, а не
   самостоятельное число, которое сравнивают с соседними. */
.totals-block .calc { color: var(--muted); font-size: 12px;
                      font-variant-numeric: tabular-nums; }
.totals-block .mgn { margin-left: auto; color: var(--muted); font-size: 12px; }
.totals-block .mgn b { color: var(--ink); font-weight: 600;
                       font-variant-numeric: tabular-nums; }
.totals-block .mgn.below b { color: var(--warn); }
/* Расхождение с выпущенным документом — предупреждение, а не справка:
   показывается, только когда оно есть. */
.mismatch { border-left: 3px solid var(--warn); background: #fff8ec;
            padding: .35rem .5rem; margin-top: .3rem; font-size: 12px; color: var(--ink); }
.mismatch b { font-variant-numeric: tabular-nums; }

/* Добавление услуги — над итогами и вне прокрутки. */
.panel > .addbar { border-top: 1px solid var(--line); padding: .45rem .6rem;
                   display: flex; align-items: center; gap: .4rem; }
.panel > .addbar .lbl { flex: none; font-size: 10px; text-transform: uppercase;
                        letter-spacing: .06em; color: var(--muted); font-weight: 600; }
.panel > .addbar .nm { flex: 1; min-width: 0; }
/* Цена услуги — той же ширины, что столбец «Цена» состава (102 px): поле
   должно выглядеть полем цены, а не полем названия. На одну вертикаль со
   столбцом оно при этом не встаёт — справа от него стоит кнопка, — и гнаться
   за этим не стоит: выравнивание держалось бы на ширине кнопки. Число
   прижато вправо, как всюду в деньгах. */
.panel > .addbar .pr { flex: none; width: 102px; text-align: right;
                       font-variant-numeric: tabular-nums; }

.money { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.margin { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.error { color: var(--err); }

/* Строка состава КП. Правка идёт прямо в значениях, поэтому поле должно
   выглядеть числом, а не окошком: рамки нет, пока на строку не навели.
   Иначе панель из десяти позиций превращается в сорок пустых рамок.
   Серым поле показывает унаследованное значение подсказкой (см.
   QuoteItemForm._show_inherited_as_placeholder) — очистка поля
   возвращает строку к общему значению документа. */
#items td { vertical-align: middle; }
/* Ширина по картинке, без правого поля: отступ до названия задан шириной
   колонки (col.c-pic ниже), а поле поверх него сдвигало бы только текст
   ячейки, которого здесь нет. position: relative держит место для
   увеличенной копии по наведению. */
#items .pic, .composition-head .pic { width: 44px; padding-right: 0;
                                      position: relative; }
#items .model .name { line-height: 1.25; }
#items .model .sub { color: var(--muted); font-size: 11px; }
#items .cell, .composition-head .cell { text-align: right; white-space: nowrap; }
#items .cell input, #items .sub input {
    border: 1px solid transparent; background: none; border-radius: 3px;
    padding: .15rem .25rem; font: inherit; text-align: right;
    font-variant-numeric: tabular-nums; }
#items .cell input { width: 100%; }
/* Ширины заданы явно: без них колонка цены сжималась до чужой ширины и
   обрезала число до «359990.» — поле не растягивается по содержимому,
   как обычная ячейка.

   Ширины в пикселях, не в rem: rem считается от корневых 16 px, а не от
   13 px текста таблицы, и на rem колонки выходят на четверть шире, отбирая
   место у названия модели. Задаются через colgroup вместе с
   table-layout: fixed — иначе поле ввода раздувает свою колонку собственной
   минимальной шириной примерно в 20 символов, и width:100% этого не
   отменяет (замерено 14.08.2026: таблица выходила на 71 px шире панели). */
table.items { table-layout: fixed; }
#items td { padding: .28rem .3rem; }
.composition-head th { position: sticky; top: 0; z-index: 2; background: #fff;
                       font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
                       color: var(--muted); font-weight: 600;
                       padding: .3rem .3rem; border-bottom: 1px solid var(--line); }
/* Заголовок стоит над значением, а не над рамкой поля.
   «Кол-во», «Цена» и «Скидка, %» правятся полями ввода, и у поля есть своя
   рамка (1 px) и своё внутреннее поле (.25rem): число внутри него кончается
   на 5 px левее правого края ячейки, а заголовок — ровно у края. Столбцы
   получались сдвинутыми друг относительно друга — «лёгкий хаос», найденный
   владельцем 16.08.2026. Добавляем эти 5 px заголовку.
   «Стоимость» из правила исключена намеренно: она не поле, а посчитанное
   число, и её заголовок с ней уже совпадает. */
.composition-head th.qty, .composition-head th.price, .composition-head th.disc {
    padding-right: calc(.3rem + .25rem + 1px); }
/* 44 картинка + 8 до текста — ровно тот отступ, что и в панели подбора
   (`.goods li { gap: .5rem }`). Прежние 46 px были меньше самой картинки:
   миниатюра вылезала за свою колонку, и до названия оставалось 2 px против
   8 px слева — разнобой, найденный владельцем 16.08.2026. Считается так:
   отступ = ширина колонки − ширина картинки, левые поля ячеек одинаковы и
   взаимно сокращаются. */
col.c-pic { width: 52px; }
col.c-qty { width: 58px; }  /* под заголовок «Кол-во», а не под число */
col.c-price { width: 102px; }
col.c-disc { width: 74px; }  /* держит заголовок «Скидка, %», а не число */
col.c-sum { width: 122px; }
col.c-drop { width: 26px; }
/* Ниже 1500 px «, %» в заголовке прячется, а колонка сжимается: полные
   74 px забираются у названия модели, оно уходит в третью строку, и строка
   растёт с 63 до 74 px. Замерено на окне 1280. */
@media (max-width: 1500px) {
  col.c-disc { width: 64px; }  /* ровно под слово «Скидка» без «, %» */
  .composition-head .pct { display: none; }
}
/* Услуги отчёркнуты от товаров: это конец каталожной части списка. Черту
   рисует соседство, а не признак «первая услуга»: сервер о порядке строк в
   разметке ничего не знает, а подмена одной строки после правки соседей не
   трогает. Только услуги подряд — черты нет вовсе, отделять нечего. */
#items tr:not(.service) + tr.service td { border-top: 1px dashed var(--line); }
#items tr.service .sub .who { font-style: italic; }
/* Поля уже своих колонок и прижаты вправо: рамка по размеру значения, а не
   по размеру заголовка. Больше «10 000 000» в цене и двух знаков в скидке
   в этих полях не бывает. */
#items td.qty input { width: 38px; display: block; margin-left: auto; }
#items td.price input { width: 92px; display: block; margin-left: auto; }
#items td.disc input { width: 38px; display: block; margin-left: auto; }
/* Стрелки счётчика у количества съедали бы место справа и дёргали число
   при наведении — ввод здесь набором, а не щелчками. */
#items input[type=number] { appearance: textfield; -moz-appearance: textfield; }
#items input[type=number]::-webkit-outer-spin-button,
#items input[type=number]::-webkit-inner-spin-button { appearance: none; margin: 0; }
/* Срок стоит в подписи под названием, а не в столбце цифр: это не число,
   а слово («В наличии», «14 дней»), и в узкой панели столбец под него
   уводил таблицу в горизонтальную прокрутку. */
/* Срок прижат вправо и не переносится, а бренд с артикулом при нехватке
   места усекаются многоточием. Иначе подпись уходила в третью строку и
   позиция занимала на экране столько же, сколько раньше. Ширины хватает
   на «В наличии»: обрезанная подсказка читалась бы как обрывок слова. */
#items .sub { display: flex; align-items: baseline; gap: .35rem; }
#items .sub .who { overflow: hidden; text-overflow: ellipsis;
                    white-space: nowrap; min-width: 0; }
#items .sub .lead { flex: none; }
/* Поле срока — ровно по подсказке «В наличии», без запаса (просьба владельца
   02.09.2026: рамка была заметно шире слова внутри).

   Размер шрифта тот же, что у подписи вокруг: 12 px против 11 px делали поле
   крупнее бренда и артикула рядом — рамка бросалась в глаза сильнее самого
   значения. 62 px — это «В наличии» при 11 px system-ui (52,4 px, замерено
   на боевом 02.09.2026) плюс поля и рамка. Уже — и подсказка обрежется
   до обрывка слова; шире — вернётся пустая рамка. Число подписано тестом
   test_the_lead_time_field_is_no_wider_than_its_hint. */
#items .sub input { width: 62px; font-size: 11px; text-align: left;
                    padding: .1rem .2rem; }
#items tr:hover .cell input, #items tr:hover .sub input { border-color: var(--line); }
#items .cell input:focus, #items .sub input:focus {
    border-color: var(--ink); background: #fff; outline: none; }
/* Стоимость числом, маржа подписью под ней — столбца «Маржа» больше нет
   (решение владельца 15.08.2026, отменяет обратный порядок от 10.08).
   Освободившееся место ушло названию модели.

   Подпись НЕ усекается многоточием: усечённое число («маржа 1 319 5…»)
   читается как другое число. Если места не хватает — сокращать надо
   колонку, а не деньги. */
#items .sum .val { text-align: right; font-variant-numeric: tabular-nums;
                   white-space: nowrap; }
#items .sum .mg { text-align: right; font-size: 11px; color: var(--muted);
                  white-space: nowrap; }
/* Убыток остаётся заметным, хотя маржа и понижена до подписи: янтарный тут
   тот же, что и в итоговой полосе, — один смысл, один цвет. */
#items .sum .mg.below { color: var(--warn); font-weight: 600; }
/* Акция поставщика (версия 4, решение владельца 11.09.2026) — подпись под
   ценой, тем же приёмом, что маржа под стоимостью: колонка не заводится,
   число живёт подписью у своего числа. «акция 199 990» занимает ровно 92 px
   — всю ширину поля цены (col.c-price), запаса на слово длиннее нет.
   Строка с подписью и так на 1 px выше остальных (49 против 48 на 1920) —
   высоту строке уже задаёт маржа под стоимостью, вторая подпись это не
   меняет.

   Второго состояния у подписи нет. Был вариант подсвечивать янтарным
   строки, где наша цена клиенту выше витрины поставщика, — отвергнут
   замером: при обычной скидке 10–15 % такими оказываются 78–91 % акционных
   позиций (82 позиции в выгрузке Фитатлона от 11.09.2026). Подсветка,
   горящая почти всегда, — не предупреждение, а фон: она обесценила бы
   янтарный цвет, которым помечена отрицательная маржа. */
#items .price .promo { text-align: right; font-size: 11px; color: var(--muted);
                       white-space: nowrap; font-variant-numeric: tabular-nums; }
#items .drop, .composition-head .drop { width: 1.7rem; padding-left: 0;
                                        text-align: right; }
#items .drop button { border-color: transparent; color: var(--muted);
                      padding: .1rem .3rem; }
#items tr:hover .drop button { border-color: var(--line); }

/* Дерево выбора прайс-листа (prices/new.html): чекбоксы, не ссылки, и
   не сворачивается: при отборе категорий нужно видеть сразу оба
   уровня, а не листать каждую ветку кликом, как в подборе КП. */
.price-tree, .price-branch { list-style: none; margin: 0; padding: 0; }
.price-branch { padding-left: .9rem; margin: .2rem 0 .4rem; border-left: 1px solid var(--line); }
.price-tree li, .price-branch li { margin-bottom: .2rem; }
.price-tree .count, .price-branch .count { color: var(--muted); font-size: 12px; }
.price-tree label.empty, .price-branch label.empty, label.uncategorised.empty { color: var(--muted); }
/* Категории столбцами (решение владельца 16.08.2026): одним вертикальным
   списком шесть десятков узлов с подкатегориями просили длинной прокрутки,
   а справа оставалась пустая половина панели.

   Число столбцов не назначено, а выведено из ширины: браузер ставит столько
   колонок по 15rem, сколько поместится, — на широком экране три, на
   ноутбучном два, на узком один. Назначь их числом — на 1366 колонки вышли
   бы по 9 rem и названия рвались бы по слогам.

   break-inside: avoid на узле первого уровня обязателен: без него категория
   остаётся внизу одного столбца, а её подкатегории уезжают в начало
   следующего — вложенность перестаёт читаться вовсе. Правило только на
   `.price-tree > li`: внутри ветки разрывать нечего, она и так неделима. */
.price-tree { columns: 15rem; column-gap: 1.4rem; }
.price-tree > li { break-inside: avoid; -webkit-column-break-inside: avoid; }
/* Бренды — тем же приёмом. Прежде это был поток flex-wrap: из-за разной
   длины названий чекбоксы соседних строк не вставали друг под другом. */
.checkbox-list { list-style: none; margin: 0; padding: 0;
                 columns: 13rem; column-gap: 1.4rem; }
.checkbox-list li { break-inside: avoid; -webkit-column-break-inside: avoid;
                    margin-bottom: .2rem; }
label.uncategorised { display: block; margin-top: .5rem; }
