/* ============================================================
   Shop-CRM — компоненты кабинета.
   Перенесены из согласованного прототипа (демо кабинета) без переработки:
   цвета, плотность таблиц, формы, карточки и итоги — те же.
   Общий слой (.btn, .card, .modal, .field, .scrim и т.п.) здесь
   отсутствует — его роль исполняет Bootstrap (мост в tokens.css).
   Начинка .toast и тумблер .sw — в файле, это стили демо.
   Токены и базовые правила (*, html, body) — в tokens.css.
   ============================================================ */

/* ---------- base ---------- */
h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.018em;text-wrap:balance}
h1{font-size:21px} h2{font-size:16px} h3{font-size:14px} h4{font-size:13px}
p{margin:0}
b,strong{font-weight:600}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:0;padding:0;text-align:left}
table{border-collapse:collapse;width:100%}
/* В прототипе вся навигация была на кнопках. В настоящем кабинете это
   ссылки — чтобы работали «назад», средняя кнопка мыши и новая вкладка.
   Поэтому типовое подчёркивание снимаем с элементов интерфейса и оставляем
   лишь там, где ссылка стоит внутри текста. */
a{color:inherit;text-decoration:none}
p a,.sub a,.psb a,.empty p a,.tfoot a{color:var(--c);text-decoration:underline}
a.svc,a.pl,a.chip2,a.navbtn{display:flex}
a.pl{flex-direction:column}
::selection{background:var(--c-soft);color:var(--c-ink)}
:focus-visible{outline:2px solid var(--c);outline-offset:2px;border-radius:var(--r)}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:500;letter-spacing:-.01em}
/* Пояснение рядом с заголовком и сноска под блоком. В демо .hint описан
   только для шапки карточки (.card-h), а у нас шапка бутстраповская — то
   есть правило не портировалось, и все 22 подписи набирались обычным
   текстом в 14 px, наравне с данными. Набор взят у имени файла (.ff-name):
   пояснение должно звучать тише того, что поясняет. В демо здесь 12 px
   цветом --faint — то же решение, отличаются полкегля и оттенок.
   Частные правила (.job, .pricebox, .setup-step) сильнее и остаются. */
.hint{font-size:12.5px;color:var(--muted)}
/* Действие в шапке карточки — ссылка, а не кнопка. Кнопка там уже стояла:
   «Усі» в «Останніх замовленнях» была `.btn.btn-sm` ростом 28 px, а справа
   от трёх соседних заголовков стоит `.hint` ростом 19. Шапка с кнопкой
   вырастала до 45 px против 36 у соседей, и две карточки в одном ряду
   переставали совпадать заголовками.

   Рост берётся у `.hint`, чтобы правая часть шапки была одной высоты, чем
   бы её ни занимали; цвет — акцентный, потому что по ней всё-таки идут. */
.card-header .hlink{font-size:12.5px;line-height:1.5;white-space:nowrap;color:var(--c);text-decoration:none}
.card-header .hlink:hover{text-decoration:underline}

.hidden{display:none !important}
.i{width:16px;height:16px;flex:none;stroke:currentColor;fill:none;stroke-width:1.6;
   stroke-linecap:round;stroke-linejoin:round;display:block}
.i-14{width:14px;height:14px} .i-18{width:18px;height:18px} .i-20{width:20px;height:20px}
.i-24{width:24px;height:24px} .i-28{width:28px;height:28px}
.eyebrow{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint)}

/* ---------- app shell ---------- */
/* Ссылка обхода блоков: не видна, пока не получит фокус с клавиатуры */
.skip{position:absolute;left:8px;top:-56px;z-index:100;padding:9px 14px;border-radius:var(--r);
  background:var(--k);color:var(--on-k);font-size:13px;font-weight:600;text-decoration:none;
  transition:top .12s}
.skip:focus{top:8px}
.shell{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);min-height:100vh}
.sidebar{background:var(--sidebar);border-right:1px solid var(--sidebar-line);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;z-index:60}
.reg{display:grid;grid-template-columns:1fr 1fr;gap:1.5px;width:var(--brand-size);height:var(--brand-size);
  border-radius:var(--r);overflow:hidden;flex:none}
.reg i{display:block}
.reg i:nth-child(1){background:#00A9E0}.reg i:nth-child(2){background:#E5007E}
.reg i:nth-child(3){background:#FFD100}.reg i:nth-child(4){background:#2A2622}
.side-brand{display:flex;align-items:center;gap:10px;padding:15px 14px 14px;border-bottom:1px solid var(--sidebar-line)}
.side-brand .nm{min-width:0;flex:1}
.side-brand .t{display:block;color:#EDF2F8;font-weight:600;font-size:13.5px;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.side-brand .s{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--sidebar-txt-2);margin-top:1px}
.plan{font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.09em;padding:2px 5px;
  border-radius:var(--r);background:rgba(0,169,224,.14);color:#5CC6F2;border:1px solid rgba(0,169,224,.28)}
.side-scroll{flex:1;overflow-y:auto;padding:12px 10px 8px;scrollbar-width:thin}
.navgrp+.navgrp{margin-top:16px}
.navgrp .lbl{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--sidebar-txt-2);padding:0 10px 7px}
.navbtn{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border-radius:var(--r);
  color:var(--sidebar-txt);font-size:13.5px;font-weight:500;position:relative;
  transition:background .12s,color .12s}
.navbtn .i{opacity:.75}
.navbtn:hover{background:var(--sidebar-2);color:#DCE4EE}
.navbtn.on{background:var(--sidebar-active);color:#F2F6FA;font-weight:600}
.navbtn.on .i{opacity:1;color:#5CC6F2}
.navbtn.on::before{content:"";position:absolute;left:-10px;top:7px;bottom:7px;width:2px;
  border-radius:0 2px 2px 0;background:#00A9E0}
.navbtn .badge{margin-left:auto;font-family:var(--mono);font-size:10px;font-weight:600;
  padding:1px 6px;border-radius:20px;background:var(--sidebar-active);color:var(--sidebar-txt)}
.navbtn.on .badge{background:rgba(0,169,224,.16);color:#7FD3F5}
.navbtn .badge.hot{background:rgba(229,0,126,.2);color:#FF7DBC}
/* Подпись под «Зверненнями»: тише пунктов меню, но читаемая — она и есть
   приглашение написать нам. Отступ слева тот же, что у текста пунктов
   (10px паддинга + 16px иконки + 10px зазора), чтобы строка стояла под
   их подписями, а не под иконками. */
.navhint{margin:4px 10px 0 36px;font-size:11.5px;line-height:1.35;color:var(--sidebar-txt-2)}
.side-foot{border-top:1px solid var(--sidebar-line);padding:10px}
.userchip{display:flex;align-items:center;gap:9px;width:100%;padding:7px 8px;border-radius:var(--r);
  transition:background .12s}
.userchip:hover{background:var(--sidebar-2)}
.userchip .av{width:28px;height:28px;border-radius:var(--r);background:linear-gradient(150deg,#1F6FBF,#0E4C8A);
  color:#fff;display:grid;place-items:center;font-size:11.5px;font-weight:600;flex:none}
.userchip .un{min-width:0;flex:1}
.userchip .un b{display:block;color:#DFE6EF;font-size:12.5px;font-weight:600;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.userchip .un span{display:block;font-size:11px;color:var(--sidebar-txt-2)}
.userchip .i{color:var(--sidebar-txt-2)}

/* ---------- topbar ---------- */
.app{min-width:0;display:flex;flex-direction:column}
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:12px;
  height:56px;padding:0 20px;background:color-mix(in srgb,var(--surface) 86%,transparent);
  backdrop-filter:saturate(1.6) blur(10px);border-bottom:1px solid var(--line)}
.crumbs{display:flex;align-items:center;gap:7px;min-width:0;flex:1 1 auto;font-size:13px}
.crumbs .c0{color:var(--faint);white-space:nowrap}
.crumbs .sep{color:var(--line-3)}
.crumbs .c1{font-weight:600;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.burger{display:none}
.tb-search{display:flex;align-items:center;gap:8px;height:32px;padding:0 9px;min-width:140px;max-width:280px;flex:1;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);color:var(--faint);
  transition:border-color .12s}
.tb-search:hover{border-color:var(--line-2)}
.tb-search span{flex:1;font-size:12.5px;color:var(--faint);overflow:hidden;white-space:nowrap}
.kbd{font-family:var(--mono);font-size:10px;font-weight:600;color:var(--faint);
  border:1px solid var(--line-2);border-bottom-width:2px;border-radius:var(--r);padding:1px 4px;background:var(--surface)}
.tb-right{margin-left:auto;display:flex;align-items:center;gap:8px}
.clientsel{display:flex;align-items:center;gap:7px;height:32px;padding:0 8px 0 10px;
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface);max-width:290px}
.clientsel .lb{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.clientsel select{border:0;background:none;font-size:12.5px;font-weight:600;max-width:200px;height:auto;
  outline:none;padding:0;cursor:pointer;box-shadow:none;text-overflow:ellipsis}
.iconbtn{width:32px;height:32px;border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  display:grid;place-items:center;color:var(--muted);position:relative;
  transition:color .12s,border-color .12s,background .12s}
.iconbtn:hover{color:var(--ink);border-color:var(--line-2);background:var(--surface-2)}
.iconbtn .dotmark{position:absolute;top:4px;right:4px;width:6px;height:6px;border-radius:50%;
  background:var(--m);box-shadow:0 0 0 2px var(--surface)}
.avatar{width:32px;height:32px;border-radius:var(--r);background:linear-gradient(150deg,#1F6FBF,#0E4C8A);
  color:#fff;display:grid;place-items:center;font-size:12px;font-weight:600;flex:none}

/* ---------- page frame ---------- */
.page{padding:22px 22px 44px;max-width:1320px;width:100%;margin:0 auto;flex:1}
.ph{display:flex;align-items:flex-end;gap:14px;margin-bottom:18px;flex-wrap:wrap}
.ph .ph-t{min-width:0}
.ph h1{font-size:22px;letter-spacing:-.025em}
/* margin снизу гасим: в демо подпись — это div, у нас p, и он приносит
   бутстраповский margin-bottom:1rem — лишние 16 px под каждым заголовком */
.ph .sub{color:var(--muted);font-size:13px;margin:3px 0 0}
.ph .ph-a{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
/* Группа внутри действий шапки: часть кнопок живёт на одной вкладке, часть
   на всех, а второй `.ph-a` разорвал бы свободное место своим margin:auto. */
.ph .ph-a .ph-a-group{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.ph .ph-a input[type=date]{width:auto;min-width:0}
.ph .ph-a input,.ph .ph-a select{height:34px}
.sec-h{display:flex;align-items:center;gap:10px;margin:26px 2px 12px}
.sec-h h2{font-size:13px}
.sec-h .rule{flex:1;height:1px;background:var(--line)}

/* ---------- buttons ---------- */
/* демо: цвета и варианты .btn.pri/.acc/.mg/.ghost — закрывается Bootstrap
   (переменные --bs-btn-* в tokens.css). Портировано то, чего он не даёт.

   Раскладка: в демо .btn это inline-flex с gap, и иконка стоит в ряд
   с надписью. У Bootstrap .btn — inline-block, а иконки проекта .i —
   display:block, поэтому надпись переносилась под иконку («Звіти» на
   «Огляді», найдено ручным QA).

   Метрики: Bootstrap набирает кнопку в 16 px/400 ростом 38, в эталоне
   13 px/600 ростом 34 — рядом с полем в 36 px и кеглем 13.5 разнобой было
   видно на каждом экране. Идут через --bs-btn-*, чтобы варианты остались
   бутстраповскими. Правило одно на все кнопки: вторая их копия по шаблонам
   расходилась бы по одной */
.btn{--bs-btn-padding-y:0;--bs-btn-padding-x:13px;--bs-btn-font-size:13px;
  --bs-btn-font-weight:600;--bs-btn-border-radius:var(--r);--bs-btn-disabled-opacity:.45;
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  height:34px;letter-spacing:-.005em;white-space:nowrap}
.btn:active{transform:translateY(.5px)}
/* .btn-sm и .btn-lg стоят в bootstrap.min.css раньше нашего .btn, а сила
   селектора у них та же — без повтора здесь их размеры перекрыло бы .btn */
.btn-sm{--bs-btn-padding-x:10px;--bs-btn-font-size:12px;--bs-btn-border-radius:var(--r);height:28px}
.btn-lg{--bs-btn-padding-x:18px;--bs-btn-font-size:14px;height:42px}
.seg{display:inline-flex;padding:2px;gap:2px;background:var(--surface-2);border:1px solid var(--line);
  border-radius:var(--r)}
.seg button{height:26px;padding:0 11px;border-radius:var(--r);font-size:12.5px;font-weight:600;
  color:var(--muted);transition:background .12s,color .12s}
.seg button:hover{color:var(--ink)}
.seg button.on{background:var(--surface);color:var(--ink);box-shadow:var(--shadow-sm)}

/* ---------- form controls ---------- */
/* демо: .field, .field>label — закрывается Bootstrap.
   Вид поля из демо привязан и к .form-control/.form-select: в шаблонах поля
   размечены бутстраповскими классами, а у класса сила селектора выше голого
   input/select. Без этого Bootstrap забирал кегль (16 px вместо 13.5) и
   паддинги, а селекту рисовал свой шеврон вместо треугольника демо.

   Типы стоят внутри :where() НАМЕРЕННО: он силы селектора не добавляет, и
   весь список весит ровно как класс. Иначе `input[type=date]` (0,1,1)
   перебивал бы модификатор `.form-control-sm` (0,1,0) ниже — поле «Готовність»
   в карточке заказа оставалось ростом 36, а соседнее поле цены (то же
   `.form-control-sm`, но без type) становилось 30 рядом с кнопкой в 28. То
   есть блок, заведённый ради общей метрики, сам её и разносил, и по коду это
   было не видно: у `select` голый тег весит меньше класса, и та же пара
   `.form-select`/`.form-select-sm` работала (найдено ревью).

   Поле файла исключено — тоже через :where(), чтобы не утяжелять селектор:
   у `input[type=file]` внутри своя кнопка, и Bootstrap ставит её
   отрицательными полями ровно под СВОИ паддинги .form-control. Демо задаёт
   полю padding:0 10px — компенсация переставала сходиться, кнопка выезжала за
   верхнюю границу, где её срезал бутстраповский
   `.form-control[type=file]{overflow:hidden}`. Поле файла в проекте рисует
   свой партиал (_partials/file_field.html.twig), нативная кнопка не
   показывается вовсе */
input:where([type=text],[type=number],[type=date],[type=search]),select,textarea,
.form-control:not(:where([type=file])),.form-select{
  height:36px;padding:0 10px;width:100%;border:1px solid var(--line-2);border-radius:var(--r);
  background:var(--surface);color:var(--ink);font-size:13.5px;outline:none;font-family:inherit;
  transition:border-color .12s,box-shadow .12s}
textarea,textarea.form-control{height:auto;padding:9px 10px;resize:vertical}
select,.form-select{cursor:pointer;padding-right:26px;appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 14px) 16px,calc(100% - 9px) 16px;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
/* Подсказка в поле — СВЕТЛЕЕ значения, и заметно.

   Своего правила у проекта не было, работало бутстраповское:
   `color:var(--bs-secondary-color); opacity:1`, то есть #5F5B54 — ровно
   наш --muted, цвет основного текста подписи. Пустое поле с подсказкой
   «2,50» выглядело заполненным, и на это попадались: в диалогах прайса
   («Заповнити порожні», «Змінити всі на %») подсказка — число, шрифт
   моноширинный, а поле пустое. Человек читал её как готовое значение,
   нажимал «Застосувати» и получал отказ на пустой ставке.

   `opacity:1` ставим сами, а не полагаемся на бутстрап: селектор ловит и
   голый `input`/`textarea` — вне `.form-control`, где бутстрап до подсказки
   не дотягивается, а Firefox по умолчанию гасит её прозрачностью. Разница
   должна быть в ЦВЕТЕ: прозрачность на цветном фоне даёт другой оттенок,
   а не другую силу.

   Цвет — не «просто светлее»: --placeholder держит 4.5:1 к фону поля
   (WCAG AA). Первый подбор дал 3.5:1 — подсказка читалась хуже основного
   текста ровно там, где она и есть единственная инструкция: в админке
   плейсхолдером набрано имя аккаунта, которое оператор обязан перепечатать,
   чтобы закрыть типографию или снести её базу. */
input::placeholder,textarea::placeholder,.form-control::placeholder{color:var(--placeholder);opacity:1}
input:focus,select:focus,textarea:focus,
.form-control:focus,.form-select:focus{border-color:var(--c);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--c) 18%,transparent)}
/* Построчная правка прямо в таблице (ставки прайса, цена позиции): в демо это
   поле мельче основного — .mtx .rate, 30 px и кегль 12.5, иначе цифры распирают
   строку. Модификатор весит как класс, ровно как блок выше, и стоит после
   него — значит перекрывает и рост, и паддинги ЛЮБОГО поля, с type и без.
   padding-right для стрелки повторён по той же причине: .form-select идёт
   выше */
.form-control-sm,.form-select-sm{height:30px;padding:0 8px;font-size:12.5px;border-radius:var(--r)}
/* Значение, которое показывают, но не правят (тип друку в карточке услуги).
   Ростом и кеглем совпадает с полем, но плоское и без рамки: рамка обещает
   ввод, которого не будет. Раньше такое значение просто не рисовали вовсе,
   и человек не понимал, ошибся ли он экраном */
.ro{display:flex;align-items:center;height:36px;font-size:13.5px;color:var(--ink)}

.form-select-sm{padding-right:22px;background-position:calc(100% - 12px) 13px,calc(100% - 7px) 13px}
/* Моно-цифры — селектором с тегом (0,1,1) НАМЕРЕННО: .form-control выше
   возвращает полю font-family:inherit, и при равном весе шрифт зависел бы от
   порядка правил. Мелкому полю это не мешает — кегль оно задаёт своё, а
   гарнитуру не трогает */
input[type=number],input[type=date]{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:500}
/* Дата по центру своего поля — и по горизонтали, и по вертикали.
   У поля с датой ширину задаёт браузер с запасом под самый широкий формат и
   под кнопку календаря, а высоту задаём мы (36px в фильтрах). Саму дату
   WebKit рисует во ВНУТРЕННЕМ элементе, до которого обычное выравнивание не
   доходит: на маке она вставала в левый верхний угол при пустом поле вокруг.
   Поэтому центрируем и внешнее поле как флекс-контейнер, и внутренний
   элемент. В Chrome ложилось иначе, и разница между машинами сбивала с
   толку сильнее самой кривизны */
input[type=date]{display:inline-flex;align-items:center;text-align:center}
input[type=date]::-webkit-datetime-edit{display:flex;align-items:center;flex:1;text-align:center;padding:0}
.row2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:11px}
.row3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px}
@media(max-width:520px){.row2,.row3{grid-template-columns:minmax(0,1fr)}}
/* Галочки вне форм Bootstrap: системный вид, но своим цветом и размером.
   Исключение обязательно — селектор с атрибутом весит больше класса, и без
   него `.form-check-input` на галочке молча проигрывал: рядом с
   переключателями оплаты, которые Bootstrap рисует сам, она читалась как
   чужой элемент. Таких галочек в кабинете восемь в шести экранах */
input[type=checkbox]:not(.form-check-input){width:16px;height:16px;accent-color:var(--c);margin:0;flex:none;cursor:pointer}
/* демо: .fhelp, .ferr (подсказка и ошибка под полем) — закрывается Bootstrap (.form-text, .invalid-feedback) */
/* демо: .card-b .field+.field и т.п. (отступы между полями формы) — закрывается Bootstrap */

/* ---------- card ---------- */
/* демо: .card, .card-h, .card-b, .card-b.flush — закрывается Bootstrap */
/* align-items:start — каждая карточка своей высоты. По умолчанию грид тянет
   их до самой высокой, и «Умови» с пятью строками растягивались на 284 px
   при содержимом в 105: сто сорок пикселей пустоты под последней строкой,
   которые читались как «здесь что-то потерялось». */
.grid2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px}
/* Три коротких поля в строку — род объявления и границы окна показа.
   На узком экране ложатся друг под друга: три поля по 90 пикселей
   не заполняет ни одно из них. */
.grid3{display:grid;grid-template-columns:repeat(3, minmax(0,1fr));gap:14px}
@media (max-width:700px){.grid3{grid-template-columns:minmax(0,1fr)}}
/* Колонка карточки клиента: свои карточки подряд, с тем же промежутком, что
   и между колонками. Раньше карточки лежали прямо в сетке и равнялись по
   высоте построчно — под короткими «Умовами» зияла пустота */
.ccol{display:flex;flex-direction:column;gap:14px;min-width:0}
.grid-wide{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);gap:14px}
.grid-narrow{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.55fr);gap:14px}
/* Схлопывание в колонку — тоже через minmax(0,…): у `1fr` минимум равен
   min-content дорожки, поэтому таблица с `min-width:680px` внутри растягивала
   бы страницу вбок вместо того, чтобы прокручиваться в своей .tbl-wrap */
@media(max-width:980px){.grid2,.grid-wide,.grid-narrow{grid-template-columns:minmax(0,1fr)}}
/* Промежуток между блоками страницы — 14 px, как в демо, и он же стоит
   между колонками сеток (`.grid-wide`, `.kpis`): по горизонтали и по
   вертикали расстояние одно, и страница читается решёткой.

   Правило было перенесено и не использовано ни разу: шаблоны ставили
   бутстраповский `.mb-4` — 24 px. Лишние десять пикселей повторялись на
   каждом стыке каждого экрана, и кабинет выглядел разреженным именно по
   высоте, хотя все остальные метрики совпадали с эталоном. */
.mb14{margin-bottom:14px}

/* ---------- pills ---------- */
.pill{display:inline-flex;align-items:center;gap:5px;height:20px;padding:0 7px;border-radius:var(--r);
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.03em;white-space:nowrap;
  border:1px solid transparent}
.pill .dt{width:5px;height:5px;border-radius:1.5px;background:currentColor;flex:none}
.pill.s-draft {color:var(--muted);background:var(--surface-3);border-color:var(--line-2)}
.pill.s-accept{color:var(--c);background:var(--c-soft);border-color:var(--c-line)}
.pill.s-print {color:var(--y);background:var(--y-soft);border-color:var(--y-line)}
.pill.s-ready {color:var(--ok);background:var(--ok-soft);border-color:var(--ok-line)}
.pill.s-done  {color:var(--muted);background:var(--surface-2);border-color:var(--line-2)}
.pill.s-reject{color:var(--bad);background:var(--bad-soft);border-color:var(--bad-line)}
.pill.p-paid  {color:var(--ok);background:var(--ok-soft);border-color:var(--ok-line)}
.pill.p-part  {color:var(--y);background:var(--y-soft);border-color:var(--y-line)}
.pill.p-debt  {color:var(--bad);background:var(--bad-soft);border-color:var(--bad-line)}
.pill.tp      {color:var(--m-ink);background:var(--m-soft);border-color:var(--m-line)}
button.pill{transition:filter .12s}
button.pill:hover{filter:brightness(.96)}
.dots{display:inline-flex;gap:3px}
.dots i{width:5px;height:13px;border-radius:2px;display:block}

/* ---------- поле файла ---------- */
/* Нативный input спрятан визуально, но остаётся в потоке и в табуляции:
   выбор файла с клавиатуры обязан работать. Фокус на нём видно по обводке
   кнопки — иначе он был бы невидим совсем */
.filefield{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.filefield>input[type=file]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
.filefield>input[type=file]:focus-visible+label{outline:2px solid var(--c);outline-offset:2px}
.filefield .ff-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:12.5px;color:var(--muted)}
/* Превью выбранного и вставленного. Своей строкой под кнопкой: имя вида
   znimok-2026-09-07-13-13-58.png не говорит ничего, а вставил человек то
   или не то — видно только глазами (жалоба владельца 07.09.2026).
   Размер тот же, что у вложения в переписке, — снимок узнаётся, но не
   заслоняет форму. */
/* align-items обязателен: по умолчанию flex растягивает элементы по высоте
   строки, и маленький снимок ехал вверх-вниз растянутой полосой */
.filefield .ff-thumbs{flex-basis:100%;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}
.ff-thumb{max-width:180px;max-height:140px;border:1px solid var(--line-3);border-radius:var(--r)}
/* Подсказка про Ctrl+V. Своей строкой (flex-basis:100%), а не рядом с именем
   файла: имя длинное, и подсказка уезжала бы за край ровно тогда, когда файл
   уже выбран, — то есть исчезала бы у того, кто как раз собрался добавить
   второй снимок */
.filefield .ff-hint{flex-basis:100%;font-size:11.5px;color:var(--muted)}

/* ---------- table ---------- */
/* position:relative обязателен: без него абсолютно позиционированный потомок
   (скрытая подпись .visually-hidden в журнале платежей) цепляется за
   ближайший позиционированный блок ВЫШЕ обёртки — за .card — и потому не
   обрезается прокруткой. Такой потомок стоял на 684-й точке широкой таблицы
   и растягивал документ до 685 px при вьюпорте 390 px: полоса прокрутки у
   всей страницы, пустая справа. */
/* Прокрутка по горизонтали нужна только там, где таблица не влезает.
   На широком экране overflow делает обёртку скролл-контейнером, липкая
   шапка прилипает к обёртке вместо страницы — то есть не прилипает вовсе.
   Порог считается: таблица 680 + поля страницы 44 + сайдбар 246 = 970,
   с запасом 1010 — ниже него полосу прокрутки обёртке возвращаем */
.tbl-wrap{position:relative}
@media(max-width:1010px){.tbl-wrap{overflow-x:auto}}
table.tbl{min-width:680px;font-size:13.5px}
table.tbl th{font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  color:var(--faint);text-align:left;padding:7px 12px;background:var(--surface-2);
  border-bottom:1px solid var(--line);white-space:nowrap}
table.tbl thead th{position:sticky;top:56px;z-index:2}
table.tbl td{padding:8px 12px;border-bottom:1px solid var(--line);vertical-align:middle;line-height:1.35}
table.tbl tbody tr:last-child td{border-bottom:0}
/* Высота строки одна на весь кабинет, эталон — список заказов.
   Правило выбора модификатора:
     .tbl        — в строке есть ПИЛЮЛЯ статуса (заказы, приходи) либо
                   ОРГАНЫ УПРАВЛЕНИЯ (кнопки склада, поля контрагентов):
                   они сами набирают нужную высоту;
     .tbl.roomy  — строка из одного текста (клиенты, документы, журнал
                   склада, состав проведённого прихода): без добавки она
                   выходит ниже соседнего экрана.
   Смешивать нельзя: добавка поверх кнопки перерастягивает строку. */
table.tbl.roomy td{padding-top:10px;padding-bottom:10px}
/* Органы управления ВНУТРИ строки таблицы — компактные.
   Те же .iconbtn и .btn-sm в шапке кабинета и в карточках остаются
   обычного размера: там они стоят сами по себе, и жать по ним удобнее.
   А в строке 32-пиксельная кнопка задаёт высоту всей строке, и «Склад»
   с «Довідниками» выходили на десяток пикселей выше «Замовлень» —
   при том, что содержимое у них такое же короткое. Размер зависит от
   места, поэтому и объявлен местом, а не отдельным классом в разметке.

   Число одно на все органы управления — 26px. Не 22, к которым тянет
   пилюля статуса (20px): <select> во многих браузерах не сжимается ниже
   своего внутреннего минимума, тогда как input и кнопка сжимаются. При 22
   получалась строка, где выпадашка выше поля ввода, — а разнобой ВНУТРИ
   строки заметнее, чем 4px между строкой с кнопками и строкой из текста.

   Высоту задаём ЯВНО. У .btn-sm выше по файлу стоит height:28px, и одни
   только кегль с полями её не отменяют: правка без height оставляла
   строку прежней, а виноватым выглядел значок. */
table.tbl .iconbtn{width:26px;height:26px}
table.tbl .btn-sm{--bs-btn-padding-y:0;--bs-btn-padding-x:7px;--bs-btn-font-size:11.5px;height:26px}
/* Поля ввода в строке — по той же причине и к той же высоте: кегль 12.5
   при поле 1px даёт ≈23px, то есть вровень со значком и кнопкой. Ниже не
   опускаем: набирать в таблице и так тесно, а нечитаемое поле хуже
   высокой строки. */
table.tbl .form-control-sm,table.tbl .form-select-sm{height:26px;padding-top:0;padding-bottom:0;font-size:12.5px}
/* Стрелка выпадашки — фон, а не потомок, поэтому за высотой поля она сама
   не едет. Отступ сверху = (высота − 5) / 2: при 36px это 16, при 30px —
   13, при 26px — 10. Забыть её значит получить поле, которое ВЫГЛЯДИТ выше
   соседнего: значок уезжает к нижнему краю и утяжеляет низ. */
table.tbl .form-select-sm{background-position:calc(100% - 12px) 10px,calc(100% - 7px) 10px}
table.tbl tbody tr.click{cursor:pointer;transition:background .1s}
table.tbl tbody tr.click:hover{background:var(--surface-2)}
table.tbl .r{text-align:right}
/* Позиции прихода: ширины колонок заданы, иначе таблица делит место по
   содержимому — под количество и сумму уходило по трети экрана, а самый
   длинный столбец, позиция, оказывался зажат. Последняя колонка узкая:
   в ней один крестик. */
table.tbl.sup-lines th:nth-child(1){width:42%}
table.tbl.sup-lines th:nth-child(2){width:20%}
table.tbl.sup-lines th:nth-child(3){width:14%}
table.tbl.sup-lines th:nth-child(4){width:16%}
table.tbl.sup-lines th:nth-child(5){width:8%}
table.tbl .cn{text-align:center}
/* Текст и значок одной строкой. Иконка .i объявлена display:block (так в
   эталоне — там она всегда лежит внутри флекса), а ячейка таблицы флексом
   не является: в обычном потоке значок вставал ПОД текстом и растягивал
   строку вдвое. Класс общий, потому что мест таких два — просрочка в
   списке заказов и карандаш ручной цены в «Собівартість і заробіток» */
table.tbl .iline{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
/* Таблица на две колонки в узкой половине .grid-wide: общие 680px туда не
   влезают (правая колонка ≈ 495px при максимуме страницы), и колонка справа
   уезжала за горизонтальный скролл — блок «Хто скільки винен» на «Звітах»
   показывал одни имена без сумм (найдено ручным QA) */
table.tbl.slim{min-width:0}
/* То же место, но у таблицы ПЯТЬ колонок — «Останні замовлення» на «Огляді».
   Одного снятого минимума ей мало: без запрета переносов пилюля статуса и
   дата ломаются на две строки, и таблица становится выше карточки рядом.
   Поэтому служебные колонки не переносятся, а уступает длинное имя клиента —
   многоточием: «Сума» справа важнее полного написания «ТОВ «Реклама-Сервіс»»
   (найдено ревью — та же болезнь, что чинили у должников) */
table.tbl.tight{min-width:0}
table.tbl.tight td,table.tbl.tight th{white-space:nowrap}
table.tbl.tight td.nm{max-width:0;width:100%;overflow:hidden;text-overflow:ellipsis}
/* склад (М5): заголовок раздела строкой таблицы, «мало» — подсветкой ряда.
   Разделы нужны потому, что без них цифра и широкоформат чередуются через
   строку; подсветка — потому что «треба закупити N» вверху не говорит, что
   именно закупить */
table.tbl.stk tr.grp th{padding-top:11px;padding-bottom:11px;background:var(--surface-3);color:var(--muted)}
table.tbl.stk tr.low td{background:var(--y-soft)}
table.tbl.stk tr.off td{color:var(--faint)}

.tfoot{display:flex;gap:22px;justify-content:flex-end;padding:9px 14px;border-top:1px solid var(--line);
  background:var(--surface-2);font-size:12.5px;color:var(--muted);flex-wrap:wrap;
  position:sticky;bottom:0;z-index:2}
.tfoot b{color:var(--ink);font-family:var(--mono);font-variant-numeric:tabular-nums}
.filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;align-items:center}
.filters .fsearch{position:relative;flex:1;min-width:160px}
.filters .fsearch .i{position:absolute;left:10px;top:10px;color:var(--faint);pointer-events:none}
.filters .fsearch input{padding-left:32px}
.filters select,.filters input[type=date]{height:36px;width:auto;min-width:0}
.dsep{color:var(--faint);font-size:13px}

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

   Рамка НЕ ставится там, где вокруг уже есть своя: в ячейке таблицы, в
   карточке и в боковой рейке. Коробка в коробке хуже отсутствия коробки,
   а внутри таблицы рамка ещё и спорит с её линиями. */
.empty{display:flex;flex-direction:column;align-items:center;text-align:center;padding:44px 24px;gap:4px;
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface)}
td .empty,.card .empty,.rail .empty,.chart .empty{border:0;background:none;padding:28px 20px}
.empty .ec{width:44px;height:44px;border-radius:var(--r);background:var(--surface-2);border:1px solid var(--line);
  display:grid;place-items:center;color:var(--faint);margin-bottom:7px}
.empty b{font-size:14px;font-weight:600}
.empty p{color:var(--muted);font-size:12.5px;max-width:290px}
/* демо: .empty .btn — закрывается Bootstrap */

/* ---------- toast ---------- */
#toasts{position:fixed;right:18px;bottom:18px;z-index:120;display:flex;flex-direction:column;gap:8px;
  max-width:min(370px,calc(100vw - 36px))}
/* Показ и скрытие — Bootstrap (toast_controller), начинка — демо:
   квадратик с иконкой, жирный вердикт, под ним пояснение (p1_css) */
.toast{display:flex;align-items:flex-start;gap:10px;padding:11px 13px;border-radius:var(--r);
  background:var(--surface);border:1px solid var(--line-2);box-shadow:var(--shadow-lg)}
.toast .ti{width:20px;height:20px;border-radius:var(--r);display:grid;place-items:center;flex:none;margin-top:1px}
.toast.ok .ti{background:var(--ok-soft);color:var(--ok)}
.toast.info .ti{background:var(--c-soft);color:var(--c)}
.toast.warn .ti{background:var(--y-soft);color:var(--y)}
.toast b{display:block;font-size:13px;font-weight:600}
.toast span{display:block;font-size:12px;color:var(--muted);margin-top:1px}

/* ---------- тумблер (экран «Друкарня») ---------- */
.sw{width:36px;height:20px;border-radius:20px;background:var(--line-2);position:relative;flex:none;
  border:0;padding:0;transition:background .16s}
.sw i{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;
  box-shadow:0 1px 2px rgba(0,0,0,.3);transition:transform .16s}
.sw.on{background:var(--c)}
.sw.on i{transform:translateX(16px)}

/* ---------- modal / command palette ---------- */
/* демо: .scrim (@keyframes fin), .modal, .modal.wide (@keyframes mup),
   .modal-h, .modal-b, .modal-f — закрывается Bootstrap */
/* демо: .pal .modal-b — закрывается Bootstrap */
.pal-in{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--line)}
.pal-in input{border:0;height:auto;padding:0;font-size:15px;background:none}
.pal-in input:focus{box-shadow:none}
.pal-list{max-height:330px;overflow:auto;padding:7px}
.pal-sec{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);padding:9px 10px 5px}
.pal-it{display:flex;align-items:center;gap:11px;width:100%;padding:9px 10px;border-radius:var(--r);font-size:13.5px}
.pal-it .i{color:var(--muted)}
.pal-it .sh{margin-left:auto;font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.pal-it:hover,.pal-it.on{background:var(--surface-2)}
.pal-it.on .i{color:var(--c)}

/* ============================================================
   Экраны
   ============================================================ */

/* ---------- KPI ---------- */
.kpis{display:grid;gap:14px;grid-template-columns:repeat(4,minmax(0,1fr))}
.kpis.k3{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Одна плитка в ряду: «Звіти» без тарифной прибыли (М8) — виручка остаётся
   одна, и держать её в трети экрана рядом с пустотой хуже, чем во всю ширину.
   Двух классов хватает, чтобы правило пережило медиазапросы на .kpis ниже. */
.kpis.k1{grid-template-columns:minmax(0,1fr)}
@media(max-width:1080px){.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.kpis.k3{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:760px){.kpis,.kpis.k3{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:460px){.kpis,.kpis.k3{grid-template-columns:minmax(0,1fr)}}
.kpi{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:14px 15px 0;
  box-shadow:var(--shadow-sm);display:flex;flex-direction:column;overflow:hidden;position:relative}
.kpi .kt{display:flex;align-items:center;gap:7px}
.kpi .kt .lb{font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.kpi .kt .i{color:var(--faint);margin-left:auto;width:14px;height:14px}
.kpi .kv{font-size:27px;font-weight:600;letter-spacing:-.035em;margin-top:9px;
  font-family:var(--mono);font-variant-numeric:tabular-nums;line-height:1.1}
.kpi .kv .cur{font-size:15px;font-weight:500;color:var(--faint);letter-spacing:0;margin-left:3px}
.kpi .kd{display:flex;align-items:center;gap:5px;font-size:12px;margin-top:5px;color:var(--muted)}
.kpi .kd b{font-family:var(--mono);font-weight:600}
.kpi .kd.up b{color:var(--ok)} .kpi .kd.dn b{color:var(--bad)}
.kpi .spark{margin:8px -15px 0;height:34px;display:block;width:calc(100% + 30px)}
.kpi.warn{border-color:var(--bad-line)}
.kpi.warn .kv{color:var(--bad)}
.kpi.gain{border-color:var(--ok-line)}
.kpi.gain .kv{color:var(--ok)}
.kpi .pad{padding-bottom:15px}

/* ---------- attention list ---------- */
/* Пять строк — и дальше прокрутка. Блок собирается из ТРЁХ групп по три
   (прострочені, готові до видачі, чекають оплати), то есть в худшем случае
   это девять строк: карточка вырастала до полутысячи пикселей, тянула за
   собой соседний график — грид равняет их по высоте — и «Огляд» переставал
   помещаться в экран.

   243 px — это ровно столько, сколько остаётся карточке от высоты соседнего
   графика: 281 минус шапка 36 и две рамки. Список при этом обрезается на
   ЧЕТВЁРТОЙ строке с четвертью — обрезанная строка честнее ровной пятой:
   она сама говорит, что список продолжается, и полоса прокрутки для этого
   даже не нужна. Ровная пятёрка выглядела концом списка и заодно делала
   карточку выше графика, а грид тянул график следом. */
.attn{display:flex;flex-direction:column;max-height:243px;overflow-y:auto}
.attn a,.attn button{display:flex;align-items:center;gap:11px;padding:9px 14px;border-bottom:1px solid var(--line);
  width:100%;transition:background .1s}
.attn button:last-child{border-bottom:0}
.attn button:hover{background:var(--surface-2)}
.attn .mk{width:26px;height:26px;border-radius:var(--r);display:grid;place-items:center;flex:none}
.attn .mk.d{background:var(--bad-soft);color:var(--bad)}
.attn .mk.t{background:var(--y-soft);color:var(--y)}
.attn .mk.o{background:var(--ok-soft);color:var(--ok)}
.attn .mk.m{background:var(--m-soft);color:var(--m)}
.attn .tx{flex:1;min-width:0}
.attn .tx b{display:block;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attn .tx span{display:block;font-size:11.5px;color:var(--muted);margin-top:1px}
.attn .vv{font-family:var(--mono);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}

/* ---------- подтверждение опасного действия ---------- */
/* Кнопки разведены по краям, в отличие от обычных диалогов, где они стоят
   парой справа. Здесь это не украшение: «Скасувати» и «Так» ведут в разные
   стороны, и рядом друг с другом промах стоит сторнированного платежа или
   удалённой записи. Расстояние — самая дешёвая защита от неверного клика. */
#confirm-dialog .modal-footer{justify-content:space-between}

/* ---------- onboarding checklist ---------- */
/* Мастер настройки — КАРТОЧКА: ряд внутри неё раскладывает `.card-body`,
   как и просит разметка. Своего flex здесь быть не должно.

   Раньше он был — вместе с `align-items:center`, — и написан под плоскую
   разметку демо (`.ring`/`.st`/`.steps` ниже; в кабинете их нет ни в одном
   шаблоне). На карточке он спорил с бутстраповским `.card{flex-direction:
   column}`: направление становилось вертикальным, `align-items:center`
   переставал центрировать и начинал СЖИМАТЬ, и тело карточки получало
   ширину по содержимому — 579 px вместо 1242. Шаги от этого казались узким
   столбцом, хотя места под них было вдвое больше. */
.setup{background:linear-gradient(112deg,var(--c-soft),transparent 62%);border:1px solid var(--c-line);
  border-radius:var(--r)}
.setup .ring{position:relative;width:46px;height:46px;flex:none}
.setup .ring svg{transform:rotate(-90deg)}
.setup .ring span{position:absolute;inset:0;display:grid;place-items:center;font-family:var(--mono);
  font-size:11px;font-weight:600;color:var(--c-ink)}
.setup .st{flex:1;min-width:180px}
.setup .st b{display:block;font-size:14px;font-weight:600}
.setup .st p{color:var(--muted);font-size:12.5px;margin-top:2px}
.setup .steps{display:flex;gap:7px;flex-wrap:wrap}
.setup .step{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:var(--r);
  background:var(--surface);border:1px solid var(--line-2);font-size:12px;font-weight:500;color:var(--muted)}
.setup .step.done{color:var(--ok);border-color:var(--ok-line);background:var(--ok-soft)}
/* Мастер настройки (М9): шаги В РЯД, каждый ведёт на свой экран.
   Столбцом они занимали пять строк на всю ширину «Огляду» и читались как
   список дел без конца; рядом это дорога, у которой видно и начало, и край.

   auto-fit, а не жёсткие пять колонок: ряд переносится сам, когда места
   мало, и горизонтального скролла на узком экране не появляется (гейт
   front-checks проверяет 390 и 430 px).

   Внутри шага сетка из двух колонок и двух строк: значок и подпись в первой
   строке, кнопка во второй на всю ширину. Вторая строка тянется (1fr), и
   кнопка прижата к низу — иначе у шагов с разной длиной подсказки кнопки
   встали бы вразнобой, как это уже было на доске цеха. */
.setup-steps{list-style:none;margin:0;padding:0;display:grid;gap:8px;
  grid-template-columns:repeat(auto-fit,minmax(168px,1fr))}
.setup-step{display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-rows:auto 1fr;
  gap:8px;padding:9px 10px;border-radius:var(--r);
  background:var(--surface);border:1px solid var(--line-2)}
.setup-step .mark{flex:none;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;
  background:var(--surface-2);border:1px solid var(--line-2);color:var(--muted);
  font-family:var(--mono);font-size:11px;font-weight:600;align-self:start}
.setup-step .body{min-width:0;display:flex;flex-direction:column}
.setup-step .body b{font-size:13px;font-weight:600}
.setup-step .hint{color:var(--muted);font-size:12px;line-height:1.4}
.setup-step .btn{grid-column:1/-1;justify-self:start;align-self:end}
.setup-step.done{border-color:var(--ok-line);background:var(--ok-soft)}
.setup-step.done .mark{color:var(--ok);border-color:var(--ok-line);background:transparent}
.setup-step.done .body b{color:var(--ok)}

/* Приглашение к асистенту — над шагами и заметнее их: это не седьмой шаг,
   а короткая дорога через первые три. Кнопка не переносится и прижата
   вправо, подпись сжимается — иначе на телефоне строка рвётся посередине. */
.setup-assist{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:9px 10px;border-radius:var(--r);
  background:var(--surface);border:1px solid var(--c-line)}
.setup-assist .body{min-width:0;flex:1;display:flex;flex-direction:column}
.setup-assist .body b{font-size:13px;font-weight:600}
.setup-assist .hint{color:var(--muted);font-size:12px;line-height:1.4}

/* Клетка, названная руками: пересчёт её не трогает, и это видно глазом —
   иначе человек не отличит её от той, что поедет при следующем пересчёте. */
.ismanual{border-color:var(--c-line);background:var(--c-soft)}

/* ---------- собівартість ---------- */
/* Услуга читается сверху вниз: имя, настройка, состояние с кнопкой.
   Таблица здесь врала колонками — в одной ячейке жили три разных дела. */
.costsvc{padding:10px 0;border-top:1px solid var(--line-2)}
.costsvc:first-of-type{border-top:0;padding-top:0}
.costsvc-h b{font-size:14px;font-weight:600}
.costsvc-h .hint{margin-left:6px}
.costsvc-f{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:6px 0 2px}
.costsvc-f .form-select{max-width:260px}
.costsvc-f .form-control{max-width:140px}
.costsvc-fl{color:var(--muted);font-size:12.5px}
.costsvc-r{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:8px;margin-top:8px;color:var(--muted);font-size:12.5px}

/* ---------- друкарня ---------- */
/* Реквизиты слева, языки и префикс справа. Правая колонка уже: в ней
   пара полей, и растягивать её на половину экрана незачем */
.shopgrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,320px);gap:12px;align-items:start}
.shopcol{min-width:0}
@media(max-width:900px){.shopgrid{grid-template-columns:minmax(0,1fr)}}

/* ---------- catalog ---------- */
.svc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(212px,1fr));gap:12px}
/* Пустое состояние в сетке карточек занимает ВСЮ сетку, а не первую её
   ячейку. Без этого сообщение вставало колонкой в 212 px у левого края:
   текст ломался на три коротких строки, а справа оставалось пустое поле
   во весь экран — выглядело так, будто карточка не догрузилась.

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

   Абзацу дана своя ширина: общие 290 px рассчитаны на узкую карточку, а
   тут места целая полоса */
.svc-grid .empty,.pl-grid .empty,.set-grid .empty{grid-column:1 / -1;padding:64px 24px}
.svc-grid .empty p,.pl-grid .empty p,.set-grid .empty p{max-width:420px;font-size:13.5px}
.svc{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:2px;padding:15px;text-align:left;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  transition:border-color .14s,background .14s}
.svc:hover{border-color:var(--line-3);background:var(--surface-2)}
.svc .ic{width:38px;height:38px;border-radius:var(--r);display:grid;place-items:center;margin-bottom:11px;
  background:var(--surface-2);border:1px solid var(--line);color:var(--ink-2)}
.svc h3{font-size:14px}
.svc p{color:var(--muted);font-size:12px;line-height:1.4}
.svc .go{margin-top:11px;display:flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--c);
  opacity:.85;transition:opacity .14s,gap .14s}
.svc:hover .go{opacity:1;gap:8px}
.svc .rate{margin-top:9px;font-family:var(--mono);font-size:11px;color:var(--faint);
  padding-top:9px;border-top:1px dashed var(--line-2)}
.svc .rate b{color:var(--ink-2);font-weight:600}
.svc.tp .ic{background:var(--m-soft);border-color:var(--m-line);color:var(--m)}
.svc.tp .go{color:var(--m)}
.svc.tp:hover{border-color:var(--m-line)}
.svc .tpmark{position:absolute;top:13px;right:13px}
.mode-note{display:flex;align-items:flex-start;gap:9px;padding:11px 13px;border-radius:var(--r);
  background:var(--surface-2);border:1px solid var(--line);color:var(--muted);font-size:12.5px;margin-bottom:16px}
.mode-note .i{color:var(--faint);margin-top:2px}
.mode-note b{color:var(--ink)}
/* Тот же блок, но когда срок на исходе. Цвет — тот же янтарь, что у полосы
   на «Огляді»: одно событие обязано выглядеть одинаково на обоих экранах,
   иначе читается как два разных. */
.mode-note.warn{background:var(--y-soft);border-color:var(--y-line);color:var(--y)}
.mode-note.warn .i,.mode-note.warn b{color:var(--y)}
/* Ссылка на оплату держится правого края, а не хвоста фразы: на «Огляді»
   этот блок шириной во всю страницу, и ссылка вплотную к тексту повисала бы
   посреди пустоты. Цвет — свой же янтарь: синяя ссылка внутри янтарной
   плашки читается как чужая. */
.mode-note.warn a{margin-left:auto;flex:none;color:inherit;font-weight:600;white-space:nowrap}

/* Товарний чек (TASK-82): одна колонка и порядок строк расчётного документа.
   Шапка по центру — суб'єкт, господарська одиниця, адреса, податковий номер;
   товарная часть; итог; и только потом форма оплаты. Две колонки выглядели
   складно, но у настоящего чека такого нет, а этой бумаге ещё предстоит
   стать фискальной — переучивать человека дважды незачем. */
.rc{max-width:440px;margin:0 auto}
.rc-head{text-align:center;font-size:13px;line-height:1.5;padding-bottom:12px;border-bottom:1px dashed var(--line-3)}
.rc-head b{font-size:14px}
.rc-title{margin-top:10px;font-weight:600;letter-spacing:.04em}
/* Позиция лентой: название слева во всю ширину, сумма справа. Вторая
   строка «кількість × ціна» появляется только там, где количество больше
   единицы, — иначе одно число повторялось бы трижды. */
.rc-lines{padding:12px 0}
.rc-line{display:flex;align-items:flex-start;gap:12px;font-size:13px;line-height:1.45;padding:5px 0}
.rc-name{flex:1;min-width:0}
.rc-amt{flex:none;text-align:right;white-space:nowrap}
.rc-mul{display:block;font-size:12px;color:var(--muted)}
.rc-sum{padding-top:10px;margin-top:10px;border-top:1px dashed var(--line-3)}
.rc-foot{margin-top:12px;padding-top:10px;border-top:1px dashed var(--line-3);
  text-align:center;font-size:12px;color:var(--muted)}


/* «Безпека входу»: подпись слева, действия справа.

   Кнопки стояли просто следом за текстом, и всё, что их разделяло,
   держалось на переносах строк в шаблоне: зазор до текста — РОВНО ноль
   (у `.hint` нет нижнего поля), между самими кнопками — три пикселя
   пробела. Отступа не было ни одного, поэтому и «прилипли». */
.sec-row{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}
.sec-row .sec-t{flex:1 1 240px;min-width:0}
.sec-row .sec-t h2{margin-bottom:4px}
/* Без `margin-left:auto`: вправо действия и так уводит `flex:1 1 240px`
   у подписи, а при переносе они должны встать под текстом СЛЕВА — иначе
   отрываются от того, к чему относятся, через всю пустую строку. Перенос
   зависит от ширины КАРТОЧКИ (окно минус боковое меню, 246px), а не окна,
   поэтому медиазапросом это правильно не поймать: он сработал бы позже,
   чем колонки реально разъехались. */
.sec-row .sec-a{display:flex;flex-wrap:wrap;align-items:center;gap:8px}

/* ---------- calculator ---------- */
/* Экран расчёта: слева сверху вниз «параметры → підсумок», справа корзина
   во всю высоту.

   Раньше «Підсумок» и корзина стояли одной колонкой справа: позицию
   добавляли вверху, а заказ оформляли внизу — два действия одного дела в
   разных концах экрана. Теперь слева читается прямым порядком (настроил →
   увидел цену → добавил), а корзина липнет к экрану сбоку и «Оформити
   замовлення» видно с любой высоты страницы.

   Расстановка сделана СЕТКОЙ, а не обёрткой в разметке: лишний `div`
   вокруг калькулятора заставил бы переотступовать двести строк шаблона
   ради двух правил. */
.calc-wrap{display:grid;grid-template-columns:minmax(0,1fr) 306px;gap:16px;align-items:start}
.calc-wrap>.calc{grid-column:1;grid-row:1}
.calc-wrap>.sum{grid-column:1;grid-row:2}
.calc-wrap>.rail{grid-column:2;grid-row:1 / span 2}
@media(max-width:1140px){
  /* В одну колонку порядок держит сам поток: корзина уходит под подсумок */
  .calc-wrap{grid-template-columns:minmax(0,1fr)}
  .calc-wrap>.calc,.calc-wrap>.sum,.calc-wrap>.rail{grid-column:1;grid-row:auto}
}
.calc{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--shadow-sm);overflow:hidden}
.calc-h{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line);
  background:var(--surface-2)}
.calc-h .ic{width:36px;height:36px;border-radius:var(--r);background:var(--surface);border:1px solid var(--line);
  display:grid;place-items:center;color:var(--ink-2);flex:none}
.calc-h .tt{flex:1;min-width:0}
.calc-h h2{font-size:15px}
.calc-h .sb{color:var(--muted);font-size:12px;margin-top:1px}
.calc-body{display:grid;grid-template-columns:minmax(0,1fr) 268px}
@media(max-width:860px){.calc-body{grid-template-columns:minmax(0,1fr)}}
.calc-form{padding:16px;display:flex;flex-direction:column;gap:13px;border-right:1px solid var(--line)}
@media(max-width:860px){.calc-form{border-right:0;border-bottom:1px solid var(--line)}}
.cf-client{background:var(--c-soft);border:1px solid var(--c-line);border-radius:var(--r);padding:11px 12px}
.cf-client>label{color:var(--c-ink)}
.cf-row{display:flex;gap:7px}
.cf-row input{flex:1;min-width:0}
.cf-meta{margin-top:7px;display:flex;gap:6px;flex-wrap:wrap}
.tag{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 7px;border-radius:var(--r);
  font-family:var(--mono);font-size:10px;font-weight:600;background:var(--surface);
  border:1px solid var(--c-line);color:var(--c-ink)}
.opts{border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.opts .oh{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  color:var(--faint);padding:8px 12px;background:var(--surface-2);border-bottom:1px solid var(--line)}
.opt{display:flex;align-items:center;gap:10px;padding:9px 12px}
.opt+.opt,.opt-sub+.opt{border-top:1px solid var(--line)}
.opt .nm{flex:1;min-width:0;font-size:13px;font-weight:500}
.opt .nm small{display:block;font-weight:400;color:var(--faint);font-size:11px;margin-top:1px}
/* Сумма не переносится: «400,00» и «₴» на разных строках — не цена, а ребус */
.opt .amt{font-family:var(--mono);font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--muted);white-space:nowrap}
.opt.off{opacity:.45}
/* Допуслуга с несколькими позициями: название слева, список посередине,
   сумма справа — одной строкой. Чипы-радио, стоявшие тут раньше, ложились
   второй строкой и переносились по одному на строку, когда названия длинные
   («софт-тач оксамитова, тільки з лиця»): блок расползался на пол-экрана.

   Слева отступ на ширину галочки (16px + gap 10px, как в .opt-sub): галочки
   у такого ряда нет — её роль играет «— не потрібно —» в самом списке, — но
   без отступа названия списков и названия галочных услуг стояли бы в разных
   колонках, и глазу пришлось бы читать блок двумя ломаными столбцами. */
.opt.opt-multi{gap:10px;padding-left:38px;flex-wrap:wrap}
/* Название — колонкой в 160: иначе начало списков гуляло бы вслед за длиной
   названия, и вместо колонки получалась бы лесенка. Список забирает остаток
   строки целиком — «глянцева плівка 32 мкм з двох боків» помещается, а не
   обрезается на полуслове под самой стрелкой. На узком экране (карточка уже
   ~340) список переносится под название во всю ширину — это лучше, чем
   выжатое до неюзабельного поле в одну строку. */
.opt.opt-multi .nm{flex:0 1 160px}
.opt.opt-multi select{flex:1 1 200px;min-width:0}
/* Вариант допуслуги — чип: видно все сразу и во что обходится выбор */
.opt-sub{padding:0 12px 10px 38px;display:flex;gap:6px;flex-wrap:wrap}
.opt-sub select{width:auto;height:30px;font-size:12.5px;padding:0 24px 0 8px;background-position:calc(100% - 12px) 13px,calc(100% - 7px) 13px}
.opt-note{padding:0 12px 10px 38px;font-size:11.5px;color:var(--faint)}

/* format preview */
.prev{padding:16px;display:flex;flex-direction:column;gap:12px;background:var(--surface-2);min-width:0}
.prev .ph2{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.11em;
  text-transform:uppercase;color:var(--faint)}
.prev .stage{background:
   linear-gradient(var(--line) 1px,transparent 1px) 0 0/100% 13px,
   linear-gradient(90deg,var(--line) 1px,transparent 1px) 0 0/13px 100%,
   var(--surface);
  border:1px solid var(--line);border-radius:var(--r);height:160px;display:grid;place-items:center;padding:16px;
  position:relative;overflow:hidden}
/* Раскладка спуска: лист и изделия на нём. Рисуется в миллиметрах через
   viewBox, поэтому пропорции здесь всегда настоящие — а высота ограничена,
   чтобы длинный лист не выдавливал факты за экран. */
.prev .imp{display:block;width:100%;max-height:180px}
.prev .imp-sheet{fill:var(--surface);stroke:var(--ink-2);stroke-width:2}
.prev .imp-item{fill:var(--c-soft);stroke:var(--c);stroke-width:1}

.prev .sheet{background:var(--surface);border:1.5px solid var(--ink-2);border-radius:2px;position:relative;
  box-shadow:var(--shadow);display:grid;place-items:center;max-width:100%;max-height:100%}
.prev .sheet::after{content:"";position:absolute;inset:5px;border:1px dashed var(--line-3);border-radius:1px}
.prev .sheet b{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);position:relative;z-index:1}
.prev .dim-w,.prev .dim-h{position:absolute;font-family:var(--mono);font-size:10px;color:var(--faint)}
.prev .dim-w{bottom:4px;left:50%;transform:translateX(-50%)}
.prev .dim-h{left:5px;top:50%;transform:translateY(-50%) rotate(-90deg);transform-origin:center}
.prev .facts{display:flex;flex-direction:column;gap:7px}
.prev .fact{display:flex;justify-content:space-between;gap:10px;font-size:12px;color:var(--muted)}
.prev .fact b{font-family:var(--mono);font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
.prev .noshape{text-align:center;color:var(--faint);font-size:11.5px}
.prev .noshape .i{margin:0 auto 6px}
/* Большая цена без поля ввода — «своя ціна» приедет вместе с корзиной */
.pricebox .val{font-size:26px;font-weight:600;letter-spacing:-.035em}
/* Скрытая группа галочек в модалке услуги/машины (фильтр по режиму) */
.opts[hidden]{display:none}

/* summary */
/* Липкости у подсумка больше нет: он стоит в потоке под калькулятором, а
   к экрану липнет корзина — она и нужна на любой высоте */
.sum{display:flex;flex-direction:column;gap:12px;min-width:0}
/* Шапка «Розрахунок» набрана и отбита ровно как «Параметри» над ней: там
   `.calc-h` с кеглем 15 и полями 14/16, здесь стоял бутстраповский `.h6`
   в 16 при вдвое меньшем верхнем поле. Два соседних блока одного экрана
   не могут быть озаглавлены по-разному */
.sum .card-header{padding:14px 16px;background:var(--surface-2)}
.sum .card-header h3{font-size:15px;margin:0}
/* Две части: слева разбор, справа цена с кнопкой на своей подложке
   (эскиз владельца 03.09.2026). В один столбец, как было в узкой рейке,
   содержимое растягивалось редкой строкой на тысячу пикселей — числа
   слева, пустота справа.

   Разделитель прячется: он рисовал черту между разбором и ценой, а стоя
   рядом им делить нечего. */
.sum .card-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,420px);
  gap:24px 0;align-items:start}
/* Вертикальная черта делит разбор и цену. Стоит она на правом столбце, а
   не отдельным элементом: делить нечего, пока колонок нет, и на узком
   экране она исчезает вместе с раскладкой */
.sum .card-body>#quote{padding-right:32px}
.sum .card-body>.sdiv{display:none}
/* Правая часть — ОДИН столбец: цена и кнопка под ней. Порознь они были
   двумя рядами сетки, а первый ряд по высоте равен левой колонке разбора,
   и правая часть всегда выходила выше на кнопку с зазором */
.sum-act{grid-column:2;grid-row:1;display:flex;flex-direction:column;gap:12px;min-width:0;
  padding-left:32px;border-left:1px solid var(--line)}
/* Форма НЕ создаёт своей коробки: `display:contents` отдаёт её содержимое
   прямо в сетку подсумка. Иначе форма занимала бы отдельный ряд, поле
   макета съезжало бы под числа, и вверху справа оставалась бы дыра во всю
   третью колонку.

   Отправку это не ломает: форма остаётся в дереве, поля с ней связаны, а
   `calc-add-params` по-прежнему цель Turbo-потока. Ту же `contents` даём
   и коробке скрытых параметров — сами поля невидимы, но её собственный
   блок занял бы ячейку и сдвинул ряд. */
.sum #cart-add,.sum #calc-add-params{display:contents}
/* Кнопка во всю ширину столбца. Поле макета из расчёта убрано вовсе:
   макет прикрепляют на карточке заказа, где для этого есть свой блок, а
   расчёт занимается ценой. */
.sum .calc-actions{display:flex;flex-direction:column;gap:8px}
.sum #calc-submit,.sum #calc-checkout{width:100%}
/* Раскладка в один столбец решается по ширине САМОГО подсумка, а не окна:
   ширину ему задаёт не окно, а корзина справа, и на 1200 px он получает
   560 px при окне, до которого прежний запрос (900 px по окну) не
   доставал. Правый столбец при этом держал свои 420 px, а разбору
   оставалось 106 px — «Разом (за прайсом)» ломался на три строки. Окно
   про это знать не может; контейнер знает. */
.sum{container-type:inline-size}
/* 720 px — замерено: при 700 px разбору достаётся 246 px, и самая длинная
   строка «Разом (за прайсом) 8,60 ₴» ещё держится в одну; двадцатка сверху
   на русские подписи, которые длиннее украинских */
@container (max-width:720px){
  .sum .card-body{grid-template-columns:minmax(0,1fr)}
  .sum .card-body>.sdiv{display:block}
  /* В один столбец черта делить нечего — снимается вместе с раскладкой */
  .sum-act{grid-column:1;grid-row:auto;padding-left:0;border-left:0}
  .sum .card-body>#quote{padding-right:0}
}
/* демо: .sum .card-b — закрывается Bootstrap */
.sline{display:flex;justify-content:space-between;gap:10px;font-size:13px;color:var(--muted)}
.sline b{font-family:var(--mono);font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
.sline.disc b{color:var(--ok)}
.sdiv{height:1px;background:var(--line)}
/* Итог разбора: то, что прайс насчитал ДО ручной правки. Отбит пустой
   строкой сверху и набран крупнее остальных — на нём столбик кончается */
.stot{display:flex;justify-content:space-between;gap:10px;margin-top:10px;padding-top:10px;
  border-top:1px solid var(--line);font-size:14px;font-weight:600}
.stot b{font-family:var(--mono);font-weight:600;font-variant-numeric:tabular-nums}
/* Название и поле стоят В ОДНОМ РЯДУ, а не друг под другом: так правая
   колонка не тянет блок вниз (эскиз владельца 03.09.2026).

   Своей рамки и подложки у коробки нет: подпись стоит на чистом поле, а
   рамка остаётся только у самого поля ввода — она там и нужна, чтобы
   показать, куда пишут. Рамка вокруг всего блока была второй по счёту
   внутри карточки «Розрахунок» и превращала подпись в содержимое коробки,
   хотя подпись эту коробку называет. */
/* Подпись слева получает свои 150 px, а поле отдаёт лишнее: столбец
   подписи считался вычитанием при жёстких 168 px у поля, и на телефоне
   ему оставалось 114 px — «Можна змінити вручну» ломалось надвое.
   Раскладки в один столбец у коробки нет ни на одном пороге, так что
   спасать её должна сама сетка. */
.pricebox{display:grid;grid-template-columns:minmax(150px,1fr) auto;gap:4px 16px;align-items:center}
/* Заголовок коробки цены. Класс `ptt`, а не `pl`: `.pl` в этом файле уже
   занят карточкой прайса в справочнике — белая коробка с рамкой, — и
   подпись подхватывала её оформление, получая рамку внутри рамки */
.pricebox .ptt{grid-column:1;min-width:0}
.pricebox label{display:block;font-size:15px;font-weight:600;letter-spacing:-.01em;color:var(--ink)}
/* Подпись под названием: то, что цену можно переписать руками, — главное
   свойство поля, и узнавать о нём наведением курсора никто не станет.
   Кегль тот же, что у «Ставки за аркуш» под прев'ю (`.prev .fact`, 12 px
   приглушённым): на экране это подписи одного веса.

   Класс — `pnote`, а НЕ `ph`: `.ph` в этом же файле занят шапкой экрана
   (`display:flex`, `margin-bottom:18px`), и подпись молча забирала себе
   её нижний отступ — коробка цены становилась на 13 px выше, а кнопка
   «Додати в замовлення» на столько же ниже. Та же ловушка, из-за которой
   отсюда уже уехал `.pl` */
.pricebox .pnote{display:block;font-size:12px;line-height:1.35;color:var(--muted);margin-top:2px}
/* Само поле — с рамкой и на белом: продавец должен видеть, что сюда
   пишут, а не догадываться по подписи (эскиз владельца 03.09.2026) */
.pricebox .pin{grid-column:2;display:flex;align-items:center;gap:7px;margin:0;min-width:0}
.pricebox input{width:168px;max-width:100%;min-width:0;border:1px solid var(--line-2);border-radius:var(--r);
  background:var(--surface);padding:6px 10px;height:auto;font-size:21px;font-weight:600;
  letter-spacing:-.03em;font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right}
.pricebox input:focus{border-color:var(--c);box-shadow:0 0 0 3px var(--c-soft);outline:0}
.pricebox .cur{flex:none}
.pricebox .cur{font-size:14px;font-weight:500;color:var(--faint);font-family:var(--mono)}
/* Строку «Розраховано за прайсом: N» не показываем: то же число стоит
   слева, в «Разом (за прайсом)» (решение владельца 03.09.2026).

   Сам элемент ОСТАЁТСЯ в дереве, и это не перестраховка: из его
   `data-amount` контроллер заполняет поле, а подмена этого элемента
   потоком и есть сигнал «приехал новый расчёт» (`calculatedTargetConnected`).
   Убрать его из разметки значило бы остановить живой пересчёт цены. */

.pricebox .hint b{font-family:var(--mono);color:var(--muted)}
/* Пометка о ручной цене занимает МЕСТО подписи, а не строку под ценой:
   так коробка не подрастает, когда цену трогают руками. Обе — подпись и
   пометка — набраны одним кеглем, поэтому подмена незаметна по высоте */
.pricebox .manual{display:none;align-items:center;gap:5px;margin-top:2px;
  font-size:12px;font-weight:600;color:var(--y)}
.pricebox .manual.on{display:flex}
.pricebox:has(.manual.on) .pnote{display:none}
.pricebox .reset{margin-left:8px;font-size:11px;color:var(--c);font-weight:600;text-decoration:underline}
/* Носитель расчётной суммы: текста нет, места не занимает */
.pricebox .hint{grid-column:1 / -1;display:none}

/* ---------- order rail ---------- */
.rail{position:sticky;top:70px;display:flex;flex-direction:column;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-sm);overflow:hidden}
.rail-h{display:flex;align-items:center;gap:8px;padding:13px 15px;border-bottom:1px solid var(--line)}
.rail-h h3{flex:1}
.rail-h .cnt{font-family:var(--mono);font-size:10.5px;font-weight:600;padding:2px 7px;border-radius:20px;
  background:var(--surface-3);color:var(--muted)}
.rail-items{display:flex;flex-direction:column;max-height:46vh;overflow:auto}
.ritem{padding:11px 15px;border-bottom:1px solid var(--line)}
.ritem .t{display:flex;justify-content:space-between;gap:8px;align-items:baseline}
.ritem .t .nm{font-size:13px;font-weight:600;display:flex;align-items:center;gap:5px;min-width:0}
.ritem .t .nm .i{color:var(--y)}
.ritem .t .pr{font-family:var(--mono);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.ritem .mt{color:var(--muted);font-size:11.5px;margin-top:3px;line-height:1.4}
.ritem .ft{display:flex;align-items:center;gap:7px;margin-top:8px}
.ritem .rm{margin-left:auto;color:var(--faint);width:22px;height:22px;border-radius:var(--r);
  display:grid;place-items:center;transition:color .12s,background .12s}
.ritem .rm:hover{color:var(--bad);background:var(--bad-soft)}
.rail-f{padding:13px 15px;border-top:1px solid var(--line);background:var(--surface-2);
  display:flex;flex-direction:column;gap:9px}
/* Итоги: правая колонка корзины и карточка прихода. Класс `.sums` дан второму
   месту, а не расширен селектор до глобального: `.tot` живёт ещё и в
   документах со своими стилями, и снятие области утащило бы туда вёрстку
   рейла. Без этого подписи прирастали к суммам — «Сума без ПДВ1 640,00 ₴». */
.rail-f .rl,.sums .rl{display:flex;justify-content:space-between;font-size:12px;color:var(--muted)}
.rail-f .rl b,.sums .rl b{font-family:var(--mono);font-weight:600;color:var(--ink)}
.rail-f .tot,.sums .tot{display:flex;justify-content:space-between;align-items:baseline;font-weight:600}
.rail-f .tot .lb,.sums .tot .lb{font-size:13px}
.rail-f .tot .vv,.sums .tot .vv{font-family:var(--mono);font-size:19px;letter-spacing:-.03em;font-variant-numeric:tabular-nums}

/* ---------- order detail ---------- */
.od{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:16px;align-items:start}
@media(max-width:1000px){.od{grid-template-columns:minmax(0,1fr)}}
.od-l,.od-r{display:flex;flex-direction:column;gap:14px}
.meta-row{display:flex;gap:20px;flex-wrap:wrap;color:var(--muted);font-size:12.5px;margin:-6px 0 16px 2px}
.meta-row b{color:var(--ink);font-weight:600}
.prow{display:flex;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.prow:last-child{border-bottom:0}
.prow .pi{flex:1;min-width:150px}
.prow .pn{font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:6px}
.prow .pm{color:var(--muted);font-size:11.5px;margin-top:2px}
.prow .pd{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:11.5px}
.prow .pd input{height:29px;width:132px;font-size:12px;padding:0 7px}
.prow .pp{font-family:var(--mono);font-size:13.5px;font-weight:600;font-variant-numeric:tabular-nums;
  min-width:86px;text-align:right}
.paybar{height:5px;border-radius:var(--r);background:var(--surface-3);overflow:hidden;margin:2px 0 4px}
.paybar i{display:block;height:100%;border-radius:var(--r);background:var(--ok);transition:width .3s}
.paybar.part i{background:var(--y)}
.paybar.over i{background:var(--bad)}
.bigline{display:flex;justify-content:space-between;align-items:baseline;padding-top:9px;margin-top:3px;
  border-top:1px solid var(--line);font-weight:600}
.bigline .vv{font-family:var(--mono);font-size:18px;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.paybox{display:flex;gap:7px;margin-top:11px}
.paybox input{flex:1;min-width:0}
.tl{list-style:none;margin:0;padding:0}
.tl li{position:relative;padding:0 0 15px 21px;border-left:1.5px solid var(--line);margin-left:5px}
.tl li:last-child{border-left-color:transparent;padding-bottom:0}
.tl .dt{position:absolute;left:-5.5px;top:3px;width:9px;height:9px;border-radius:50%;background:var(--line-3);
  border:2px solid var(--surface)}
.tl li.hi .dt{background:var(--c)}
.tl .tt{font-size:13px;font-weight:600}
.tl .mt{color:var(--muted);font-size:11.5px;margin-top:1px;font-family:var(--mono)}

/* ---------- charts ---------- */
.chart{width:100%;height:190px;display:block;overflow:visible}
.chart .grid-l{stroke:var(--line);stroke-width:1}
.chart .axl{font-family:var(--mono);font-size:9.5px;fill:var(--faint)}
.chart .ln{fill:none;stroke:var(--c);stroke-width:2;stroke-linejoin:round;stroke-linecap:round}
.chart .ar{fill:url(#gr-c)}
.chart .pt{fill:var(--surface);stroke:var(--c);stroke-width:2}
.chart .lbl-v{font-family:var(--mono);font-size:10px;font-weight:600;fill:var(--ink)}
.bars{display:flex;flex-direction:column;gap:11px}
.brow{display:grid;grid-template-columns:minmax(80px,124px) 1fr auto;gap:11px;align-items:center;font-size:12.5px}
.brow .bn{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.brow .trk{display:block;height:8px;border-radius:var(--r);background:var(--surface-3);overflow:hidden}
.brow .fil{display:block;height:100%;border-radius:var(--r);background:var(--c);transition:width .4s}
.brow .fil.m{background:var(--m)}
.brow .bv{font-family:var(--mono);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--muted)}
.split{display:flex;gap:10px}
.split .sp{flex:1;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);padding:11px 13px}
.split .sp .pk{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.split .sp .pv{font-family:var(--mono);font-size:19px;font-weight:600;letter-spacing:-.03em;margin-top:3px;
  font-variant-numeric:tabular-nums}
.volrow{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 0;
  border-bottom:1px solid var(--line);font-size:13px;color:var(--muted)}
.volrow:last-child{border-bottom:0}
.volrow .vl{display:flex;align-items:center;gap:9px;color:var(--ink)}
.volrow .vl .i{color:var(--faint)}
.volrow b{font-family:var(--mono);font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}

/* ---------- prices ---------- */
.pl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(238px,1fr));gap:12px}
.pl{display:flex;flex-direction:column;align-items:stretch;padding:15px;text-align:left;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r);
  transition:border-color .14s,background .14s}
.pl:hover{border-color:var(--line-3);background:var(--surface-2)}
.pl .pt2{display:flex;align-items:center;gap:9px;margin-bottom:10px}
.pl .pic{width:32px;height:32px;border-radius:var(--r);background:var(--surface-2);border:1px solid var(--line);
  display:grid;place-items:center;color:var(--ink-2);flex:none}
.pl .pnm{font-size:13.5px;font-weight:600;letter-spacing:-.01em}
.pl .psb{color:var(--muted);font-size:11.5px;margin-top:1px}
.pl .prow2{display:flex;justify-content:space-between;font-size:11.5px;color:var(--muted);
  padding-top:10px;border-top:1px dashed var(--line-2)}
.pl .prow2 b{font-family:var(--mono);color:var(--ink)}
.mtx{font-size:12.5px;min-width:520px}
.mtx th{font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  color:var(--faint);text-align:left;padding:8px 12px;background:var(--surface-2);border-bottom:1px solid var(--line)}
.mtx td{padding:6px 11px;border-bottom:1px solid var(--line)}
.mtx tr:last-child td{border-bottom:0}

/* Матрица на телефоне: строка становится карточкой, а подпись колонки —
   ярлыком у своей клетки. Разметка под это уже готова: data-th
   (атрибуты стоят на клетках значений, опознавательные их не имеют) */
@media(max-width:560px){
  .mtx{min-width:0;font-size:13px}
  .mtx thead{display:none}
  .mtx tbody,.mtx tr,.mtx td{display:block;width:auto}
  .mtx tr{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
    margin-bottom:10px;padding:8px 11px}
  .mtx tr:last-child{margin-bottom:0}
  .mtx td{padding:4px 0;border-bottom:0;text-align:left}
  .mtx td:not([data-th]){font-weight:600}
  .mtx td:not([data-th]):empty{display:none}
  .mtx td[data-th]{display:flex;align-items:center;justify-content:space-between;gap:12px}
  .mtx td[data-th]::before{content:attr(data-th);font-family:var(--mono);font-size:10px;
    font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
  .mtx .rate{width:120px}
}
.mtx .rate{width:96px;height:30px;text-align:right;font-family:var(--mono);font-size:12.5px;
  font-variant-numeric:tabular-nums;padding:0 8px}
.mtx .r{text-align:right}
.mtx tbody tr:hover{background:var(--surface-2)}

/* Поле даты шириной по дате. `narrow-sm` тут не годится: 26rem с
   авто-полями растягивают поле на всю колонку и выталкивают кнопку
   «Зберегти» на следующую строку. */
.field-date{width:9.5rem;flex:none}

/* Подпись под галочкой встаёт по тексту подписи, а не по краю столбца:
   она объясняет именно эту галочку, и висящая левее строка читается как
   замечание ко всему блоку. Отступ тот же, что бутстрап даёт `.form-check`
   под сам квадратик, — 1.5em, поэтому строки совпадают при любом кегле. */
.hint-check{margin-left:1.5em}

/* Поле только для чтения выглядит нечитаемым СРАЗУ, а не после попытки
   в него написать: приглушённая подложка и текст. Так «Внесено» при
   «Повністю» видно как ответ системы, а не как поле, куда забыли пустить.
   Бутстрап так подсвечивает только `:disabled`, а отключить поле нельзя —
   отключённое не уезжает в POST, и сумма пропала бы из формы. */
.form-control[readonly]{background:var(--surface-3);color:var(--muted)}
.form-control[readonly]:focus{background:var(--surface-3);color:var(--muted)}

/* Список выбора в диалоге поиска: строки-кнопки во всю ширину, текущий
   выделен. Прокручивается сам, чтобы диалог не рос вслед за списком */
.picklist{display:flex;flex-direction:column;max-height:min(52vh,360px);overflow-y:auto;
  border:1px solid var(--line);border-radius:var(--r)}
.picklist .list-row{appearance:none;background:none;border:0;border-bottom:1px solid var(--line);
  padding:9px 12px;text-align:left;font:inherit;color:var(--ink);cursor:pointer}
.picklist .list-row:last-child{border-bottom:0}
.picklist .list-row:hover,.picklist .list-row:focus-visible{background:var(--surface-3)}
.picklist .list-row.is-current{font-weight:600}

/* Имя заказа под номером: крупнее подписи клиента, но легче заголовка —
   это название работы, а не второй заголовок страницы */
.ordname{margin:2px 0 0;font-size:15px;font-weight:600;color:var(--ink)}

.staffbar{display:flex;align-items:center;gap:9px;padding:8px 22px;background:var(--y-soft);
  border-bottom:1px solid var(--y-line);color:var(--y);font-size:12.5px;font-weight:600}
.staffbar .i{flex:none}
/* Кнопка-действие вправо и без сжатия — одинаково во всех полосах: возврат
   из подмены, «Зрозуміло» у объявления и у плашки роли */
.staffbar form{margin-left:auto;flex:none}
.staffbar .ann-text{flex:1;min-width:0}

/* Полоса входа платформы в кабинет клиента (М11): тот же каркас, что у
   плашки роли, но тревожного цвета — она сообщает не «кто вы», а «здесь
   сейчас чужой». Кнопка возврата живёт прямо в полосе. */
.staffbar.warn{background:var(--bad-soft);border-bottom-color:var(--bad-line);color:var(--bad)}
/* У полосы режима чтения два действия и ни одной формы: вправо толкает
   первая ссылка, вторая идёт за ней */
.staffbar.readonly .linkbtn:first-of-type{margin-left:auto}
.staffbar.readonly .linkbtn+.linkbtn{margin-left:14px}

/* Макет позиции. Поле и кнопка — одной строкой: без явного flex поле
   занимало всю ширину карточки, и «Зберегти» уезжала на следующую строку.
   Локальный путь моноширинным и с переносом по любому знаку: UNC-путь
   бывает длиннее колонки, а обрезанный путь бесполезен — по нему файл
   не найти. */
.artwork{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
.artwork-form{display:flex;gap:6px;align-items:center;width:100%}
.artwork-field{flex:1 1 auto;min-width:0;max-width:22rem}
.artwork-value{display:inline-flex;align-items:center;gap:6px;font-size:13px;min-width:0}
/* Полоса объявления платформы. Родов три: новость — синим, работы —
   жёлтым по умолчанию `.staffbar`, важное — красным `.warn`. Текст
   переносится, кнопка «Зрозуміло» держится справа и не уезжает. */
.staffbar.info{background:var(--c-soft);border-bottom-color:var(--c-line);color:var(--c-ink)}
.staffbar.info .linkbtn{color:var(--c-ink)}
/* Длинный текст переносится, а иконка и кнопка остаются на первой строке —
   и у объявления, и у плашки роли: подписи ролей в ru длиннее строки */
.staffbar.announcement,.staffbar.role{align-items:flex-start}
.staffbar.announcement .ann-text b{margin-right:6px}
.staffbar.announcement .linkbtn,.staffbar.role .linkbtn{background:none;border:0;padding:0;
  font:inherit;font-weight:600;text-decoration:underline;cursor:pointer;white-space:nowrap}

/* Счётчик ожидающих на пункте меню платформы. Красный, потому что это
   не украшение, а «кто-то ждёт ответа»; ноль сюда не попадает вовсе */
.navbadge{margin-left:auto;min-width:19px;height:19px;padding:0 5px;border-radius:10px;
  background:var(--bad);color:#FFFFFF;font-size:11.5px;font-weight:700;line-height:19px;
  text-align:center;flex:none}

.artwork-value code{font-size:12.5px;color:var(--ink);background:var(--surface-3);
  padding:1px 6px;border-radius:var(--r);overflow-wrap:anywhere}
.staffbar.warn .linkbtn{background:none;border:0;padding:0;color:var(--bad);font:inherit;
  font-weight:600;text-decoration:underline;cursor:pointer}

/* ---------- справочники ---------- */
.dlist{max-height:296px;overflow-y:auto;margin:0 -2px;padding:0 2px;scrollbar-width:thin}
.dlist::-webkit-scrollbar{width:8px}
.dlist::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:var(--r);border:2px solid var(--surface)}
.chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:11px}
.chip2{display:inline-flex;align-items:center;gap:6px;height:27px;padding:0 10px;border-radius:var(--r);
  border:1px solid var(--line-2);background:var(--surface);font-size:12.5px;font-weight:500;color:var(--muted);
  transition:border-color .12s,background .12s,color .12s}
.chip2 b{font-family:var(--mono);font-size:10.5px;font-weight:600;color:var(--faint)}
.chip2:hover{border-color:var(--line-3);color:var(--ink)}
.chip2.on{background:var(--c-soft);border-color:var(--c-line);color:var(--c-ink)}
.chip2.on b{color:var(--c)}
/* демо: .card.span2{grid-column:1/-1} — корень .card выброшен с общим слоем;
   ВНИМАНИЕ: растяжку на всю ширину сетки Bootstrap НЕ закрывает */
.srow .st2 .cat{display:inline;color:var(--faint)}
.dlist.tall{max-height:392px}

/* ---------- settings ---------- */
.set-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;align-items:start}
.srow{display:flex;align-items:center;gap:8px;padding:9px 0;border-bottom:1px solid var(--line)}
.srow form{margin:0;display:flex}
.srow:last-child{border-bottom:0}
.srow .st2{flex:1;min-width:0}
.srow .st2 b{display:block;font-size:13px;font-weight:600}
.srow .st2 span{display:block}
.srow .st2 span{display:block;font-size:11.5px;color:var(--muted);margin-top:1px}
.brandprev{display:flex;align-items:center;gap:11px;padding:13px;border-radius:var(--r);
  background:var(--sidebar);border:1px solid var(--sidebar-line)}
.brandprev span{min-width:0}
.brandprev .t{display:block;color:#EDF2F8;font-size:13.5px;font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.brandprev .s{display:block;font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--sidebar-txt-2);margin-top:2px}
.plan-card{border:1px solid var(--c-line);background:linear-gradient(140deg,var(--c-soft),transparent 65%)}
.plan-card .pv{font-family:var(--mono);font-size:26px;font-weight:600;letter-spacing:-.03em}
.plan-card .pv small{font-size:13px;color:var(--muted);font-weight:500}
.feats{display:flex;flex-direction:column;gap:7px;margin-top:4px}
.feats div{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--muted)}
.feats .i{color:var(--ok)}

/* ---------- печатные формы ---------- */
.doc{background:#fff;color:#141414;border:1px solid var(--line);border-radius:var(--r);
  max-width:840px;margin:0 auto;box-shadow:var(--shadow);font-size:12px;line-height:1.45;overflow:hidden}
.doc *{color:inherit}
.doc .lh{height:4px;display:flex}
.doc .lh i{flex:1}
.doc .lh i:nth-child(1){background:#00A9E0}.doc .lh i:nth-child(2){background:#E5007E}
.doc .lh i:nth-child(3){background:#FFD100}.doc .lh i:nth-child(4){background:#2A2622}
.doc .in{padding:26px 34px 30px}
@media(max-width:640px){.doc .in{padding:18px 15px 22px}}
.doc .req{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:26px;padding-bottom:14px}
@media(max-width:640px){.doc .req{grid-template-columns:minmax(0,1fr);gap:16px}}
.doc .req .lb{font-family:var(--mono);font-size:9px;letter-spacing:.11em;text-transform:uppercase;
  color:#8b8b86;margin-bottom:5px}
.doc .req b{font-size:13.5px;display:block;margin-bottom:3px;letter-spacing:-.01em}
.doc .kv{display:flex;gap:7px;font-size:11.5px;color:#3f3f3c;margin-top:1px}
.doc .kv i{color:#8b8b86;font-style:normal;flex:none;min-width:52px}
.doc .kv u{text-decoration:none;font-family:var(--mono);font-variant-numeric:tabular-nums}
.doc .ttl{text-align:center;padding:15px 0 2px;border-top:2px solid #141414;margin-top:4px}
.doc .ttl h2{font-size:17px;letter-spacing:-.015em}
.doc .ttl .sm{font-size:11.5px;color:#54544f;margin-top:4px}
.doc table.inv{width:100%;border-collapse:collapse;margin-top:15px;font-size:11.5px}
.doc table.inv th{background:#f4f3f0;border:1px solid #b6b6b0;padding:6px 8px;font-size:9px;
  font-family:var(--mono);letter-spacing:.06em;text-transform:uppercase;color:#54544f;text-align:left;font-weight:600}
.doc table.inv td{border:1px solid #d8d8d2;padding:7px 8px;vertical-align:top}
.doc table.inv td.r,.doc table.inv th.r{text-align:right;font-family:var(--mono);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.doc table.inv td.c,.doc table.inv th.c{text-align:center;font-family:var(--mono)}
.doc table.inv .sub{color:#6b6b66;font-size:10.5px;margin-top:2px;line-height:1.35}
.doc .tot{display:flex;justify-content:flex-end;margin-top:12px}
.doc .tot table{width:auto;border-collapse:collapse;font-size:12px;min-width:280px}
.doc .tot td{padding:4px 0}
.doc .tot td.r{text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums;padding-left:24px}
.doc .tot tr.g td{border-top:1.5px solid #141414;padding-top:8px;font-weight:600;font-size:14.5px}
.doc .sumline{margin-top:15px;font-size:11.5px}
.doc .sumline b{font-weight:600}
.doc .words{margin-top:3px;font-size:11.5px}
.doc .note{margin-top:13px;padding-top:10px;border-top:1px dashed #cfcfc9;font-size:10.5px;color:#6b6b66;line-height:1.5}
.doc .sig{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:44px;margin-top:30px;font-size:11px;color:#54544f}
@media(max-width:640px){.doc .sig{gap:24px}}
.doc .sig .ln{border-bottom:1px solid #141414;height:28px;margin-bottom:4px}
.doc .mp{margin-top:18px;font-size:10.5px;color:#8b8b86;text-align:right}
.doc .lines{margin-top:6px}
.doc .lines .l{border-bottom:1px dotted #b6b6b0;height:20px}
.doc .dfoot{margin-top:22px;padding-top:10px;border-top:1px solid #e6e6e0;font-size:10px;color:#9a9a94;
  display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap}
.docbar{display:flex;gap:8px;align-items:center;margin-bottom:16px;flex-wrap:wrap}
.docbar .chk{display:flex;align-items:center;gap:7px;height:34px;padding:0 11px;font-size:12.5px;
  border:1px solid var(--line-2);border-radius:var(--r);background:var(--surface);cursor:pointer}

/* ---------- mobile ---------- */
.mob-scrim{display:none}
@media(max-width:940px){
  .shell{grid-template-columns:minmax(0,1fr)}
  .sidebar{position:fixed;left:0;top:0;bottom:0;width:270px;transform:translateX(-100%);
    transition:transform .22s cubic-bezier(.2,.8,.3,1);box-shadow:var(--shadow-lg)}
  body.nav-open .sidebar{transform:none}
  body.nav-open .mob-scrim{display:block;position:fixed;inset:0;z-index:50;background:rgba(8,12,18,.5)}
  .burger{display:grid}
  .topbar{padding:0 14px;gap:9px}
  .tb-search{flex:none;min-width:0;width:32px;padding:0;justify-content:center}
  .tb-search span,.tb-search .kbd{display:none}
  .clientsel{max-width:150px}
  .clientsel select{max-width:104px}
  .clientsel .lb{display:none}
  .page{padding:16px 14px 40px}
  .ph h1{font-size:19px}
  .sum,.rail{position:static}
}
@media(max-width:560px){
  .crumbs .c0,.crumbs .sep{display:none}
  .kpi .kv{font-size:23px}
  .meta-row{gap:14px}
}

/* Документ всегда светлый — печатная форма не зависит от темы кабинета.
   В демо это достигалось жёсткими локальными цветами (#fff, #141414 и т.д.)
   внутри .doc — они перенесены выше как есть; тёмных переопределений для .doc
   в демо (p2_css.html) нет. color-scheme:light дополнительно приводит
   нативные контролы и скроллбары внутри документа к светлой схеме. */
.doc { color-scheme: light; }

/* ---------- заказы: точки статусов и просрочка (М3, PR-31) ----------
   В демо цвет точки задавался инлайновым style — здесь классом:
   инлайновые стили в шаблонах запрещены (GUIDELINES §6). У «Відхилено»
   в демо цвета не было вовсе (ловушка №1 инвентаризации) — задан явно. */
.dots i.s-draft{background:var(--line-3)}
.dots i.s-accept{background:var(--c)}
.dots i.s-print{background:var(--y)}
.dots i.s-ready{background:var(--ok)}
.dots i.s-done{background:var(--line-3)}
.dots i.s-reject{background:var(--bad)}
.overdue{color:var(--bad);font-weight:600}
.od-r .btn-danger-soft{color:var(--bad);border-color:var(--bad-line);background:none}
.od-r .btn-danger-soft:hover{background:var(--bad-soft);color:var(--bad)}

/* Полоса оплаты: ширина шагом 5 % классом — инлайновый style в шаблонах
   запрещён (GUIDELINES §6), а 101 класс на каждый процент избыточен. */
.paybar i.pb-0{width:0%}
.paybar i.pb-5{width:5%}
.paybar i.pb-10{width:10%}
.paybar i.pb-15{width:15%}
.paybar i.pb-20{width:20%}
.paybar i.pb-25{width:25%}
.paybar i.pb-30{width:30%}
.paybar i.pb-35{width:35%}
.paybar i.pb-40{width:40%}
.paybar i.pb-45{width:45%}
.paybar i.pb-50{width:50%}
.paybar i.pb-55{width:55%}
.paybar i.pb-60{width:60%}
.paybar i.pb-65{width:65%}
.paybar i.pb-70{width:70%}
.paybar i.pb-75{width:75%}
.paybar i.pb-80{width:80%}
.paybar i.pb-85{width:85%}
.paybar i.pb-90{width:90%}
.paybar i.pb-95{width:95%}
.paybar i.pb-100{width:100%}

/* --- М4/М6 (PR-32): деньги, отчёты, документы ---------------------- */

/* Ширина полосы шагом 5 % классом — инлайновый style в шаблонах запрещён
   (GUIDELINES §6). Общие классы для .brow .fil и .paybar i: два набора
   на одно и то же расходились бы. */
.pct-0{width:0%}
.pct-5{width:5%}
.pct-10{width:10%}
.pct-15{width:15%}
.pct-20{width:20%}
.pct-25{width:25%}
.pct-30{width:30%}
.pct-35{width:35%}
.pct-40{width:40%}
.pct-45{width:45%}
.pct-50{width:50%}
.pct-55{width:55%}
.pct-60{width:60%}
.pct-65{width:65%}
.pct-70{width:70%}
.pct-75{width:75%}
.pct-80{width:80%}
.pct-85{width:85%}
.pct-90{width:90%}
.pct-95{width:95%}
.pct-100{width:100%}

/* Подписи оси X графика: равные доли, как в демо (xLabels) */
.xlabels{display:flex;margin-top:6px}
.xlabels span{flex:1;text-align:center;font-family:var(--mono);font-size:9.5px;color:var(--faint)}

/* Сегментированный переключатель документов: в кабинете это ссылки, а не
   кнопки формы — переход по адресу, а не отправка */
.seg a{display:inline-flex;align-items:center;height:26px;padding:0 11px;border-radius:var(--r);
  font-size:12.5px;font-weight:600;color:var(--muted);text-decoration:none;border:0;background:none}
.seg a:hover{color:var(--ink)}
.seg a.on{background:var(--surface);color:var(--ink);box-shadow:var(--shadow-sm)}

/* Сторнированная строка журнала: перечёркнута, но видна — история денег
   не удаляется, она дополняется обратной записью */
tr.strike td{text-decoration:line-through;color:var(--faint)}
tr.strike td .tag{text-decoration:none}

/* Ширины колонок печатных форм (в демо стояли инлайновым style) */
.doc table.inv th.col-n{width:30px}
.doc table.inv th.col-qty{width:66px}
.doc table.inv th.col-unit{width:58px}
.doc table.inv th.col-price{width:84px}
.doc table.inv th.col-sum{width:96px}
.doc table.inv th.col-status{width:92px}
.doc table.inv th.col-ready{width:72px}
.doc .doc-logo{max-height:46px;max-width:220px;margin-bottom:12px;display:block}
.doc .sig.sig3{grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}
.doc .note.note-flat{border-top:0;padding-top:14px}

/* ---------- цех (М7) ----------
   Доска — карточками, а не таблицей: у машины стоят и смотрят с полутора
   метров, там читают крупный размер и тираж, а не строку из десяти колонок.
   Просроченная работа отбивается рамкой, а не только красной датой. */
.jobs{display:grid;grid-template-columns:repeat(auto-fill,minmax(288px,1fr));gap:12px}
.job .card-body{display:flex;flex-direction:column;gap:8px}
.job.late{border-color:var(--bad-line)}
.job .jh{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.job .jn{font-size:14px;font-weight:600;letter-spacing:-.01em;flex:1;min-width:0}
.job .jsz{font-size:20px;font-weight:600;letter-spacing:-.02em;line-height:1.1}
.job .jm{color:var(--muted);font-size:12px}
/* Реестр документов: девять колонок, и номера с датами рвались на две
   строки. Служебные значения не переносятся — их читают целиком; имя
   клиента переносить можно, оно и длинное. */
.tbl.docs td.num,.tbl.docs th{white-space:nowrap}
.tbl.docs td.nowrap{white-space:nowrap}

/* Аннулированный документ: пометка на листе и приглушённая строка в
   реестре. Удаления в модуле нет — номер сгорел и мог уйти на бумаге. */
.void-stamp{display:inline-block;margin-bottom:10px;padding:3px 10px;border-radius:var(--r);
  background:var(--bad-soft);border:1px solid var(--bad-line);color:var(--bad);
  font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}
tr.voided td{opacity:.55}
/* Зачёркнут ТОЛЬКО номер: ссылка на заказ и кнопка просмотра работают —
   аннулированный документ открывается и печатается как прежде */
tr.voided td:first-child a{text-decoration:line-through}

/* Допуслуги — плашками. Каждая своей границей: в подписи бывает свой
   разделитель («Ламінація · Матова плівка»), и в общей строке через « · »
   работа читалась одним нечитаемым хвостом. */
.job .jx{display:flex;flex-wrap:wrap;gap:4px}
.job .jxi{font-size:11.5px;line-height:1.6;padding:1px 8px;border-radius:var(--r);
  color:var(--ink-2);background:var(--surface-3);border:1px solid var(--line)}
.job .jf{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px}
.job .jf .jl{color:var(--faint);margin-right:4px}
.job .js{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12px;
  padding:6px 8px;border-radius:var(--r);background:var(--surface-2);border:1px solid var(--line)}
.job .js.short{color:var(--y);background:var(--y-soft);border-color:var(--y-line);font-weight:600}
.job .jc{display:flex;align-items:flex-start;gap:6px;color:var(--ink-2);font-size:12px}
.job .jc .i{margin-top:2px;color:var(--faint)}
.job .ja{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:28px}
.job .hint{color:var(--faint);font-size:11.5px}
.job .jic{display:grid;place-items:center;flex:none;color:var(--faint)}
/* Кнопка прижата к низу: работы в ряду разной наполненности — у одной нет
   размера, у другой комментария или макета, — и действие плавало по высоте,
   хотя сами карточки сетка растягивает до общей */
.job .card-body>form{margin-top:auto}

/* ---------- публичные страницы (М12) ----------
   Витрина живёт в языке кабинета: те же токены, то же скругление, тот же
   шрифт. Человек, пришедший из поиска и заведший потом друкарню, не должен
   решить, что попал в другую программу. Отличается только ритм: здесь
   читают текст, а не работают, поэтому колонка уже и воздуха больше. */
/* Витрина стоит на БЕЛОМ, а выделенное — на светло-сером: обратное
   кабинетному, и намеренно. В кабинете серый фон нужен, чтобы белые
   карточки с данными от него отделялись; здесь главное — текст, и
   серая подложка делала его тусклым. */
.site{min-height:100vh;display:flex;flex-direction:column;background:var(--surface)}
.site-top{display:flex;align-items:center;gap:22px;padding:0 24px;min-height:60px;border-bottom:1px solid var(--line);
  background:var(--surface);position:sticky;top:0;z-index:20;flex-wrap:wrap}
.site-grid .card,.site-tiers .card{background:var(--surface-2)}
.site-brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--ink);font-weight:600}
/* Знак крупнее кабинетного: там он метка в углу меню, здесь — марка на
   первом экране. Токен `--brand-size` общий, поэтому размер задан тут */
.site-top .reg{width:30px;height:30px}
.site-act .btn{height:38px;padding:0 16px;font-size:13.5px;display:inline-flex;align-items:center}
.site-brand .nm{font-family:var(--mono);font-size:13.5px;letter-spacing:.08em;text-transform:uppercase}
.site-nav{display:flex;gap:24px;flex:1;min-width:0;flex-wrap:wrap}
.site-nav a{color:var(--muted);text-decoration:none;font-size:14.5px;padding:4px 0;border-bottom:2px solid transparent}
.site-nav a:hover{color:var(--ink)}
.site-nav a.on{color:var(--ink);border-bottom-color:var(--k)}
.site-act{display:flex;align-items:center;gap:8px}
.site-lang{display:flex;gap:2px;margin-right:6px}
.site-lang a{font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.06em;color:var(--faint);
  text-decoration:none;padding:4px 7px;border-radius:var(--r)}
.site-lang a.on{color:var(--ink);background:var(--surface-2)}
/* Ширина витрины равна ширине кабинета (`.page`, 1320): раньше публичные
   страницы были у́же собственного продукта, и на 1920 по краям оставалось
   по 360 px пустоты. Одно число на весь продукт, а не два похожих.

   `overflow-wrap:anywhere` — из-за оферты: почта поддержки одним словом
   длиннее колонки телефона и уносила страницу вбок. Слово важнее строки:
   адрес, который нельзя прочитать целиком, бесполезен, а перенос по любой
   букве его сохраняет */
.site-main{flex:1;width:100%;max-width:1320px;margin:0 auto;padding:0 28px 52px;overflow-wrap:anywhere}
/* Подвал колонками. Тон тот же, что у первого экрана: тонированы края
   страницы, содержимое между ними белое */
.site-foot{border-top:1px solid var(--line);background:var(--surface-2);
  padding:34px 24px 20px;font-size:12.5px;color:var(--faint)}
/* Имя продукта шире остальных колонок: под ним строка о том, что это
   вообще такое, — человеку, пришедшему по ссылке на оферту, страница
   больше нигде этого не говорит */
.foot-in{max-width:1200px;margin:0 auto;display:grid;
  grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(0,1fr));gap:28px 32px}
.foot-brand .site-brand{display:inline-flex;align-items:center;gap:9px;color:var(--ink);text-decoration:none}
.foot-brand .reg{width:24px;height:24px}
.foot-brand p{margin:12px 0 0;max-width:280px;line-height:1.5}
.foot-col{display:flex;flex-direction:column;gap:8px;align-items:flex-start}
.foot-col h2{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.12em;
  text-transform:uppercase;color:var(--faint);margin:0 0 2px}
.foot-col a{color:var(--muted);text-decoration:none;line-height:1.4;font-size:13.5px}
.foot-col a:hover{color:var(--ink);text-decoration:underline}
.foot-bot{max-width:1200px;margin:28px auto 0;padding-top:16px;border-top:1px solid var(--line)}
@media(max-width:820px){
  .foot-in{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}
  .foot-brand{grid-column:1 / -1}
}
@media(max-width:430px){.foot-in{grid-template-columns:minmax(0,1fr)}}

/* ---------- главная ----------
   Вес распределён, а не роздан поровну: крупный первый экран, возможности
   без коробок, продукт — главный визуальный объект. Прежняя версия была
   набором маленьких bordered boxes одного веса; это старит страницу сильнее
   любых цветов и градиентов. */
/* Подложка первого экрана — во всю ширину окна (приём тот же, что у подвала:
   тонированы КРАЯ страницы, а содержимое между ними белое). Растянута тенью
   с обрезкой, а не полями на 100vw: vw считает вместе с полосой прокрутки и
   дал бы горизонтальный сдвиг, который `make front-checks` ловит на 390 px.
   Тень расходится от границы блока во все стороны, поэтому узкая колонка в
   860 px закрашивает всю полосу так же, как широкая. */
.hero{padding:44px 0 48px;margin:0 auto;
  background:var(--surface-2);box-shadow:0 0 0 100vmax var(--surface-2);clip-path:inset(0 -100vmax)}
/* Две колонки: разговор слева, продукт справа. Правая шире — снимок
   работает как изображение, а не как иллюстрация к тексту. `minmax(0,…)`
   у обеих: без него длинное слово в колонке растягивает грид и уносит
   страницу вбок */
.hero-in{position:relative;display:grid;grid-template-columns:minmax(0,47fr) minmax(0,53fr);
  gap:34px;align-items:center}
.hero-txt{min-width:0}
.hero h1{font-size:clamp(34px,4vw,56px);line-height:1.04;letter-spacing:-.035em;font-weight:600;margin:0 0 18px}
.hero .lead{font-size:18px;line-height:1.55;color:var(--muted);max-width:560px;margin:0 0 24px}
.hero-act{display:flex;gap:10px;flex-wrap:wrap;margin:0}
/* Три возражения под кнопкой. Сетка в три равные доли, а не flex с
   переносом: перенос давал ряд «2 + 1», и третья опора читалась как
   недописанная. Разделители — тонкие линии слева, как в спецификации */
.hero-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px;
  list-style:none;margin:40px 0 0;padding:0}
.hero-facts li{display:flex;align-items:flex-start;gap:7px;font-size:13.5px;line-height:1.35;
  color:var(--muted);padding-left:12px;border-left:1px solid var(--line)}
.hero-facts li:first-child{padding-left:0;border-left:0}
.hero-facts .i{flex:none;margin-top:1px;color:var(--line-2)}
.hero-facts b{display:block;font-weight:600;color:var(--ink-2);margin-bottom:2px}
/* Снимок целиком и развёрнут вполоборота (решение владельца 01.09.2026).
   Разворот — на самой картинке, а не на коробке: коробка остаётся
   прямоугольной и держит место в сетке, поэтому соседний текст никуда не
   съезжает.

   Сторону разворота задают ЗНАК угла и точка вращения, и они ходят парой.
   Здесь ближе правый край: точка на правом крае, угол отрицательный —
   правая сторона стоит на месте и остаётся крупной, левая уходит вглубь
   (выбор владельца 01.09.2026; обратная сторона — это `rotateY(11deg)` с
   `transform-origin:0 50%`, ровно один знак и одно число).

   `scale(1.05)` возвращает ширину, которую съедает перспектива. Рост идёт
   ВЛЕВО, от закреплённого правого края, — то есть в зазор между текстом и
   снимком, а не за поля страницы. За поля снимок не выходит ни на 1440,
   ни на 1000, ни на 390 px.

   Разворот стоит резкости, и это осознанная плата. Трёхмерное
   преобразование уводит картинку в композитный слой, который браузер
   растрирует один раз и без промежуточных уровней: снимок 1400 px,
   ужатый до 566, теряет пятую часть чёткости краёв — замерено на этой же
   странице, 14,3 против 18,9 у прямого. Плоский `skewY` дал бы 17,6, но
   он не разворачивает, а наклоняет: глубины в нём нет. Попытки заставить
   слой растрироваться крупнее (ширина 150–200 % с обратным `scale`)
   делают ХУЖЕ — 12,1 и 10,3, — поэтому их здесь нет */
.hero-shot img{display:block;width:100%;height:auto;
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  box-shadow:var(--shadow-lg);
  transform:perspective(1100px) rotateY(-11deg) scale(1.05);transform-origin:100% 50%}
/* Метки реза и краски — в левом поле, которое для них и освобождено
   отступом `.hero-in`. Отрицательных сдвигов нет намеренно: они дали бы
   горизонтальную прокрутку ровно на тех ширинах, где поля уже кончились */
.hero-marks{display:none;position:absolute;left:0;top:6px;bottom:6px;
  grid-auto-flow:row;align-content:space-between;justify-items:center}
.hero-marks .crop{position:relative;width:16px;height:16px}
.hero-marks .crop::before,.hero-marks .crop::after{content:'';position:absolute;background:var(--line-3)}
.hero-marks .crop::before{left:0;right:0;top:50%;height:1px}
.hero-marks .crop::after{top:0;bottom:0;left:50%;width:1px}
.hero-marks .cmyk{display:grid;gap:2px}
.hero-marks .cmyk i{display:block;width:9px;height:15px}
.hero-marks .cmyk i:nth-child(1){background:#00A9E0}
.hero-marks .cmyk i:nth-child(2){background:#E5007E}
.hero-marks .cmyk i:nth-child(3){background:#FFD100}
/* K — цветом текста, а не бумажной чёрной краской: на тёмной теме
   #2A2622 сливается с фоном, и в полосе появлялась дырка. Смысл тот же:
   ключевой цвет — это цвет, которым напечатан текст страницы */
.hero-marks .cmyk i:nth-child(4){background:var(--ink)}
@media(min-width:1120px){.hero-in{padding-left:48px}.hero-marks{display:grid}}
/* Ниже 900 px первый экран складывается в одну колонку */
@media(max-width:900px){
  .hero-in{grid-template-columns:minmax(0,1fr);gap:30px}
  .hero .lead{max-width:none}
  /* Разворот снимают: на узкой колонке он крадёт и ширину, и остатки
     читаемости — а деваться картинке уже некуда, она и так одна в ряду */
  .hero-shot img{transform:none}
}
/* Три опоры в ряд перестают помещаться раньше, чем ломается всё
   остальное: «До 6 співробітників» в колонке 110 px переносится по слогам */
@media(max-width:560px){
  .hero-facts{grid-template-columns:minmax(0,1fr);gap:9px}
  .hero-facts li{padding-left:0;border-left:0}
  .hero-facts b{display:inline;margin:0}
  .hero-facts b::after{content:' — '}
}
/* Возражения: две колонки на широком экране, одна на узком */
/* Вопросы перед заявкой. Заголовок раздела стоял кеглем 16 — мельче
   собственного лида страницы, — а вопросы шли в ТРИ узкие колонки без
   разделителей. Раздел от этого читался как россыпь текста посреди
   страницы, хотя содержательно он один из главных: это разговор с тем,
   кто дочитал и засомневался.

   Теперь две колонки, вопрос крупнее ответа, и каждая пара отделена
   тонкой линией — линия здесь честнее рамки: она разделяет, но не
   заводит коробку, которой на странице больше нигде нет. */
.objections{padding:40px 0 38px}
/* Заголовок стоит СБОКУ. Сверху он был третьим подряд «заголовок + сетка»,
   и три нижних раздела читались одним серым полем. Сбоку меняется ритм, а
   заголовок вдобавок работает ярлыком колонки */
/* `stretch`, а не `start`: колонка заголовка закрашена и обязана идти на
   всю высоту вопросов. При `start` она обрезалась бы по строке текста, и
   заливка выглядела бы прямоугольником неизвестно откуда */
.qa-in{display:grid;grid-template-columns:minmax(0,250px) minmax(0,1fr);gap:52px;align-items:stretch}
/* Колонка заголовка залита тоном подвала — тем же `--surface-2`, что у
   первого экрана и низа страницы. Третьего тона это не заводит: цвет на
   странице один, меняется только то, какую площадь он занимает */
.objections h2{font-size:30px;letter-spacing:-.028em;line-height:1.15;margin:0;
  background:var(--surface-2);border-radius:var(--r);padding:26px 24px}
.qa{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 48px;margin:0}
.qa>div{padding:15px 0;border-top:1px solid var(--line)}
.qa dt{font-size:18px;font-weight:600;letter-spacing:-.015em;margin:0 0 5px}
/* Ответ сдвинут от вопроса на пару знаков. Отступ в `em`, а не в
   пикселях: он задан относительно кегля ответа и уедет вместе с ним, если
   тот когда-нибудь поменяется */
.qa dd{margin:0 0 0 1em;color:var(--muted);font-size:15.5px;line-height:1.55;max-width:520px}
@media(max-width:980px){
  .qa-in{grid-template-columns:minmax(0,1fr);gap:18px}
  /* В одну колонку заливка превращается в широкую плашку над текстом —
     приём читается только вертикальным столбцом, поэтому здесь его нет */
  .objections h2{background:none;padding:0}
}
@media(max-width:760px){.qa{grid-template-columns:minmax(0,1fr);gap:0}}
/* Шапка внутренней страницы. Отступ сверху был 6 px — заголовок прилипал
   к линии шапки сайта, и страница начиналась без начала. Теперь у неё есть
   верхняя зона; на тарифах она ещё и тонирована (`band`) — тем же приёмом,
   что первый экран главной: тон обозначает верх страницы. */
.site-head{padding:40px 0 32px}
.site-head h1{font-size:40px;letter-spacing:-.03em;line-height:1.1;margin:0 0 12px}
.site-head .lead{font-size:18px;line-height:1.5;color:var(--muted);max-width:720px;margin:0}
.site-head .hint{margin:10px 0 0}
/* Пробный период — не сноска: это первое, что человек хочет знать до цены */
.site-head .trial{margin:12px 0 0;font-size:15px;font-weight:500;color:var(--ok)}
.site-head.band{background:var(--surface-2);box-shadow:0 0 0 100vmax var(--surface-2);
  clip-path:inset(0 -100vmax);padding:44px 0 40px;margin-bottom:40px}
/* Экраны с ОДНИМ действием — заявка и благодарность — идут колонкой по
   центру. Прижатые к левому краю страницы в 1200 px, они выглядели началом
   таблицы, которая не загрузилась: справа было пусто на две трети экрана.
   `.narrow-md` из app.css задаёт только ширину: в кабинете он стоит внутри
   колонок, где выравнивать нечего, и центрирование там было бы лишним */
/* Экран с одним действием стоит посреди окна, а не под шапкой: заявка и
   благодарность занимают треть высоты, и прижатые к верху они выглядели
   началом страницы, которая не догрузилась.
   `safe center` — чтобы высокая форма на низком экране не уезжала верхом
   под шапку: тогда центрирование отключается и работает обычный поток */
.site-main.mid{display:flex;flex-direction:column;justify-content:safe center;
  padding-top:24px;padding-bottom:24px;gap:0}
/* Отступ сверху заголовку тут не нужен — воздух даёт само центрирование, а
   с ним форма помещалась на экран ноутбука только со скроллом в 36 px */
.site-main.mid .site-head{padding-top:0;padding-bottom:22px}
.site-head.mid{text-align:center}
.site-head.mid .lead{margin-left:auto;margin-right:auto}
.site .narrow-md{margin-inline:auto}
.site-head.mid .btn{margin-top:6px}

/* Пробный период над формой заявки: две строки — что даём и когда спросим
   про тариф. Отделено линией, как всё, что не поле формы: читать это надо
   до «Назви друкарні», а не вместе с ней. */
.trial-note{margin:0 0 20px;padding:0 0 16px;border-bottom:1px solid var(--line)}
.trial-note b{display:block;font-size:15px;line-height:1.35}
.trial-note span{display:block;margin-top:3px;font-size:13px;color:var(--muted)}

/* Согласия: две галочки над кнопкой. Отделены линией — это не поле формы,
   а решение, и читать его надо отдельно от «міста» и «пошти» */
.accepts{margin:18px 0 16px;padding:14px 0 0;border-top:1px solid var(--line);display:grid;gap:9px}
.accept{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;line-height:1.45;cursor:pointer}
.accept input{margin-top:2px;flex:none;width:15px;height:15px;accent-color:var(--k);cursor:pointer}
/* Оферта и политика — ССЫЛКИ, и это должно быть видно: в проекте ссылка
   красится своим блоком (`a{color:inherit}`), и здесь они читались обычным
   текстом. Человек обязан принять документ, которого не заметил, что он
   открывается (снимок владельца, 23.09.2026). */
.accept a{color:var(--c);text-decoration:underline;text-underline-offset:2px}
.accept a:hover{color:var(--c-ink)}

/* Линия между разделами вместо рамки вокруг каждого */
hr.zone{border:0;border-top:1px solid var(--line);margin:0}

/* Шесть возможностей: номер, название, строка. Держит сетка и воздух */
/* Сверху поле больше, чем снизу. Номера стояли отдельной строкой над
   названиями и держали блок на расстоянии от первого экрана; уехав в
   строку заголовка, они забрали с собой около двадцати пяти пикселей, и
   блок подскочил к подложке героя. Поле возвращает это расстояние */
.hgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:30px 40px;padding:62px 0 36px}
/* Номер живёт в строке заголовка, набран крупнее названия и чёрным, а
   само название — серым. Роли перевёрнуты намеренно (решение владельца
   01.09.2026): по блоку сперва читается порядок, потом содержание.

   Серый взят `--line-2` — тот же, которым нарисованы линейки разделов.
   Для заголовка это светло, но по контрасту он проходит: 3,2 : 1 на белом
   при кегле 20 полужирным — это «крупный текст», которому норма ставит
   порог 3 : 1. Ниже опускать нельзя, запаса нет.

   `line-height:1` у цифры — иначе крупный кегль растягивает строку
   заголовка и шесть блоков разъезжаются по высоте.

   `white-space:nowrap` на паре «номер + точка» — чтобы перенос никогда не
   отрывал цифру от точки; сам заголовок переносится как обычно. */
.feat h2{font-size:20px;line-height:1.28;letter-spacing:-.018em;margin:0 0 8px;color:var(--line-2)}
.feat h2 .no{font-size:30px;line-height:1;letter-spacing:-.03em;color:var(--ink);
  white-space:nowrap;margin-right:2px}
.feat p{margin:0;font-size:16px;line-height:1.55;color:var(--muted);max-width:360px}

/* Рабочий цикл — путь ОДНОГО заказа, а не перечень возможностей (для
   перечня есть сетка 01–06 выше).

   Заголовок по центру — единственное такое место на странице, и это не
   недосмотр. Центр держится там, где центрирована сама вещь: под
   заголовком не сетка текста, а схема, у которой узлы и подписи стоят по
   центрам колонок. Заголовок слева над центрированной схемой читался бы
   подписью к чему-то другому (решение владельца 01.09.2026).

   Коробок здесь больше нет. Они были единственными на странице, и это же
   их и выдавало: страница объявляет «возможности без рамок», а следом
   четыре рамки подряд. Теперь блок держат иконка, шаг и стрелка между
   шагами — стрелка говорит «дальше» точнее, чем рамка вокруг текста, и
   заодно называет жанр: это последовательность, а не набор.

   Подложки у блока нет. Пробовали дважды — на закрывающем призыве и
   здесь, — и оба раза владелец забраковал (01.09.2026). Тонированы
   остаются только края страницы: первый экран и подвал. Блок держат
   иконки, стрелки и воздух. */
.wf{position:relative;padding:38px 0 40px;text-align:center}
.wf h2{font-size:32px;letter-spacing:-.028em;line-height:1.15;margin:0 0 10px}
.wf .lead{font-size:17px;line-height:1.5;color:var(--muted);max-width:640px;margin:0 auto 30px}
/* Схема процесса, а не четвёртая по счёту сетка текста.
   Раздел притворялся диаграммой, а был колонками подписей: иконка 26 px
   контуром и стрелка в восемь пикселей на полосе в 1264 px — несколько
   процентов краски на всю ширину, и связь между шагами не читалась.

   Теперь узлы нанизаны на одну линию. Линия идёт от центра первого узла
   до центра последнего — отсюда 12,5 % с каждой стороны: при четырёх
   равных колонках центр первой приходится на одну восьмую ширины. Узел
   закрашен фоном страницы и потому разрывает линию сам, без подкладок.

   Подписи по центру под узлами — это фигура, а не текст: у фигуры метки
   стоят под тем, что подписывают, и левый край страницы им не указ. */
.wf-flow{position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  list-style:none;margin:0;padding:0;text-align:center}
.wf-flow::before{content:'';position:absolute;top:22px;left:12.5%;right:12.5%;
  border-top:1px solid var(--line-3)}
.wf-flow li{position:relative;padding:0 16px}
.wf-flow .node{position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border:1px solid var(--line-3);border-radius:50%;background:var(--surface)}
.wf-flow .i{color:var(--ink-2);stroke-width:1.4}
.wf-flow b{display:block;font-size:17px;line-height:1.3;letter-spacing:-.015em;margin:16px 0 6px}
.wf-flow .cap{display:block;font-size:14.5px;line-height:1.5;color:var(--muted)}

/* Продукт — главный объект: во всю полосу и без рамки-контейнера. У снимка
   своя рамка окна, вторая вокруг неё — коробка в коробке */
.showcase{padding:36px 0 38px}
.showcase h2{font-size:32px;letter-spacing:-.028em;line-height:1.15;margin:0 0 10px}
.showcase .lead{font-size:17px;color:var(--muted);max-width:640px;margin:0}
.showcase .more{display:inline-block;margin-top:16px;font-size:14px;color:var(--c);text-decoration:none}
.showcase .more:hover{text-decoration:underline}
.showcase img{display:block;width:100%;height:auto;margin:20px auto 0;border:1px solid var(--line);
  border-radius:var(--r);box-shadow:0 1px 2px rgba(26,24,21,.06), 0 24px 60px -18px rgba(26,24,21,.28)}

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

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

   Шаги стоят ПЕРЕД кнопкой намеренно. «Спробувати безкоштовно» само по
   себе обещает мгновенный вход, которого нет: заявку рассматривает
   человек. Стоя перед кнопкой, шаги снимают это обещание раньше, чем оно
   прозвучало. */
.start{padding:38px 0 4px}
.start h2{font-size:30px;letter-spacing:-.028em;line-height:1.15;margin:0 0 26px}
/* Шаги — строка во всю ширину, четыре колонки в ряд. В два ряда по два
   они занимали половину блока и спорили по весу с призывом под ними.

   Порядок держит СТРЕЛКА, а не номер (решение владельца 01.09.2026).
   Номера здесь были — «01», «02» — и говорили ровно то же, что стрелка,
   занимая при этом отдельный ярус над каждым названием. Заодно они
   спорили с нумерацией шести возможностей выше: там номер крупный, в
   строке заголовка и без ведущего нуля, и два способа нумеровать списки
   на одной странице читались непоследовательностью.

   Стрелка живёт в промежутке между колонками, поэтому промежуток задан
   шире обычного: ей нужно место, и рисуется она не на глазок, а от
   левого края следующего шага. */
.steps{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:26px 46px;text-align:left}
.steps li{position:relative}
.steps li + li::before{content:'';position:absolute;top:50%;left:-29px;width:10.5px;height:10.5px;
  border-top:1.5px solid var(--line-2);border-right:1.5px solid var(--line-2);
  transform:translateY(-50%) rotate(45deg)}
/* Пара «фраза + листы» стоит по центру полосы и прижата друг к другу.
   Левая колонка была `1fr` и растягивалась на весь остаток, хотя текст в
   ней занимает пятьсот с небольшим пикселей: между фразой и картинкой
   оставалось поле шириной с саму фразу.

   Теперь обе колонки заданы своей шириной, а `justify-content:center`
   ставит пару в середину. Выравнивание самого текста прежнее — по левому
   краю: центрировать его значило бы ломать строку кнопки и подписи. */
.start-in{display:grid;grid-template-columns:minmax(0,520px) minmax(0,470px);gap:72px;
  align-items:center;justify-content:center;margin-top:48px}
/* Фраза кеглем первого экрана: это последнее, что человек читает, и
   единственное на странице предложение, обращённое прямо к нему */
.start-close .big{font-size:clamp(26px,2.6vw,40px);line-height:1.1;letter-spacing:-.03em;
  font-weight:600;margin:0 0 24px;max-width:520px}
.start-close .hint{margin:14px 0 0;max-width:400px;font-size:14px}
.steps b{display:block;font-size:18px;line-height:1.3;margin-bottom:6px}
.steps .hint{display:block;font-size:14px;line-height:1.5}
/* Призыв: листы лежат свободно, кнопка под ними. Ни панели, ни рамки, ни
   подложки — у объекта вырезан фон, и серый прямоугольник вокруг него
   превращал бы фирменный элемент в баннер.

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

   Потолок ширины — не про вкус, а про высоту блока: без него листы
   занимали всю колонку и растягивали секцию до полутысячи пикселей, а
   слева под шагами оставалась пустота той же высоты. */
.start-act img{display:block;width:100%;height:auto}

@media(max-width:900px){
  .hgrid{grid-template-columns:minmax(0,1fr);gap:32px;padding:40px 0}
  .start-in{grid-template-columns:minmax(0,1fr);gap:30px;margin-top:34px}
  .steps{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px 46px}
  /* В два столбца стрелка нужна только между парой в ряду: у первого в
     строке слева не предыдущий шаг, а край блока */
  .steps li:nth-child(odd)::before{display:none}
  /* Схема складывается в две пары: линия между узлами в один столбец
     ничего не связывает, поэтому её убираем вместе с раскладкой */
  .wf-flow{grid-template-columns:repeat(2,minmax(0,1fr));gap:30px 0}
  .wf-flow::before{display:none}
  /* На узкой колонке листы вылезать некуда: ширина ровно по месту */
  .start-act img{width:100%;margin:0}
  .start-close .hint{max-width:none}
  /* Поля разделов на телефоне МЕНЬШЕ, чем на большом экране: вертикаль
     здесь дороже всего, и воздух, который на 1920 держит ритм, на 390
     просто отодвигает следующий экран.
     Числа пересчитаны от новых базовых: сетку и кегли правили одной
     правкой, базовые поля уехали вниз (64→44, 42→36, 46→38), а эти
     остались от старых — и каждое из них стало ПРИБАВЛЯТЬ воздух там,
     где обязано убавлять. Нижнее поле `.start` не трогаем: четыре
     пикселя там стоят намеренно, за ним сразу закрытие */
  .hero{padding:36px 0 38px}
  .showcase{padding:30px 0 32px}
  .start{padding:32px 0 4px}
  .wf{padding:32px 0 34px}
}
@media(max-width:560px){
  /* В один столбец ни линии схемы, ни стрелок между шагами: порядок
     держит сам столбец, а связь, нарисованная сверху вниз, читалась бы
     хуже, чем её отсутствие */
  .wf-flow{grid-template-columns:minmax(0,1fr);gap:26px 0}
  .steps{grid-template-columns:minmax(0,1fr);gap:14px}
  .steps li::before{display:none}
}

/* Тарифы: средний выделен рамкой, а не цветом — цвет тут кричал бы.
   Карточки здесь оправданы, в отличие от главной: человек СРАВНИВАЕТ три
   предложения, и граница помогает ему держать их порознь. Значит и воздух
   им нужен внутри, а не по краям страницы — карточка во всю полосу с
   полями в 24 px читается как товар, а в 14 px — как строка таблицы.
   Белая заливка вместо серой: серые карточки на белом полотне выглядели
   выключенными, а выбирать предлагается из всех трёх. */
.site-tiers{display:grid;gap:18px;grid-template-columns:repeat(3,minmax(0,1fr))}
.site-tiers .card{background:var(--surface)}
.site-tiers .card.pick{border-color:var(--k);box-shadow:var(--shadow)}
.tier-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
.pick-badge{flex:0 0 auto;font-size:10px;font-weight:600;letter-spacing:.02em;color:var(--muted);background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:2px 9px;white-space:nowrap}
.site-tiers .card-body{flex:1;display:flex;flex-direction:column;padding:26px 24px 24px}
/* Ярлык тарифа не растягивается колонкой: в flex-колонке элемент по
   умолчанию тянется на всю ширину, и `Free` выглядел пустым полем ввода */
.site-tiers .tag{align-self:flex-start}
.site-tiers .price{margin:16px 0 0}
.site-tiers .price b{font-size:36px;letter-spacing:-.035em;line-height:1}
.site-tiers .price .hint{font-size:14px}
/* Места — второй по важности параметр после цены, поэтому отделены линией
   и набраны кеглем строки, а не сноски: платят именно за них */
.site-tiers .seats{margin:20px 0 0;padding:16px 0 0;border-top:1px solid var(--line)}
.site-tiers .seats b{display:block;font-size:17px;letter-spacing:-.015em;line-height:1.25}
.site-tiers .seats span{font-size:13.5px;color:var(--muted)}
.site-tiers .from{margin:20px 0 0;font-size:13px}
.site-tiers .site-list{margin:10px 0 0;gap:9px;font-size:14px}
.site-tiers .extra{margin:14px 0 0;font-size:13px}
/* Кнопка прижата к низу: у всех трёх карточек она на одной линии, даже
   если списки разной длины */
.site-tiers .card-body>.btn{margin-top:auto}
.site-tiers .site-list,.site-tiers .extra{margin-bottom:24px}

/* Сравнение: то, ради чего на эту страницу и приходят. Строки собираются
   из тех же `featuresOf()`, что и карточки, — руками таблицу не пишем */
.site-compare{margin-top:48px}
.site-compare h2{font-size:22px;letter-spacing:-.02em;margin:0 0 16px}
.site-compare .tbl-wrap{border:1px solid var(--line);border-radius:var(--r);background:var(--surface)}
.site-compare table.tbl{min-width:520px;width:100%}
.site-compare table.tbl td.nm{width:100%}
.site-compare table.tbl th.cn,.site-compare table.tbl td.cn{width:118px;white-space:nowrap}
/* На телефоне таблица шире экрана — и это нормально, ненормально было
   другое: в исходном положении не видно НИ ОДНОГО значения, только колонка
   названий, а прокрутив вбок, теряешь названия строк. Колонка имён
   прилипает к левому краю, столбцы сужаются, и на 390 px видно название и
   два тарифа сразу; третий доезжает прокруткой, не унося подписи. */
@media(max-width:560px){
  .site-compare table.tbl{min-width:420px}
  .site-compare table.tbl th.cn,.site-compare table.tbl td.cn{width:86px}
  .site-compare table.tbl td.nm,.site-compare table.tbl thead th:first-child{
    position:sticky;left:0;z-index:1;background:var(--surface);
    box-shadow:1px 0 0 var(--line)}
  .site-compare table.tbl thead th:first-child{background:var(--surface-2)}
}
.site-compare .yes{color:var(--ok);margin:0 auto}
.site-compare .no{color:var(--faint)}

/* Оплата: не сноска под карточками, а ответ на «что будет после нажатия».
   Мелким шрифтом внизу это читалось юридической оговоркой */
.site-pay{margin-top:36px;max-width:660px}
.site-pay h2{font-size:17px;letter-spacing:-.015em;margin:0 0 7px}
.site-pay p{margin:0;font-size:14.5px;line-height:1.6;color:var(--muted)}
.site-list{list-style:none;padding:0;margin:14px 0;display:grid;gap:7px;font-size:13.5px}
.site-list li{padding-left:18px;position:relative;color:var(--muted)}
.site-list li::before{content:"·";position:absolute;left:6px;color:var(--faint);font-weight:700}

/* Снимки экранов: настоящие, поэтому им нужна рамка и подпись */
.site-shots{display:grid;gap:14px;grid-template-columns:repeat(2,minmax(0,1fr))}
.site-shots img{width:100%;height:auto;display:block;border-bottom:1px solid var(--line)}
.site-shots h2{margin:0 0 4px}
.site-shots p{margin:0}

/* Юридический документ: сорок три раздела, которые читают не подряд, а
   ищут в них пункт. Поэтому номер пункта вынесен в поле слева — по колонке
   номеров глаз находит «34.4» за секунду, а сплошной текст с номерами
   внутри абзаца заставляет читать всё. */
.lex p{display:grid;grid-template-columns:52px minmax(0,1fr);gap:0 4px;margin:0 0 9px}
.lex p .n{font-family:var(--mono);font-size:12px;color:var(--faint);padding-top:2px}
.lex h2{font-size:15px;letter-spacing:-.01em;margin:26px 0 12px}
.lex h2:first-of-type{margin-top:18px}
.lex .lex-lead{display:block;margin:0 0 12px;color:var(--muted);line-height:1.6}
.lex .gap{color:var(--bad);font-family:var(--mono);font-size:12.5px}
/* Предупреждение о незаполненных реквизитах и о языке — не украшение:
   документ, который принимают галочкой, обязан говорить о своём состоянии */
.lex-warn{display:block !important;margin:0 0 18px;padding:10px 12px;border:1px solid var(--y-line);
  border-radius:var(--r);background:var(--y-soft);color:var(--y);font-size:13.5px;line-height:1.5}
/* Политика — документ прозой, без нумерации пунктов: абзацы, подзаголовки
   и перечни. Поэтому своя оболочка, а не `.lex`: там абзац — сетка с полем
   под номер, и текст без номера ушёл бы в колонку шириной 52 px */
.lexdoc p{margin:0 0 10px;line-height:1.6}
.lexdoc h2{font-size:15px;letter-spacing:-.01em;margin:26px 0 12px;color:var(--ink)}
.lexdoc h2:first-of-type{margin-top:18px}
.lexdoc h3{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--ink-2)}
.lexdoc ul{margin:0 0 12px;padding-left:18px;display:grid;gap:4px;line-height:1.5}
.lexdoc .lex-lead{color:var(--muted)}
.lexdoc b{color:var(--ink)}
.lex-req{display:grid;grid-template-columns:200px minmax(0,1fr);gap:6px 12px;margin:14px 0 18px;font-size:14px}
.lex-req dt{color:var(--muted)}
.lex-req dd{margin:0}
/* Перечни внутри пунктов: они висят на левом поле номеров, чтобы список
   читался как продолжение своего пункта, а не как отдельный блок */
.lex-list{margin:0 0 9px 56px;padding-left:16px;display:grid;gap:4px;font-size:14px;line-height:1.5}
.lex-copy{display:block !important;margin:26px 0 0;font-size:13px;color:var(--faint)}
.lex-acts{margin:12px 0 0;padding-left:18px;display:grid;gap:5px;font-size:14px;color:var(--muted)}

/* Оферта и «о продукте»: колонка для чтения, а не для работы */
.site-doc{max-width:760px}
.site-doc h2{margin:26px 0 8px}
.site-doc h2:first-child{margin-top:0}
.site-doc p{margin:0;color:var(--muted);line-height:1.6}

/* Ловушка для роботов: убрана из потока, но НЕ display:none — часть ботов
   такие поля пропускает именно по нему */
.trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

@media(max-width:900px){
  .site-grid,.site-tiers,.site-shots{grid-template-columns:minmax(0,1fr)}
  .hero h1{font-size:30px}
  .site-nav{order:3;flex-basis:100%}
}

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

   Ритм держат не картинки, а система заголовков: РАЗДЕЛ одним словом →
   сильная строка → объяснение. Страница из десяти одинаковых «заголовок и
   четыре строки» читается как статья, в которой глазу негде остановиться. */
.longread h2{font-size:13px;line-height:1.2;letter-spacing:.09em;text-transform:uppercase;
  font-family:var(--mono);font-weight:600;color:var(--faint);margin:0 0 10px}
.longread p{margin:0 0 12px;line-height:1.65;color:var(--ink-2)}
.longread p:last-child{margin-bottom:0}
.longread .lead{font-size:16.5px;color:var(--muted)}
.lr-block{margin:0 0 34px}
.lr-block:last-child{margin-bottom:0}
/* Сильная строка — крупнее текста и темнее: на неё падает взгляд, когда
   страницу просматривают, не читая каждое предложение */
.lr-punch{font-size:18px;line-height:1.4;letter-spacing:-.015em;color:var(--ink);margin:0 0 12px}
.lr-audience{font-size:15px;color:var(--ink-2);margin:10px 0 0}
/* Позиционирование: другой фон и воздух, без толстых рамок и цвета */
.lr-pull{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);
  padding:24px 22px;margin:0 0 34px;text-align:center}
.lr-pull p{margin:0 0 6px;font-size:17px;line-height:1.45;color:var(--ink-2)}
.lr-pull p:last-child{margin-bottom:0}
.lr-rule{border:0;border-top:1px solid var(--line);margin:0 0 34px}
/* Маленькая иллюстрация принципа — не данные, а пример: строка денег
   объясняет связь «замовлено — сплачено — борг» быстрее абзаца */
.mini{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 14px}
.mini span{display:inline-flex;align-items:center;height:28px;padding:0 11px;border:1px solid var(--line-2);
  border-radius:var(--r);background:var(--surface);font-family:var(--mono);font-size:12.5px;color:var(--ink-2)}
.mini span.due{color:var(--bad);border-color:var(--bad-line);background:var(--bad-soft)}
.longread .chain{margin:0 0 14px}

/* Цепочка: два места текста описывают ДВИЖЕНИЕ — статусы позиции и историю
   склада. Словами это читается вдвое дольше, чем видно глазом. */
.chain{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:16px 0 18px}
.chain .lnk{display:inline-flex;align-items:center;height:26px;padding:0 10px;border:1px solid var(--line-2);
  border-radius:var(--r);background:var(--surface);font-size:12.5px;font-weight:500;color:var(--ink)}
.chain .arw{color:var(--faint);font-size:13px;line-height:1}

/* «Чего не делает» — отдельным блоком, а не сноской в конце: это фильтр,
   и человек должен наткнуться на него до заявки, а не через три недели. */
.lr-not{margin-top:38px;padding:24px;border:1px solid var(--line);border-radius:var(--r);background:var(--surface-2)}
.lr-not p{color:var(--muted)}

/* Итог лонгрида по левому краю, как весь текст выше. По центру стояла
   одна строка — та, что вводит список под собой, — и связь между ними
   рвалась: центрированный лид читался отдельной репликой, а не началом
   фразы, которую список продолжает */
.lr-end{margin-top:38px}
.lr-goals{list-style:none;padding:0;margin:16px 0 0;display:grid;gap:8px}
.lr-goals li{padding-left:20px;position:relative;color:var(--ink-2);line-height:1.55}
.lr-goals li::before{content:"";position:absolute;left:4px;top:9px;width:6px;height:6px;border-radius:var(--r);
  background:var(--k)}

/* Отсечка в конце страницы: «дальше — вот это». Стилей у неё не было
   вовсе, и блок доставался браузеру как есть: заголовок кеглем h5 —
   МЕЛЬЧЕ строки под ним, — кнопки обычного размера и ни отступа, ни
   границы от текста выше. Конец страницы выглядел оборванным.

   Линия сверху — та же, что делит разделы лонгрида: отсечка обязана
   читаться отдельным блоком, а не последним абзацем предыдущего. */
.site-cut{margin-top:44px;padding-top:34px;border-top:1px solid var(--line)}
.site-cut h2{font-size:26px;letter-spacing:-.025em;line-height:1.2;margin:0 0 8px}
.site-cut .lead{font-size:17px;line-height:1.55;color:var(--muted);max-width:640px;margin:0 0 22px}

/* Настоящие экраны: снимок во всю ширину полосы, подпись над ним. Без
   рамок-ноутбуков и наклонов — мокап прячет ровно то, ради чего смотрят */
.site-look{margin-top:0}
.shot{margin:0 0 22px;border:1px solid var(--line);border-radius:var(--r);background:var(--surface);overflow:hidden}
.shot:last-child{margin-bottom:0}
.shot figcaption{display:flex;align-items:center;gap:16px;padding:14px 18px;border-bottom:1px solid var(--line);
  background:var(--surface-2)}
.shot figcaption .tt{flex:1;min-width:0}
.shot figcaption .btn{flex:none;margin-left:auto}
.shot figcaption h2{margin:0 0 3px}
.shot figcaption p{margin:0;font-size:13.5px;line-height:1.5;color:var(--muted)}
.shot img{width:100%;height:auto;display:block}


/* ——— Переписка обращения (TASK-84) ———
   Одна разметка на кабинет друкарні и на кабинет платформы: расходясь,
   они однажды покажут вложение в одном и не покажут в другом.
   Своя реплика прижата вправо и подкрашена интерактивным цветом — тем
   же, каким кабинет метит «наше действие» везде. */
.thread{display:flex;flex-direction:column;gap:12px}
.msg{border:1px solid var(--line);border-radius:var(--r);padding:10px 12px;background:var(--surface-2);max-width:88%}
.msg.platform{background:var(--c-soft);border-color:var(--c-line);align-self:flex-end}
.msg-who{font-family:var(--mono);font-size:11px;color:var(--faint);margin-bottom:4px}
.msg-body{margin:0;font-size:14px}
.msg-files{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;align-items:center}
.msg-thumb{max-width:180px;max-height:140px;border:1px solid var(--line-3);border-radius:var(--r)}
/* Вложение-картинка: клик открывает снимок, иконка в углу — сохраняет.
   Иконка приходит по наведению, чтобы не закрывать собой то, ради чего
   снимок и прислали. Фокус с клавиатуры показывает её так же, как
   наведение.

   Невидимая — значит и не нажимается: opacity:0 оставляет элемент
   кликабельным, и на сенсорном экране палец попадал бы в «зберегти»
   там, где человек метил в снимок. `any-hover`, а не `hover`: на
   ноутбуке с сенсорным экраном основное устройство умеет наводить, и
   по `hover:none` иконка осталась бы невидимой и при этом ловящей
   касания.

   Снимку задан минимум: вставленный кусочек экрана бывает 40×30, и на
   нём иконка накрыла бы собой весь файл. object-fit оставляет пропорции,
   поля добирает плитка. */
.msg-file{position:relative;display:inline-flex}
.msg-thumb{min-width:96px;min-height:72px;object-fit:contain}
.msg-file-get{position:absolute;top:6px;right:6px;display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;border:1px solid var(--line-3);border-radius:var(--r);
  background:var(--surface);color:var(--ink-2);opacity:0;pointer-events:none;transition:opacity .12s}
.msg-file:hover .msg-file-get,.msg-file:focus-within .msg-file-get{opacity:1;pointer-events:auto}
.msg-file-get:hover{color:var(--c)}
@media (any-hover:none){.msg-file-get{opacity:1;pointer-events:auto}}
/* Снимок в лайтбоксе: во всю ширину диалога, но не выше экрана — иначе
   низ картинки уезжает за край и до него не добраться */
.lightbox-image{max-width:100%;max-height:74vh}
/* Имя файла ужимается многоточием только с min-width:0 — без него
   flex-элемент упирается в min-content и растягивает шапку */
#lightbox .modal-title{min-width:0}
/* Файл задачи: превью и под ним «Прибрати». figure/figcaption, а не два
   div: подпись действительно относится к изображению, и снимок экрана в
   задаче без подписи «убрать» пришлось бы терпеть вечно — вставленный не
   туда он не редкость, а обычное дело */
.taskfile{margin:0;display:flex;flex-direction:column;align-items:flex-start;gap:4px}
.taskfile-drop{background:none;border:0;padding:0;font:inherit;font-size:11.5px;
  color:var(--muted);text-decoration:underline;cursor:pointer}
.taskfile-drop:hover{color:var(--bad)}

/* Ряд видов обращения: radio без предвыбора, кликается вся плашка.
   Нативный кружок спрятан визуально, но остаётся в табуляции — иначе
   вид стало бы не выбрать с клавиатуры. */
.kindpick{display:flex;flex-wrap:wrap;gap:8px}
.kindpick-item{position:relative;display:inline-flex}
.kindpick-item input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
.kindpick-item span{display:inline-block;padding:6px 14px;border:1px solid var(--line-3);border-radius:20px;
  background:var(--surface-2);font-size:13px;color:var(--ink-2)}
.kindpick-item input:checked + span{background:var(--k);border-color:var(--k);color:var(--on-k)}
.kindpick-item input:focus-visible + span{outline:2px solid var(--c);outline-offset:2px}

/* ——— Доска задач платформы (TASK-84) ———
   Три колонки, карточка с полосой приоритета слева. Перенос кнопками —
   основной путь: доска, работающая лишь мышью, недоступна с клавиатуры
   и мертва на телефоне. */
.board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
@media (max-width:860px){.board{grid-template-columns:1fr}}
.board-col{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);padding:10px}
.board-col-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:9px;
  font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.board-card{background:var(--surface);border:1px solid var(--line);border-left-width:3px;border-radius:var(--r);
  padding:9px 10px;margin-bottom:8px;box-shadow:var(--shadow-sm)}
.board-card.s-reject{border-left-color:var(--bad)}
.board-card.s-accept{border-left-color:var(--c)}
.board-card.s-draft{border-left-color:var(--line-3)}
.board-card-title{display:block;font-size:13.5px;font-weight:600;margin-bottom:5px;line-height:1.35}
.board-card-foot{display:flex;justify-content:space-between;gap:8px;font-size:11px;color:var(--faint)}
.board-card-move{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
.board-card.dragging{opacity:.5}
.board-col.dropping{outline:2px dashed var(--c);outline-offset:-4px}

/* ——— Мелочь очереди и карточки обращения (TASK-84) ———
   Три класса, на которые разметка ссылалась без описания: без них
   контекст рисуется списком в столбик, страницы очереди слипаются,
   а перечень получателей несёт браузерные маркеры и отступ. */
.ctx{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0;font-size:12.5px}
.ctx dt{color:var(--muted)}
.ctx dd{margin:0;color:var(--ink-2);overflow-wrap:anywhere}
@media (max-width:520px){.ctx{grid-template-columns:1fr;gap:2px 0}.ctx dd{margin-bottom:6px}}
.pager{display:flex;flex-wrap:wrap;gap:4px}
.plain{list-style:none;margin:0 0 12px;padding:0}
.plain li{padding:3px 0;font-size:13px}

/* ——— Асистент налаштування (TASK-91) ———
   Ответы — плитками в несколько колонок, а не списком в столбик: шесть
   форматов столбиком выглядят анкетой, а плиткой — выбором. Цены на
   девятом шаге по той же причине идут в два-три ряда: двадцать полей
   столбиком не помещаются в модалку и заставляют скроллить то, что
   человек должен видеть целиком. */
.a-picks{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}
.a-pick{display:flex;align-items:center;gap:8px;padding:9px 11px;background:var(--surface-2);
  border:1px solid var(--line);border-radius:var(--r);font-size:13.5px;cursor:pointer}
.a-pick:hover{border-color:var(--line-3)}
.a-pick input{flex:none}
.a-pick>span{min-width:0}
.a-pick small{display:block;font-size:11px;color:var(--faint);margin-top:1px}
.a-pick .pill{margin-left:6px;vertical-align:middle}
.a-pick.a-locked{opacity:.6;cursor:default}
.a-pick.a-other{border-style:dashed;background:transparent;color:inherit}
/* Шаг с ОДИНОЧНЫМ выбором: карточки одинаковы со следующими шагами, где
   выбор множественный, и человек жал три подряд, каждый раз молча снимая
   предыдущий. Отмеченная карточка теперь видна сама, без чтения подсказки. */
.a-picks.a-one .a-pick:has(input:checked){border-color:var(--c);background:var(--surface-3);box-shadow:inset 0 0 0 1px var(--c)}
.a-pick.a-other:hover{color:var(--c)}
.a-own{margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
.a-rates{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:8px}
.a-rate{display:flex;flex-direction:column;gap:3px}
.a-rate-t{font-size:12px;color:var(--muted);overflow-wrap:anywhere}
.a-review{max-height:46vh;overflow:auto;border:1px solid var(--line);border-radius:var(--r)}
.a-review .tbl{margin:0}
/* Шапка таблицы липнет к странице на 56px — под верхнюю панель кабинета.
   В своей прокрутке отсчёт идёт от коробки, и те же 56px опускали шапку в
   середину списка, поверх второй строки.
   Селектор с `table.tbl` намеренно: у правила выше специфичность 0-1-3, и
   `.a-review thead th` (0-1-2) его не перебивал — правка была написана и
   не работала. */
.a-review table.tbl thead th{top:0}

/* ——— Послуги і ціни (TASK-98) ———
   Вкладки — серверные: активная в адресе, и «назад» браузера работает.
   Список услуг — строками, а не карточками: в строке умещается и цена, и
   состояние, а сравнивать услуги удобнее в столбик. */
.mt14{margin-top:14px}
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);margin-bottom:16px;overflow-x:auto;scrollbar-width:none}
.tabs .tab{padding:9px 12px;font-size:13px;font-weight:500;color:var(--muted);border-bottom:2px solid transparent;margin-bottom:-1px;
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap;transition:color .12s}
.tabs .tab:hover{color:var(--ink)}
.tabs .tab.on{color:var(--ink);border-color:var(--k);font-weight:600}
.tabs .tab .n{font-family:var(--mono);font-size:10.5px;font-weight:600;color:var(--faint);background:var(--surface-3);padding:0 6px;border-radius:20px;line-height:17px}
.tabs .tab .n.warn{color:var(--y);background:var(--y-soft)}
.ph .kind .pill{vertical-align:middle}

.pill.ok{color:var(--ok);background:var(--ok-soft);border-color:var(--ok-line)}
.pill.warn{color:var(--y);background:var(--y-soft);border-color:var(--y-line)}
.pill.bad{color:var(--bad);background:var(--bad-soft);border-color:var(--bad-line)}
/* «Терміново»: организационная пометка очереди работ. Цвет — `.pill.bad`
   в разметке, своего красного заводить не за чем; здесь только отступ от
   номера заказа. */
.pill.urg{margin-left:6px}
.urgent-toggle{display:inline-flex;align-items:center;gap:5px;cursor:pointer}
.urgent-toggle.on{color:var(--bad);font-weight:600}
.pills{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}

/* Смуга справ: число, что с ним делать и куда идти. Ссылка целиком — на
   телефоне в число не попадёшь */
.todo{display:flex;flex-wrap:wrap;border:1px solid var(--line);border-radius:var(--r);background:var(--surface);margin-bottom:14px;overflow:hidden}
.todo>a{display:flex;align-items:center;gap:9px;padding:10px 14px;font-size:12.5px;color:var(--muted);flex:1 1 220px;border-right:1px solid var(--line);transition:background .12s}
.todo>a:last-child{border-right:0}
.todo>a:hover{background:var(--surface-2);color:var(--ink)}
.todo>a>span:not(.kv){display:flex;flex-direction:column;min-width:0}
.todo .kv{font-family:var(--mono);font-size:18px;font-weight:600;letter-spacing:-.03em;color:var(--ink);line-height:1;flex:none}
.todo .kv.warn{color:var(--y)}
.todo .kv.bad{color:var(--bad)}
.todo .kv.ok{color:var(--ok)}
.todo .hint{font-size:11.5px}
.todo .arr{margin-left:auto;color:var(--line-3);flex:none}

/* Группа — по виду расчёта; строка — услуга */
.grp{display:flex;align-items:baseline;gap:9px;padding:14px 15px 6px;flex-wrap:wrap}
.grp .eyebrow{color:var(--muted)}
.grp .cnt{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.grp .hint{margin-left:auto}
.grp-page{padding:18px 0 8px}
.svcrow{display:grid;grid-template-columns:36px minmax(0,1fr) auto auto 16px;gap:12px;align-items:center;padding:11px 15px;border-top:1px solid var(--line);color:inherit;transition:background .12s}
.svcrow:hover{background:var(--surface-2)}
.svcrow .ic{width:36px;height:36px;border-radius:var(--r);background:var(--surface-2);border:1px solid var(--line);display:grid;place-items:center;color:var(--muted)}
.svcrow.off .ic,.svcrow.off .t{opacity:.55}
.svcrow .t{min-width:0}
.svcrow .t b{display:block;font-size:13.5px;font-weight:600;letter-spacing:-.01em}
.svcrow .t span{display:block;font-size:11.5px;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.svcrow .pr{text-align:right;font-family:var(--mono);font-size:12.5px;white-space:nowrap}
.svcrow .pr b{display:block;font-weight:600}
.svcrow .pr span{display:block;font-size:10.5px;color:var(--faint)}
.svcrow .chev{color:var(--line-3)}
.svcrow:hover .chev{color:var(--muted)}
@media (max-width:760px){
  .svcrow{grid-template-columns:36px minmax(0,1fr) 16px}
  .svcrow .pr{grid-column:2;text-align:left}
  .svcrow .pr b,.svcrow .pr span{display:inline}
  .svcrow .pr span{margin-left:6px}
  .svcrow .pills{grid-column:2;justify-content:flex-start}
}

/* Оси услуги на вкладке «Що входить»: группы галочек в несколько колонок */
.optsgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
.opts .opt small{display:block;font-weight:400;color:var(--faint);font-size:11px}

/* Собівартість по клеткам: откуда каждое число */
.src{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.02em;color:var(--faint);margin-top:2px;white-space:nowrap}
.src a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.src-in{font-family:var(--mono);font-size:10px;letter-spacing:.02em;color:var(--faint);margin-left:6px;font-weight:400}
.tbl td.nowrap{white-space:nowrap}
/* Таблицы «Матеріалів»: материал одной ячейкой на все его полки, поля
   ростом со строку */
/* Строки «Матеріалів» ужаты, как матрица цен: строка текста в высоту,
   и в панелях тоже (владелец, 20.09.2026) */
/* Селектор той же формы, что у базового `table.tbl td` (0,1,2), иначе
   базовые 8 px побеждают, и строка снова вырастает до сорока шести */
table.tbl.tbl-mat td{padding:3px 10px}
table.tbl.tbl-mat th{padding:6px 10px}
.tbl-mat td.rs{vertical-align:middle}
.mat .srow{padding:5px 0}
.mat .card-body .sub{margin-bottom:8px}
.tbl-mat tr.grp-end td{border-bottom:1px solid var(--line-2)}
.tbl-mat tr:last-child td{border-bottom:0}
.tbl-mat input.rate{width:88px;height:28px;padding:1px 8px;margin-left:auto;font-family:var(--mono);font-size:12.5px;font-weight:500}
.tbl-mat input.isempty{border-style:dashed;border-color:var(--y-line);background:var(--y-soft)}
.tbl-mat .chips{gap:4px}
.tbl td.below{color:var(--bad)}
.tbl td.gain{color:var(--ok)}

/* Матрица цен на карточке услуги (TASK-98): поля узкие, как числа, строки
   собраны бумагой, под себестоимостью — из чего она сложилась */
.mtx-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.mtx-head h2{margin:0}
.mtx-head .mtx-tiers{margin-left:auto;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.mtx-head .mtx-tiers form{margin:0;display:flex}
.mtx-head .tier-in{width:96px}
/* Инструменты над всем прайсом — справа: слева читается то, что про сам
   прайс (имя, число комбинаций, пороги), а действия стоят там же, где
   кнопки строкой выше (владелец, 20.09.2026). */
.mtx-head .mtx-tools{display:flex;gap:6px;flex-wrap:wrap;width:100%;justify-content:flex-end;
  padding-top:8px;border-top:1px dashed var(--line)}
/* Строка матрицы — одна строка текста: Bootstrap кладёт в .table по 8 px
   паддинга сверху и снизу и берёт селектором выше нашего, отсюда селектор
   с той же формой. Поля — ростом со строку, а не с форму */
.table.mtx > :not(caption) > * > *{padding:3px 10px}
.table.mtx > thead > * > *{padding:6px 10px}
.mtx input.rate{width:88px;height:28px;padding:1px 8px;margin-left:auto;font-family:var(--mono);font-size:12.5px;font-weight:500}
.mtx td{white-space:nowrap}
.mtx input.isempty{border-style:dashed;border-color:var(--y-line);background:var(--y-soft)}
.mtx tr.grp-row td{background:var(--surface-2);font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--faint);padding:5px 11px;border-bottom:1px solid var(--line)}
.mtx td.st{white-space:nowrap}
.mtx td.st .pill+.pill{margin-left:4px}
.mtx td .src{text-align:right;white-space:normal}
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:11.5px;color:var(--muted)}
.legend .sw2{display:inline-block;width:14px;height:12px;border-radius:2px;border:1px solid var(--line-2);vertical-align:-1px;margin-right:5px;background:var(--surface)}
.legend .sw2.manual{border-color:var(--c-line);background:var(--c-soft)}
/* Не `.empty`: так зовётся пустое состояние списка с отступами в 44 px,
   и квадратик легенды раздувался до карточки */
.legend .sw2.blank{border-style:dashed;border-color:var(--y-line);background:var(--y-soft)}
.pill.manual{color:var(--c-ink);background:var(--c-soft);border-color:var(--c-line)}

/* Допуслуги: строка раскрывается в свои позиции (native details — без
   скрипта), стрелка поворачивается */
.xrow summary{list-style:none;cursor:pointer}
.xrow summary::-webkit-details-marker{display:none}
.xrow summary .xopen{transition:transform .12s}
.xrow[open] summary .xopen{transform:rotate(180deg)}
.xrow .xbody{padding:4px 0 12px 0;border-bottom:1px solid var(--line)}
.xrow:last-child .xbody{border-bottom:0}
.xrow .xbody .dlist{max-height:none}
button.pill{cursor:pointer}
button.pill:hover{border-color:var(--line-3);color:var(--ink)}

/* «Друкарня» (по прототипу): поля рядами-сеткой, подпись под полем, тумблер
   ПДВ — галочка под видом переключателя, одна кнопка внизу справа. Правила
   под .frow: голый .field живёт и на карточке клиента с бутстраповскими
   отступами, менять его всем экранам незачем */
.frow{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;align-items:start}
.frow.c2{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.frow+.frow{margin-top:12px}
.frow .field{display:flex;flex-direction:column;gap:5px;min-width:0}
.frow .field .form-label{margin:0;font-size:12.5px;font-weight:500;color:var(--ink-2)}
.frow .field .ft{font-size:11.5px;color:var(--faint);line-height:1.4}
.saverow{display:flex;align-items:center;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:14px}
/* Тумблер на галочке: сама галочка спрятана, но остаётся в форме и в
   фокусе с клавиатуры; переключатель рисует .sw по её состоянию */
.swlbl{display:inline-flex;align-items:center;gap:8px;min-height:34px;position:relative;cursor:pointer;font-size:13.5px;margin:0}
.swlbl input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.swlbl input:checked+.sw{background:var(--c)}
.swlbl input:checked+.sw i{transform:translateX(16px)}
.swlbl input:focus-visible+.sw{box-shadow:0 0 0 3px var(--c-soft)}
.logo-prev{max-height:64px;width:auto}

/* Пороги тиража: подпись плашкой, снятие — отдельной кнопкой с корзиной.
   Крестик внутри плашки читался как украшение, и порог, заведённый
   случайно, казался неснимаемым (владелец, 20.09.2026) */
.mtx-head .mtx-tiers .tierchip{display:flex;align-items:center;gap:3px}
.mtx-head .mtx-tiers .tierchip .iconbtn{width:24px;height:24px}
/* Фильтр матрицы: строки прячутся на месте, форма остаётся одной */
.mtx-head .mtx-filter{display:flex;align-items:center;gap:6px;flex-wrap:wrap;width:100%}
.mtx-head .mtx-filter .form-select{width:auto;min-width:150px}

/* На телефоне матрица — карточки (правило выше по файлу), и ужатые отступы
   строк с узкими полями ей ни к чему. Селекторы той же формы, что у
   компактных, иначе те побеждают и в карточке */
@media(max-width:560px){
  .table.mtx > :not(caption) > * > *{padding:4px 0}
  .mtx input.rate{width:120px;margin-left:0}
}
