:root{
  /* --- Design-token reskin (see project brief) ---
     Old names kept (avoids touching every selector in this file / ui.rs
     class references) but VALUES now come from the approved palette:
       --bg/--surface/--surface-2/--border  -> --bg/--surface/--surface-2/--border
       --text/--muted/--faint                -> --text/--text-dim/--text-faint
       --primary/--primary-d/--primary-l     -> --accent/--accent-strong/--accent-soft
       --amber/--amber-l  -> --warn/--warn-soft   --red/--red-l -> --danger/--danger-soft
       --cyan/--cyan-l    -> --info/--info-soft   --slate/--slate-l -> --text-dim/--chip-bg
     --green/--purple/--ink have no equivalent in the new token set (the
     approved palette only defines accent/warn/danger/info) — approximated
     in the same hue family/contrast so status chips still read distinctly. */
  --bg:#F4F5F7; --surface:#FFFFFF; --surface-2:#ECEEF2; --border:#DBDFE6; --border-2:#C7CCD6;
  /* --faint затемнён (#8990A0 -> #64697A): использовался на обычном тексте
     11-11.5px (заголовки таблиц, подписи форм/KPI) при контрасте ~2.9-3.2:1
     к --surface/--bg — ниже WCAG AA (4.5:1). Тёмная тема уже проходила
     (~6.15:1), её не трогаем. */
  --text:#161B26; --muted:#5B6472; --faint:#64697A;
  --primary:#28406E; --primary-d:#1B2C4D; --primary-l:#E4E9F3;
  --green:#2F7A4D; --green-l:#E3F1E7; --amber:#95620B; --amber-l:#F7EEDD;
  --red:#9C3B33; --red-l:#F6E7E4; --purple:#5B4A9C; --purple-l:#EBE7F5;
  --cyan:#2E5F8A; --cyan-l:#E4EDF4; --slate:#5B6472; --slate-l:#EDEFF3;
  --ink:#161B26;
  /* Рамка при наведении (кнопки, поле выбора файла) и рамка «мягко опасного»
     действия. Токены, а не литералы: раньше здесь стояли светлые #b6c2d4 и
     #f1c6c6 — на тёмной теме они давали рамку СВЕТЛЕЕ основного акцента,
     то есть наведение выглядело ярче нажатия. */
  --border-h:#B6C2D4; --red-b:#E0AEA8;
  /* Затемнение вокруг подсвеченного блока в интерактивном обучении
     (.tour-hole, см. tour.js). Токен, а не литерал: в тёмной теме тот же
     светлый scrim давал бы серую вуаль вместо затемнения. */
  --tour-scrim:rgba(15,23,42,.55);
  --radius:10px; --radius-sm:7px; --radius-xs:5px;
  --shadow:0 1px 2px rgba(16,24,40,.04),0 4px 14px -8px rgba(16,24,40,.10);
  --shadow-md:0 4px 12px rgba(16,24,40,.10);
  --shadow-lg:0 8px 22px -12px rgba(16,24,40,.24);
}
/* Тёмная тема — ТОЛЬКО по явному выбору пользователя (см. .theme-toggle в
   app.js — ставит data-theme на <html>, хранит выбор в localStorage).
   Раньше тут же был @media(prefers-color-scheme:dark), автоматически
   включавший тёмную по системной теме ОС/браузера — убран по прямой
   просьбе пользователя: базовая тема продукта — светлая, а не «следует за
   системой», тёмная — только когда её выбрали явно тумблером. */
:root[data-theme="dark"]{
  --bg:#12161D; --surface:#1E2531; --surface-2:#293244; --border:#4D5A78; --border-2:#5C6A8A;
  --text:#FBFCFE; --muted:#CAD1E0; --faint:#9BA4B8;
  --primary:#AEC2F1; --primary-d:#DAE3FA; --primary-l:#35446C;
  --green:#8FD9A8; --green-l:#1E3A28; --amber:#EEC17E; --amber-l:#4A3714;
  --red:#EB9F98; --red-l:#4A2523; --purple:#C7BEEF; --purple-l:#332B54;
  --cyan:#9BC5F1; --cyan-l:#263B54; --slate:#CAD1E0; --slate-l:#303A4D;
  --border-h:#7B8AAD; --red-b:#7A3F39;
  --tour-scrim:rgba(2,6,16,.72);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{font-family:'Manrope',-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.45;-webkit-font-smoothing:antialiased}
a{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
h1,h2,h3{margin:0}
.mono{font-family:"IBM Plex Mono","SF Mono",Consolas,"Roboto Mono",monospace;font-variant-numeric:tabular-nums}
.muted{color:var(--muted)}
.small{font-size:12.5px}
.tabnum{font-variant-numeric:tabular-nums;font-family:"IBM Plex Mono",monospace}

/* ---------- Числа: неразрывность (правило проекта) ----------
   Значащее число (сумма, счётчик, срок, SLA, процент, дата) никогда не
   переносится на следующую строку и не отрывается от своей единицы измерения.
   Три эшелона:
     1) money() в ui.rs склеивает разряды и «₽» через U+00A0 — работает даже
        там, где класс не проставлен (тосты, баннеры, print.rs);
     2) правила ниже — nowrap на самих носителях чисел;
     3) .kpi .val + min-width плиток — чтобы nowrap не приводил к обрезке.
   .nw — точечная утилита для пар «число + слово» («SLA 24ч», «3 из 5»). */
.nw{white-space:nowrap}
.num,.mono,.tabnum,
.due,
.tab .cnt,.nav-badge,.plan-note,
.log-ts,.feed .when{white-space:nowrap;font-variant-numeric:tabular-nums}
/* Обратный клапан: длинный неразрывный токен (URL приглашения в .mono-ячейке,
   ключ объекта в аудите) должен уметь перенестись — иначе он растягивает
   таблицу до бесконечности. */
.mono.brk,.num.brk{white-space:normal;overflow-wrap:anywhere}
/* Иконка спрайта (замена эмодзи) — размер и цвет наследуются от текста вокруг. */
.ico{width:1em;height:1em;display:inline-block;vertical-align:-0.15em;flex-shrink:0}
/* Точка вкл/выкл (замена 🟢/🔴) — простой цветной индикатор, не значок. */
.dot-on,.dot-off{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:2px}
.dot-on{background:var(--green)}
.dot-off{background:var(--red)}
/* Цвет беджа этапа (задача 95, /admin/workflow) — свободно выбираемый
   произвольный цвет (`<input type=color>` в редакторе этапа, тот же
   контрол, что и у цвета страховой), не токен темы: в узких местах
   (ячейка номера в таблице этапов, акцент полосы этапа карточки
   направления) рендерится маленьким кружком-свотчем тем же приёмом, что
   .dot-on/.dot-off, а не заливкой всего блока — контраст текста тогда
   считать не нужно вовсе. Сам цвет — через inline style на элементе. */
.color-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;vertical-align:middle;flex:none}
/* ---------- Текст поверх насыщенных заливок ----------
   Единое правило проекта (заведено полосой этапов, см. .sb-cur ниже):
   на заливке --primary/--primary-d/--green/--red текст пишется цветом
   var(--bg), а НЕ #fff. Токены заливок в тёмной теме инвертируются
   (--primary #28406E → #AEC2F1, --red #9C3B33 → #EB9F98, --primary-d
   #1B2C4D → #DAE3FA), а захардкоженный белый — нет: кнопка «Войти»,
   аватар и бейдж колокольчика давали контраст 1.15–1.9:1 и были
   нечитаемы. --bg инвертируется вместе с темой, поэтому контраст
   верный в обеих. */
/* Skip-link: невидим, пока не получит фокус (Tab с самого начала страницы) —
   даёт обойти боковую навигацию и сразу попасть в контент. */
.skip-link{position:absolute;top:-40px;left:8px;z-index:100;background:var(--primary);color:var(--bg);padding:8px 14px;border-radius:6px;font-size:13px;font-weight:600}
.skip-link:focus{top:8px}
main:focus{outline:none}

/* ---------- Topbar (единая шапка страницы — заголовок+действия в один
   ряд; логотип бренда переехал в .sidebar .brand, см. ниже) ---------- */
.topbar{position:sticky;top:0;z-index:51;display:flex;align-items:center;gap:16px;min-height:64px;padding:12px 22px;background:var(--bg);border-bottom:1px solid var(--border)}
.page-title{font-size:19px;font-weight:800;letter-spacing:-.01em}
/* .page-head раньше был flex:0 1 auto (наследие обычной страницы, где он
   лежит в потоке .wrap и растягивается как блочный элемент) — внутри
   .topbar это же flex:0 1 auto делает его flex-item'ом БЕЗ роста: он
   съёживается по контенту (title+кнопки) и виснет сразу после бургера,
   а весь остаток ширины шапки съедает .spacer, который следует ПОСЛЕ него.
   В итоге justify-content:space-between внутри .page-head бесполезен — ему
   некуда распределять пространство, оно всё уже отдано соседу — и кнопка
   #dash-settings-btn (и любая другая кнопка справа page-head) виснет
   вплотную к заголовку, а не у правого края страницы, как должно быть
   независимо от длины title. Даём .page-head расти (flex:1 1 auto) — он
   занимает всё место ДО .tenant, и внутренний space-between наконец
   прижимает правый блок кнопок к правому краю (или к .tenant, если он есть
   на странице). .spacer тогда не нужен именно на страницах с page-head —
   гасим его рост только когда он идёт СРАЗУ за .page-head (сосед по
   разметке — см. nav_shell: {page_head}<div class='spacer'></div>), чтобы
   не трогать страницы без page-head (там заголовок — просто .page-title, и
   .spacer по-прежнему должен расти, чтобы прижать .tenant вправо). */
.topbar .page-head{margin-bottom:0;flex:1 1 auto;min-width:0}
.topbar .page-head h1{font-size:19px}
.topbar .spacer{flex:1}
.topbar .page-head + .spacer{flex:0 0 auto}
.topbar .tenant{display:flex;align-items:center;gap:8px;background:var(--surface-2);border:1px solid var(--border);padding:5px 12px;border-radius:999px;font-size:12.5px;font-weight:700}
.topbar .tenant .dot{width:7px;height:7px;border-radius:50%;background:var(--cyan)}
/* ---------- Меню пользователя — теперь в подвале сайдбара, а не в шапке
   (<details id='user-menu'>) — колокольчик топбара убран целиком (дублировал
   бейдж «Ленты событий» в меню, см. ui.rs::nav_shell), а бейдж аккаунта
   переехал в самый низ боковой панели. Раскрытие — нативное (details),
   поэтому работает и без JS; app.js только добавляет Esc/клик-вне/стрелки.
   Маркер треугольника гасим двумя способами: ::marker для стандартных
   браузеров и ::-webkit-details-marker для Safari. */
.user-menu{position:relative}
.user-menu>summary{display:flex;align-items:center;gap:9px;width:100%;padding:7px 8px;
  border:1px solid transparent;border-radius:var(--radius-xs);cursor:pointer;list-style:none;
  color:var(--text);font-size:13px;font-weight:600}
.user-menu>summary::-webkit-details-marker{display:none}
.user-menu>summary::marker{content:''}
.user-menu>summary:hover,.user-menu[open]>summary{background:var(--surface-2);border-color:var(--border)}
.user-menu>summary:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.user-menu .user-name{max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.user-menu .chev{display:inline-flex;margin-left:auto;color:var(--faint);transition:transform .15s}
.user-menu .chev .ico{width:14px;height:14px}
.user-menu[open] .chev{transform:rotate(180deg)}
/* Панель прижата к низу вьюпорта — список открывается ВВЕРХ (bottom:100%),
   иначе он бы норовил уйти за нижний край экрана. Левый край, не правый:
   якорь (summary) занимает всю ширину узкой панели, а не висит справа. */
.user-menu-list{position:absolute;left:0;right:auto;bottom:calc(100% + 8px);top:auto;z-index:5;min-width:240px;padding:6px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg)}
.user-menu-head{padding:6px 10px 10px;margin-bottom:6px;border-bottom:1px solid var(--border)}
.user-menu-name{font-size:13px;font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.user-menu-sub{font-size:11.5px;color:var(--faint);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.user-menu-head .role-chip{margin-top:8px}
.user-menu-item,.settings-link{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--radius-xs);color:var(--text);font-size:13.5px;font-weight:550}
.user-menu-item:hover,.user-menu-item:focus-visible,.settings-link:hover,.settings-link:focus-visible{background:var(--slate-l);color:var(--text);text-decoration:none}
.user-menu-item .ico,.settings-link .ico{width:16px;height:16px;color:var(--muted);flex:none}
.user-menu-item.danger,.settings-link.danger,.user-menu-item.danger .ico,.settings-link.danger .ico{color:var(--red)}
.user-menu-item.danger:hover,.settings-link.danger:hover{background:var(--red-l);color:var(--red)}
.user-menu-sep{height:1px;background:var(--border);margin:6px 4px}
.avatar{position:relative;width:32px;height:32px;border-radius:var(--radius-xs);background:var(--primary-d);display:grid;place-items:center;font-size:12px;font-weight:700;color:var(--bg);text-transform:uppercase}
/* Точка на аватаре — тариф истекает/просрочен (см. ui.rs::nav_shell, urgent).
   Обводка цветом сайдбара — визуально «вырезает» точку из аватара, тот же
   приём, что у бейджей на иконках (см. .nav-burger-badge). */
.avatar-alert{position:absolute;top:-2px;right:-2px;width:10px;height:10px;border-radius:50%;
  background:var(--red);border:2px solid var(--surface)}

/* ---------- Layout ---------- */
.wrap{padding:22px 26px 40px;container:card / inline-size}
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:16px;flex-wrap:wrap}
.page-head h1{font-size:21px;font-weight:700}
.toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.card-head .toolbar{margin-bottom:0}

/* ---------- Переполнение командной панели (app.js::initToolbarOverflow) ----------
   Панель, отмеченная [data-overflow-bar], не переносит элементы на вторую
   строку (в отличие от обычного .toolbar) — то, что не поместилось по
   ширине, JS уводит под кнопку «Ещё» в поповер рядом (полным списком,
   вертикально, с подписями — те же кнопки, что были бы видны на широком
   экране). Поповер — СОСЕД панели (общая обёртка .toolbar-overflow-wrap), а
   не её потомок: overflow:hidden на самой панели (без него хвост урезанных
   элементов на миг мелькал бы перед тем, как JS их спрячет) иначе обрезал
   бы и поповер тоже. */
.toolbar-overflow-wrap{position:relative}
.toolbar-overflow-wrap[hidden]{display:none}
[data-overflow-bar]{flex-wrap:nowrap;overflow:hidden;margin-bottom:0}
.toolbar-more{flex:none}
.toolbar-more-pop{position:absolute;top:calc(100% + 6px);right:0;z-index:15;
  display:flex;flex-direction:column;gap:4px;min-width:230px;max-width:min(340px,90vw);
  padding:8px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);box-shadow:var(--shadow-lg)}
.toolbar-more-pop>*{width:100%}
.toolbar-more-pop>.btn,.toolbar-more-pop>form>.btn{width:100%;justify-content:flex-start}
.toolbar-more-pop>form{display:flex;width:100%}
.toolbar-more-pop .wf-bulk-actions{display:flex;flex-direction:column;gap:6px;width:100%}
/* Карточка направления: смета и реквизиты.

   ПО УМОЛЧАНИЮ — одна колонка, и реквизиты СВЕРХУ. Смете нужно ~820px
   (семь колонок, см. .table-scroll .est), и пока она этого не получает, она
   листается вбок внутри вкладки — то есть таблицу приходится читать в
   форточку. Реквизиты в этом случае уезжают наверх, а не вниз: под широкой
   таблицей их пришлось бы искать прокруткой, а сверху они всегда на виду и
   отдают смете всю ширину под собой.

   Две колонки включаются, ТОЛЬКО если левой реально хватит на 820px:
   (W - 16) * 1.6/2.6 >= 820  =>  W >= 1348. Порог считается от контейнера, а
   не от окна, поэтому раскладка сама перестраивается, когда пользователь
   сворачивает боковую панель и места становится больше.

   Container query как ДОБАВКА к одноколоночной раскладке, а не наоборот:
   браузер без её поддержки покажет одну колонку — она полностью рабочая. */
.grid-2{display:grid;grid-template-columns:1fr;gap:16px;align-items:start}
/* min-width:0 обязателен: по умолчанию у grid-элемента min-width:auto, то есть
   колонка не может стать уже своего содержимого. Внутри левой лежит таблица
   сметы с min-width:820px, и колонка растягивалась до 850px — на телефоне по
   горизонтали ехала ВСЯ страница вместо .table-scroll внутри карточки. */
.grid-2>*{min-width:0}
/* Реквизиты (второй элемент) наверх, пока раскладка одноколоночная. */
.grid-2>*:last-child{order:-1}
@container card (min-width:1348px){
  .grid-2{grid-template-columns:1.6fr 1fr}
  .grid-2>*:last-child{order:0}
}

/* ---------- Card ---------- */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}
.card+.card{margin-top:16px}
.card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:16px 18px 12px;border-bottom:1px solid var(--border)}
.card-head h2{font-size:14px;font-weight:650}
.card-body{padding:16px 18px}
.card-body.tight{padding:12px 14px}

/* ---------- Buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--border-2);background:var(--surface);color:var(--text);border-radius:var(--radius-sm);padding:8px 14px;font-size:13.5px;font-weight:550;cursor:pointer;transition:.12s;white-space:nowrap;font-family:inherit}
.btn:hover{background:var(--surface-2);border-color:var(--border-h);text-decoration:none}
.btn:active{transform:translateY(1px)}
.btn.primary{background:var(--primary);border-color:var(--primary);color:var(--bg)}
.btn.primary:hover{background:var(--primary-d);border-color:var(--primary-d)}
.btn.success{background:var(--green);border-color:var(--green);color:var(--bg)}
.btn.success:hover{filter:brightness(.95)}
.btn.ghost{background:transparent;border-color:transparent;color:var(--muted)}
.btn.ghost:hover{background:var(--slate-l);color:var(--text)}
.btn.sm{padding:5px 11px;font-size:12px;font-weight:700;color:var(--muted)}
.btn.sm:hover{border-color:var(--primary);color:var(--primary-d)}
/* .btn.sm задаёт color ПОСЛЕ .btn.primary/.success/.danger и с той же
   специфичностью — на заливке это давало приглушённый текст поверх цветного
   фона, а в тёмной теме (где --primary светлый, а --muted тоже светлый)
   кнопка читалась почти никак. Возвращаем цвет текста НА ЗАЛИВКЕ: в проекте
   это var(--bg), а не #fff — токены инвертируются вместе с темой. */
.btn.sm.primary,.btn.sm.success,.btn.sm.danger,
.btn.sm.primary:hover,.btn.sm.success:hover,.btn.sm.danger:hover{color:var(--bg)}
.btn.icon{padding:6px 9px}
.btn.danger-soft{color:var(--red);border-color:var(--red-b);background:var(--surface)}
.btn.danger-soft:hover{background:var(--red-l)}
/* Заливка (не -soft) — для НЕОБРАТИМЫХ действий вне списка (сброс всех
   сессий, отзыв доступа): визуальная иерархия опасности должна совпадать
   с последствиями. Раньше класс не был определён вовсе — самая опасная
   кнопка в админке («Сбросить все сессии») выглядела как обычная серая. */
.btn.danger{background:var(--red);border-color:var(--red);color:var(--bg)}
.btn.danger:hover{filter:brightness(.9)}
.btn:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}
/* Более узкий случай disabled — «идёт отправка» (см. блокировку двойного
   сабмита в app.js): та же .btn:disabled-специфичность (класс+псевдокласс
   у одной, класс+класс у другой) — держим правило ПОСЛЕ .btn:disabled по
   каскаду, иначе оно окажется молча перекрыто (проверено ревью волны 1.5:
   изначально это правило стояло раньше и не применялось вовсе). */
.btn.is-submitting{cursor:wait;opacity:.6}
.btn-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}

/* ---------- Inputs ---------- */
.input,select.input,input[type=text],input[type=password],input[type=number]{appearance:none;border:1px solid var(--border-2);background:var(--surface);border-radius:var(--radius-sm);padding:8px 11px;font-size:13.5px;font-family:inherit;color:var(--text);transition:.12s}
/* Поле выбора обязано ВЫГЛЯДЕТЬ полем выбора. appearance:none выше снимает
   стрелку вместе с нативным оформлением, и <select> становился неотличим от
   <input> — нажать на него догадывались не все. Рисуем шеврон фоном (data-URI,
   а не <svg> в разметке: селектов на странице десятки) и оставляем справа
   место под него. currentColor в data-URI не работает — цвет задан явно и
   приглушён, чтобы годиться и в светлой, и в тёмной теме.
   То же и для input со списком (list=…): выбор там тоже есть. */
select,select.input,input[list]{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23808A9B' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 9px center;background-size:14px;
  padding-right:30px}
/* Нативная стрелка Chrome у input[list] встала бы рядом с нашей. */
input[list]::-webkit-calendar-picker-indicator{display:none!important}
select:disabled,input[list]:disabled{background-image:none}
/* Мультиселект и «размер > 1» — это список, а не выпадашка: шеврон там лжёт. */
select[multiple],select[size]:not([size="1"]){background-image:none;padding-right:11px}
.input:focus,input:focus,select:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-l)}
.field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}
.field label{font-size:12.5px;font-weight:600;color:var(--slate)}

/* ---------- Единый компонент формы (разметку собирает ui.rs::field_html
   и обёртки text_field/select_field/textarea_field) ----------
   Раньше каждая форма размечалась по-своему: «<label>Текст<input class='input'>
   </label>» подряд во flex-column (профиль, уведомления) либо
   «<div><label class='muted small'>…</label><br><input></div>» во flex-wrap
   (все админские «Добавить …»). В первом случае подпись и контрол оказывались
   в ОДНОЙ строке и каждое поле начиналось на своём X; во втором вёрстку держал
   <br>, а ширину — индивидуальный style= на каждом контроле. Ниже —
   единственное место, где решается, как выглядит и выравнивается поле.
   Ширина 100% намеренно ограничена потомками .form-field: глобальный
   .input{width:100%} сломал бы .search input и микроформы в ячейках таблиц. */
.form{display:flex;flex-direction:column;gap:18px}
.form>.card+.card{margin-top:0}
.form-section+.form-section{margin-top:18px;padding-top:16px;border-top:1px solid var(--border)}
.form-section-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--faint);margin:0 0 10px}
.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px 18px;align-items:start}
.form-grid.cols-1{grid-template-columns:1fr}
.form-grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.form-grid.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.form-field{display:flex;flex-direction:column;gap:6px;min-width:0}
.form-field>.form-label{font-size:12px;font-weight:650;color:var(--muted);line-height:1.3}
.form-label .req{color:var(--red);margin-left:2px}
.form-field>.input{width:100%;min-width:0}
.form-hint{font-size:11.5px;color:var(--faint);line-height:1.35}
.form-error{font-size:11.5px;font-weight:650;color:var(--red)}
.form-field.f-invalid>.input{border-color:var(--red);box-shadow:0 0 0 3px var(--red-l)}
.form-field.f-span2{grid-column:1/-1}
.form-field.f-num>.input{max-width:160px}
.form-field.f-sm>.input{max-width:220px}
.form-field.f-color>.input{width:76px;height:38px;padding:3px;cursor:pointer}
/* Инлайн-форма смены цвета страховой на /admin/providers — бедж + пикер +
   кнопка «Сохранить» в одну строку под основными данными провайдера. */
.prov-color-form{display:flex;align-items:center;gap:6px;margin-top:6px}
.prov-color-form input[type=color]{width:32px;height:28px;padding:2px;cursor:pointer;border:1px solid var(--border);border-radius:var(--radius-sm)}
/* display:contents — дети остаются участниками сетки родителя, но
   el.style.display='none' (app.js) по-прежнему прячет группу целиком. */
.form-fieldset{display:contents}
.form-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:18px;padding-top:14px;border-top:1px solid var(--border)}
.form-actions.end{justify-content:flex-end}
.form-actions.bare{border-top:none;padding-top:0}
/* Компактная форма в строку — фильтры, «Добавить …», формы в ячейках таблиц.
   Заменяет 7 дословных копий style='display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end'. */
.form-inline{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px}
.form-inline .form-field{flex:0 1 auto;min-width:160px}
.form-inline .form-field.f-grow{flex:1 1 240px;min-width:200px}
.form-inline .form-field.f-narrow{flex:none;width:110px;min-width:0}
.form-inline .form-field.f-num>.input,.form-inline .form-field.f-sm>.input{max-width:none}
.form-inline>.btn{flex:none}
textarea.input{min-height:70px;resize:vertical;line-height:1.45}
.input[type=color]{padding:3px;height:38px;width:76px;cursor:pointer}
@media(max-width:680px){
  .form-grid,.form-grid.cols-2,.form-grid.cols-3{grid-template-columns:1fr}
  .form-field.f-num>.input,.form-field.f-sm>.input{max-width:none}
  .form-inline{flex-direction:column;align-items:stretch}
  .form-inline .form-field,.form-inline .form-field.f-narrow,.form-inline .form-field.f-grow{width:auto;min-width:0;flex:none}
  .form-inline>.btn{width:100%;justify-content:center}
  .form-actions>.btn{width:100%;justify-content:center}
}

.search{position:relative}
.search input{padding-left:34px;min-width:240px}
.search .ico{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--faint)}

/* Сегментированный переключатель «Все/Мои/Просрочено» в шапке карточки
   (и «Схема/Таблица» на /admin/workflow — тот же вид, но кнопки, не ссылки). */
.tab-row{display:flex;gap:4px;background:var(--slate-l);padding:3px;border-radius:9px}
/* Счётчик в кружке: сколько направлений окажется на вкладке. Ряд KPI-плиток
   над таблицей показывал ровно эти же числа — один фильтр, показанный
   дважды; плитки сняты, числа переехали к самим вкладкам. */
.tab-row a{display:inline-flex;align-items:center;gap:6px}
.tab-n{min-width:18px;padding:0 5px;border-radius:999px;background:var(--surface-2);
  color:var(--muted);font-size:11px;font-weight:700;line-height:17px;text-align:center}
.tab-row a.on .tab-n{background:var(--primary);color:var(--bg)}
.tab-row a,.tab-row button{border:none;background:transparent;padding:6px 12px;border-radius:7px;font-size:12.5px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}
.tab-row a.on,.tab-row button.on{background:var(--surface);color:var(--text);box-shadow:var(--shadow)}
.tab-row a:hover{text-decoration:none}
.tab-row button:hover{color:var(--text)}
/* Переключатель Схема/Таблица/Справка (/admin/workflow) — те же пилюли
   .tab-row, но иконки без подписи: подпись — title/aria-label кнопки. */
#wf-view-switch button{padding:7px 10px}
#wf-view-switch .ico{width:16px;height:16px}
/* Группа действий в правом углу шапки страницы: [Версии] [Схема/Таблица/
   Справка] — версии ЛЕВЕЕ переключателя режимов. */
.wf-head-actions{display:flex;align-items:center;gap:8px}

/* Срок этапа — три уровня: в пределах нормы / горит (<24ч) / просрочен. */
.due{font-size:12px}
.due.ok{color:var(--muted)}
.due.soon{color:var(--amber);font-weight:700}
.due.over{color:var(--red);font-weight:700}

/* ---------- Chip (единый компонент статуса — раньше .pill/.tag/.badge порознь) ---------- */
.chip{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;font-size:11.5px;font-weight:700;white-space:nowrap}
.chip .dot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.9}
.chip-stage{border-radius:var(--radius-sm);padding:4px 9px}
.chip-slate{background:var(--slate-l);color:var(--slate)}
.chip-amber{background:var(--amber-l);color:var(--amber)}
.chip-green{background:var(--green-l);color:var(--green)}
.chip-red{background:var(--red-l);color:var(--red)}
.chip-blue{background:var(--primary-l);color:var(--primary-d)}
.chip-purple{background:var(--purple-l);color:var(--purple)}
.chip-cyan{background:var(--cyan-l);color:var(--cyan)}
.chip-dark{background:var(--ink);color:#fff}
/* Бедж страховой (реестр /admin/providers) — цвет произвольный, задан
   человеком через <input type=color>, поэтому background/color приходят
   инлайн-стилем из ui::insurer_badge(), а не через модификатор chip-*
   (см. app.css «Единый компонент формы» рядом — там же f-color). Этот
   класс только про геометрию/обрезку длинного названия. */
.chip-insurer{margin-right:6px;max-width:120px;overflow:hidden;text-overflow:ellipsis}
.flags{display:flex;gap:6px}
.flag{width:24px;height:24px;border-radius:7px;display:grid;place-items:center;font-size:13px;background:var(--slate-l)}
.flag.on{background:var(--primary-l)}
.flag.warn{background:var(--amber-l)}
.flag.over{background:var(--red-l)}

/* ---------- Table ---------- */
.tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:13.3px}
.tbl th{text-align:left;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--faint);padding:11px 18px;border-bottom:1px solid var(--border);white-space:nowrap}
.tbl td{padding:13px 18px;border-bottom:1px solid var(--border);vertical-align:middle}
/* Последняя колонка почти везде — действия (иконки/кнопки «Изменить»,
   «Удалить»...) и визуально требует больше воздуха до края таблицы/карточки,
   чем обычный текстовый столбец — иначе кнопка читается как забытый отступ.
   Специфичнее любого табличного варианта padding (.wfe-tbl и т.п.) —
   действует поверх него, см. комментарий у .wfe-tbl th,.wfe-tbl td ниже. */
.tbl th:last-child,.tbl td:last-child{padding-right:24px}
/* table-layout остаётся auto (нужен для .table-scroll с overflow-x), но при
   width:100% браузер распределяет ЛЮБОЙ свободный остаток пропорционально
   ВСЕМ колонкам — включая узкие иконка/бейдж/дата, которым лишнее место не
   нужно и только раздвигает соседние ячейки (напр. /notifications: пустая
   колонка-иконка перед беджем «прочитано»). width:1% — стандартный приём
   «не расти сверх контента» для auto-layout таблиц; текстовая колонка
   (обычно с min-width:0) забирает освободившееся место вместо этого. */
.tbl th.nw,.tbl td.nw{width:1%}
/* Рука-курсор и подсветка — ТОЛЬКО там, где строка действительно открывает
   карточку (журнал: <tr data-href>, см. app.js). Раньше правило висело на
   всех .tbl разом, и в /admin/outbox, /admin/audit, /admin/incidents,
   /admin/tenants, /admin/providers, /admin/users курсор обещал переход,
   которого нет ни по клику, ни по Enter. */
.tbl tbody tr{transition:background .1s}
.tbl tbody tr[data-href]{cursor:pointer}
.tbl tbody tr[data-href]:hover{background:var(--surface-2)}
/* Чекбокс «обязателен» (с подписью, не голый квадратик) над полем названия
   статуса у конкретной страховой — ячейка матрицы «Обязательные статусы»
   (/admin/providers). Раньше это был один неподписанный чекбокс, а после
   первой попытки добавить название — тесное поле с обрезанным плейсхолдером
   рядом, которое легко принять за задизейбленное. Теперь чекбокс и поле
   стоят друг под другом, поле — на всю ширину ячейки. */
.chk-line{display:inline-flex;align-items:center;gap:6px;cursor:pointer}
/* Двойной клик по строке открывает её модалку правки — тот же контрол,
   что и явная иконка-карандаш в ячейке (см. app.js: делегированный
   dblclick, data-row-edit). Курсор — тот же сигнал, что и у кликабельных
   строк журнала выше, чтобы обещание было согласовано с поведением. */
.tbl tbody tr:has([data-row-edit]){cursor:pointer}
.tbl tbody tr:has([data-row-edit]):hover{background:var(--surface-2)}
.notes li:has([data-row-edit]){cursor:pointer}
/* Несколько иконок-действий в одной ячейке строки — в столбик на узкой
   колонке, без обёртки-карточки. */
.row-act{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.tbl tbody tr:last-child td{border-bottom:none}
.dot-new{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--primary);margin-right:6px;vertical-align:middle}
/* Заголовок-разделитель фазы в таблице этапов (/admin/workflow, докс
   «группировка 31 этапа по фазам») — не строка данных. Управление группой
   (переименовать фазу СРАЗУ у всех её этапов, добавить/удалить группу) —
   кнопки с краю той же строки, поэтому внутри ячейки flex: имя слева,
   кнопки справа. display:flex — на ОБЁРТКЕ .phase-row-inner, не на самой
   <td>: flex на td с colspan под table-layout:fixed ломает расчёт ширины
   colspan-ячейки в некоторых браузерах (сводит её к ширине первой
   охватываемой колонки вместо суммы всех) — сама <td> остаётся обычной
   table-cell, оборачивающий div ничего в геометрии таблицы не решает. */
.tbl tr.phase-row td{background:var(--surface-2);padding:0}
.phase-row-inner{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--faint);padding:6px 14px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.phase-row-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Кнопка наследует внешний вид заголовка (капс/трекинг) по умолчанию — тут
   явно возвращаем нормальный текст, иначе «Переименовать» читается криком. */
.phase-row .btn{text-transform:none;letter-spacing:normal;font-weight:500;flex:none}
/* Заголовок с сортировкой: ссылка внутри <th aria-sort=…> (см. Pager::sort_th). */
.th-sort a{display:inline-flex;align-items:center;gap:4px}
.th-sort:hover a{color:var(--primary)}
.tbl .num{text-align:right;font-variant-numeric:tabular-nums;font-family:"IBM Plex Mono",monospace}
.veh{font-weight:600}
.veh small{display:block;font-weight:400;color:var(--muted);font-size:12px}

/* estimate editable table */
.est{width:100%;border-collapse:collapse;font-size:13.5px}
.est th{text-align:left;font-size:11.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.03em;padding:8px 10px;border-bottom:1px solid var(--border)}
.est td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:middle}
.est tfoot td{border-top:2px solid var(--border);border-bottom:none;font-weight:700}
.est .num{text-align:right;font-variant-numeric:tabular-nums;font-family:"IBM Plex Mono",monospace}
.est .kindtag{font-size:11px;padding:1px 7px;border-radius:5px;background:var(--slate-l);color:var(--slate);white-space:nowrap}
.est .kindtag.part{background:var(--cyan-l);color:var(--cyan)}
.est tr.disagreement{background:var(--amber-l)}

/* --- Инлайн-правка: ячейка И ЕСТЬ поле ввода ---------------------------
   Отдельного режима «просмотр/правка» нет — поле стилизовано под текст и
   проявляет рамку на hover/focus. Отсюда бесплатно берутся Tab (нативный
   переход), Enter (implicit submit формы строки) и работа без JS. */
.est .ed{width:100%;min-width:0;font:inherit;color:inherit;background:transparent;
  border:1px solid transparent;border-radius:var(--radius-xs);padding:3px 6px;margin:-3px -6px;appearance:none}
.est .ed:hover{border-color:var(--border);background:var(--surface)}
.est .ed:focus{outline:none;border-color:var(--primary);background:var(--surface);box-shadow:0 0 0 3px var(--primary-l)}
.est .ed::placeholder{color:var(--faint)}
.est td.num .ed{text-align:right;font-variant-numeric:tabular-nums;font-family:"IBM Plex Mono",monospace}
.est .cell-art .ed{padding:1px 6px;margin:-1px -6px}
/* Спиннеры <input type=number> съедают половину узкой колонки */
.est .ed[type=number]::-webkit-outer-spin-button,
.est .ed[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.est .ed[type=number]{-moz-appearance:textfield;appearance:textfield}
.est .cell-note{display:block;color:var(--amber);font-size:11.5px;margin-top:2px}

/* Состояние строки. Маркер — левая граница первой ячейки, а не фон и не
   box-shadow: у .est border-collapse:collapse, тень на <tr> в этом режиме
   рисуется браузерами по-разному, а фон конфликтовал бы с .disagreement. */
.est tbody tr[data-row] td:first-child{border-left:3px solid transparent}
.est tbody tr.is-dirty  td:first-child{border-left-color:var(--amber)}
.est tbody tr.is-saving td:first-child{border-left-color:var(--faint)}
.est tbody tr.is-saved  td:first-child{border-left-color:var(--green)}
.est tbody tr.is-error  td:first-child{border-left-color:var(--red)}
.est tbody tr.is-saving{opacity:.6}
.est tbody tr[data-row]:hover{background:var(--surface-2)}
.est tbody tr.disagreement:hover{background:var(--amber-l)}
/* Тот же паттерн, что у строки журнала (.tbl tbody tr[data-href]) — курсор
   pointer только там, где клик по строке действительно что-то открывает.
   Позиция сметы редактируется в модалке (см. .row-edit/app.js), поэтому
   курсор появляется только пока смета редактируема — иначе он обещал бы
   клик, которого после согласования уже нет. */
.est tbody tr[data-row]:has(.row-edit){cursor:pointer}
.row-state{display:inline-block;min-width:78px;text-align:right;font-size:11px;font-weight:700;color:var(--faint);white-space:nowrap}
.est tr.is-dirty .row-state{color:var(--amber)}
.est tr.is-saved .row-state{color:var(--green)}
.est tr.is-error .row-state{color:var(--red)}
/* ✓ показываем, только когда есть что сохранять (или строка в фокусе) —
   иначе в каждой строке висят две одинаково важные иконки. */
.est .row-save{visibility:hidden}
.est tr.is-dirty .row-save,.est tr:focus-within .row-save{visibility:visible}
/* display:flex НЕ на самой <td> (ломает вычисление ширины/высоты строки
   таблицы у некоторых браузеров под auto table-layout — тот же класс бага,
   что раньше чинился у workflow-table.js), а на внутреннем div — <td>
   остаётся обычной table-cell. */
.est td.c-act{white-space:nowrap}
.est .c-act-inner{display:flex;gap:4px;justify-content:flex-end;align-items:center}
.est td.c-act form{display:inline;margin:0}
#est-total.is-preview{color:var(--amber)}

/* Строка добавления — часть таблицы, а не форма-простыня под ней. */
.est-add td{background:var(--surface-2)}
.est-add .ed{border-color:var(--border);background:var(--surface)}
.est-add .addsub{display:flex;gap:10px;margin-top:6px}
.est-add .addrow{display:inline-flex;margin:0;padding:0;border:none;gap:6px}

/* Заголовок группы при включённой группировке позиций по разделу
   (est-toolbar, тумблер «Группировка по разделам» — ui.rs::estimate_tab). */
.est-section-row td{background:var(--surface-2);font-weight:700;font-size:11.5px;
  text-transform:uppercase;letter-spacing:.03em;color:var(--muted);padding:6px 10px;
  border-bottom:1px solid var(--border)}

/* ---------- Tabs ---------- */
.tabs{display:flex;gap:4px;padding:0 6px;border-bottom:1px solid var(--border);background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;overflow:auto}
.tab{appearance:none;border:none;background:none;padding:13px 14px;font-size:13.5px;font-weight:600;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;display:flex;align-items:center;gap:7px;font-family:inherit;white-space:nowrap}
.tab:hover{color:var(--text)}
.tab.active{color:var(--primary-d);border-bottom-color:var(--primary)}
.tab .cnt{background:var(--slate-l);color:var(--slate);border-radius:999px;font-size:11px;padding:0 7px;font-weight:700}
.tab.active .cnt{background:var(--primary-l);color:var(--primary-d)}
/* Счётчик «требуется действие» (невыполненные условия перехода во вкладке
   «Что дальше») — единственный, который значит «надо что-то сделать», а не
   «столько всего накопилось». Идёт после .tab.active .cnt, поэтому
   побеждает в обоих состояниях вкладки. */
.tab .cnt.cnt-warn{background:var(--red-l);color:var(--red)}
.tab-panel{display:none}
.tab-panel.active{display:block}

/* ---------- Info grid ---------- */
/* minmax(0,1fr), а не 1fr: по умолчанию у колонки грид-трека минимум
   min-content, и длинное значение (VIN, UUID станции) распирало первую
   колонку шире всей карточки — реквизиты вылезали за её край. */
.info{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 22px}
/* Значение переносится, а не растягивает колонку. Числа и суммы от этого не
   страдают: у них своя защита от переноса (.num/.nw/money). */
.info .v{overflow-wrap:anywhere}
.info .k{font-size:11.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.03em}
.info .v{font-size:13.5px;font-weight:550;margin-top:1px}
/* Уточнение под значением: адрес станции, её идентификатор у страховой. */
.info .v-sub{font-size:11.5px;font-weight:400;color:var(--faint);margin-top:1px;overflow-wrap:anywhere}
/* Логотип марки в заголовке карточки направления (ui::detail,
   car_logos::resolve_direction_logo) — намеренно КРУПНЕЕ plate-badge
   (номера, ~33.5px) рядом в том же <h1>: марка — первое, на что смотрят,
   мельче номера она снова терялась бы. Белая подложка без обводки:
   логотипы auto.ru часто тёмный знак на прозрачном фоне (на тёмной теме
   иначе сливались бы с карточкой), а обводка поверх и так контрастного
   белого пятна на тёмном фоне была лишней — просто визуальный шум.
   ФИКСИРОВАННЫЙ width+height (не auto!) с object-fit:contain — сами PNG
   заранее обрезаны по альфа-каналу до плотного bbox (см. скрипт обрезки,
   генерирующий эти файлы), но соотношение сторон у разных марок разное
   (от почти квадратных эмблем до широких тонких текстовых лого) и внутри
   исходного bbox всё ещё бывает мельче контейнера — auto-размер тогда НЕ
   растягивал бы такой логотип, просто оставлял мелким. Фиксированный бокс
   заставляет браузер масштабировать КАЖДЫЙ логотип (вверх или вниз) под
   размер бокса, сохраняя пропорции, — то самое «растянуть на всю область
   картинки», а не только «не даём вылезти за рамки». */
.brand-logo{width:68px;height:44px;object-fit:contain;background:#fff;border-radius:4px;padding:2px;vertical-align:middle;flex:none}
.kpis{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;margin-bottom:16px}
.kpi{flex:1;min-width:150px;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px}
.kpi .lbl{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.03em}
/* размеры .kpi .val — ниже, отскоуплены по контексту (.kpi-row / .kpis) */
.kpi .val{font-weight:700;font-variant-numeric:tabular-nums;font-family:"IBM Plex Mono",monospace}
.kpi.accent .val{color:var(--primary-d)}
.kpi.warn .val{color:var(--amber)}

/* ---------- Вкладка «Что дальше»: гейт перехода ----------
   Раньше здесь жил вертикальный степпер (.steps/.step) на все 31 этап; он
   уехал наверх, в полосу этапов (.stagebar), а вкладка теперь показывает
   чек-лист условий перехода, ветки и триггеры. */
.proc-next{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px 14px;margin-top:10px;background:var(--surface-2)}
.proc-next-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.proc-next-head b{font-size:13.5px}
.proc-checks{list-style:none;margin:4px 0 0;padding:0}
.proc-checks li{display:flex;gap:8px;align-items:flex-start;font-size:12.5px;padding:3px 0;color:var(--muted)}
.proc-checks li .ico{flex:none;margin-top:2px}
.proc-checks li.ok{color:var(--green)}
.proc-checks li.no{color:var(--red);font-weight:650}
.proc-vars{display:flex;gap:6px;flex-wrap:wrap}
/* Пагинация вкладки «История» — стандартная раскладка: количество на
   странице слева, страницы с переходами вперёд/назад справа, одной
   строкой под списком (см. ui.rs::history_tab). */
/* padding — .hist-pager обычно стоит ПОСЛЕДНИМ прямым потомком .card (после
   .table-scroll, БЕЗ обёртки .card-body — см. ui::audit/ui::messages), а у
   .card самого по себе отступов нет (их обычно даёт .card-body) — без
   своего padding текст пагинатора упирался в края карточки. */
.hist-pager{display:flex;justify-content:space-between;align-items:center;
  flex-wrap:wrap;gap:8px;margin-top:10px;padding:0 16px 16px}

/* ---------- Documents gallery ---------- */
.gallery{display:flex;gap:12px;flex-wrap:wrap}
.doc{width:150px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;background:var(--surface);transition:.12s}
.doc:hover{box-shadow:var(--shadow-md);border-color:var(--border-2)}
.doc .thumb{height:96px;background:var(--slate-l);display:grid;place-items:center;overflow:hidden}
.doc .thumb img{width:100%;height:100%;object-fit:cover}
.doc .thumb .file-ico{font-size:34px;opacity:.5}
.doc .meta{padding:8px 10px}
.doc .meta .name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.doc .meta .sub{font-size:11px;color:var(--muted)}
.dropzone{margin-top:14px;border:2px dashed var(--border-2);border-radius:var(--radius-sm);padding:16px;background:var(--surface-2)}
.dropzone .upload-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.file-input{display:flex;align-items:center;gap:8px}
.doc.local{position:relative}
.doc-del{position:absolute;top:4px;right:4px;margin:0}
.doc-del .btn{width:24px;height:24px;padding:0;border-radius:999px;display:grid;place-items:center;background:var(--surface);box-shadow:var(--shadow-md)}
/* Селектор с классом .icon (а не просто .doc-del .btn) — эта кнопка несёт
   ещё и класс .btn.icon, у которого в touch-таргет блоке ниже есть
   ОТДЕЛЬНОЕ, более специфичное правило `.btn.icon{min-width:44px}` (та же
   специфичность 0-2-0, что у .doc-del .btn, но позже по каскаду — значит
   без класса .icon в этом селекторе min-width всё равно перебивался бы
   обратно на 44px, а min-height — нет, там такого доп. правила нет). Тот
   же осознанный отказ от 44px, что и у .topbar .tenant-switch .btn — без
   него кнопка раздувалась на любом тач-устройстве, вылезая квадратом за
   пределы превью документа. */
.doc-del .btn.icon{min-width:24px;min-height:24px}
.upload-queue{margin-top:10px;display:flex;flex-direction:column;gap:6px}
.upload-queue:empty{display:none}
/* Строка очереди загрузки фото. Класс называется .upload-item, а НЕ
   .queue-item: то имя занимала очередь «Мои этапы» в снятом рейле
   журнала (см. ниже) — два разных компонента делили одно имя, побеждал
   поздний по каскаду, и у строк загрузки пропадал горизонтальный padding,
   а текст прилипал к цветной левой полосе. Разметку строит app.js
   (queueFile) — класс менять только вместе с ним. */
.upload-item{display:flex;align-items:center;gap:10px;padding:7px 10px;border:1px solid var(--border);border-left:4px solid var(--amber);border-radius:var(--radius-sm);background:var(--surface);font-size:12.5px}
.upload-item.done{border-left-color:var(--green)}
.upload-item.error{border-left-color:var(--red)}
.upload-item .qi-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.upload-item .qi-status{color:var(--muted);white-space:nowrap}
.upload-item.error .qi-status{color:var(--red)}
.upload-item.done .qi-status{color:var(--green)}
.upload-item .qi-retry{appearance:none;border:none;background:none;color:var(--primary);cursor:pointer;font-size:12px;padding:0;text-decoration:underline}

/* ---------- Feed / history ---------- */
.feed{list-style:none;margin:0;padding:0}
.feed li{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--border);font-size:13px}
.feed li:last-child{border-bottom:none}
.feed .ic{flex:none;width:26px;height:26px;border-radius:7px;display:grid;place-items:center;background:var(--slate-l);font-size:13px}
.feed .ic.note{background:var(--primary-l)}
.feed .ic.appr{background:var(--green-l)}
.feed .ic.stat{background:var(--purple-l)}
.feed .ic.stage{background:var(--primary-l);color:var(--primary-d)}
.feed .when{color:var(--faint);font-size:11.5px}

/* Единая строка ленты активностей: «Ответственный — действие», затем
   описание, затем необязательная мелкая строка (срок/канал/комментарий).
   Раньше у каждого из 4 источников ленты был свой набор полей в своём
   порядке — теперь верстается одним и тем же кодом (см. ui::activity_row). */
.act-line{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.act-verb{color:var(--muted);font-weight:600}
.act-desc{white-space:pre-wrap}
.act-desc:empty{display:none}
/* Пустое состояние списка/таблицы — ЕДИНСТВЕННОЕ объявление (раньше .empty
   был объявлен здесь и ещё раз ниже по файлу с другим выравниванием;
   побеждало второе, а первое только путало при чтении). */
.empty{padding:20px 0;text-align:center;color:var(--faint);font-size:13px}
/* Онбординг нового тенанта (журнал без единого направления) — более
   заметный вариант .empty: заголовок + абзац вместо однострочной подписи. */
.empty.onboarding{text-align:left;max-width:480px;margin:0 auto;padding:28px 16px}
.empty.onboarding b{display:block;color:var(--text);font-size:14px;margin-bottom:6px}
.empty.onboarding p{margin:0;line-height:1.5}

/* ---------- Action bar ---------- */
.actionbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;background:linear-gradient(0deg,var(--surface-2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;box-shadow:var(--shadow)}
.actionbar .stage-now{display:flex;flex-direction:column}
.actionbar .stage-now .lbl{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.03em}
.actionbar .stage-now .name{font-size:15px;font-weight:700}
.actionbar .sep{width:1px;align-self:stretch;background:var(--border)}

/* Отключённая до поры возможность: разметка и маршрут живы, но из
   интерфейса убрана (печатные формы, выгрузка CSV). Возврат — снятие
   класса, а не восстановление удалённого кода. */
.feature-off{display:none!important}

/* ---------- Toast ---------- */
.toast{position:fixed;top:70px;right:22px;z-index:50;max-width:380px;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--amber);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:13px 16px;display:flex;align-items:flex-start;gap:10px;animation:slidein .25s ease}
.toast.warn{border-left-color:var(--amber)}
.toast.ok{border-left-color:var(--green)}
.toast.error{border-left-color:var(--red)}
.toast .ico,.proc-gate .ico{width:18px;height:18px;flex:none;margin-top:1px}
.toast .msg,.proc-gate .msg{font-size:13px;flex:1}
/* Плашка-блокер вкладки «Что дальше» — выглядит как статичный баннер, но НЕ
   .toast: класс .toast на карточке означает одноразовый flash, и на нём
   висят проверки его одноразовости (tools/smoke.sh) и e2e-локаторы. */
.proc-gate{display:flex;align-items:flex-start;gap:10px;margin-bottom:14px;padding:13px 16px;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--green);border-radius:var(--radius-sm)}
.proc-gate.warn{border-left-color:var(--amber)}
/* Постоянные плашки в потоке страницы (отказ СК, разногласия, превышение
   резерва, ожидание согласования) — тот же вид, что у тоста, но без
   fixed-позиционирования, анимации появления и скрытия при печати. Раньше
   каждая из четырёх несла дословную копию inline-стиля
   style='position:static;max-width:none;margin-bottom:14px' и вместе с
   одноразовыми тостами исчезала из распечатки. */
.banner{position:static;max-width:none;margin-bottom:14px;animation:none;box-shadow:none;top:auto;right:auto}
@media print{.banner{display:flex!important}}
.toast-close{appearance:none;border:none;background:none;padding:0;margin-left:4px;font-size:13px;line-height:1;color:var(--faint);cursor:pointer}
.toast-close:hover{color:var(--text)}
@keyframes slidein{from{transform:translateX(20px);opacity:0}to{transform:none;opacity:1}}

/* ---------- Login ---------- */
.login-wrap{min-height:100vh;display:grid;place-items:center;background:radial-gradient(1200px 600px at 50% -10%,#28406E,#1B2C4D)}
.login-card{width:360px;background:var(--surface);border-radius:var(--radius);box-shadow:var(--shadow-lg);padding:28px}
.login-card .brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:18px;margin-bottom:4px}
.login-card .brand .logo{width:44px;height:44px;display:grid;place-items:center;color:var(--primary)}
.logo-svg{width:100%;height:100%;transform:rotate(-8deg)}
.login-card .sub{color:var(--muted);font-size:13px;margin-bottom:20px}
.login-card .btn.primary{width:100%;justify-content:center;padding:11px}
.login-err{background:var(--red-l);color:var(--red);border-radius:var(--radius-sm);padding:9px 12px;font-size:13px;margin-bottom:14px}
.demo-hint{margin-top:16px;padding-top:14px;border-top:1px solid var(--border);font-size:12px;color:var(--muted)}
.demo-hint code{background:var(--slate-l);padding:1px 6px;border-radius:5px;color:var(--text)}

/* .admin-link удалён вместе с .appbar: класса нет ни в одном шаблоне (ссылки
   администрирования переехали в сайдбар и меню аккаунта). Правила были
   рассчитаны на фиксированно-тёмный фон — #C6D3EC и белый hover на светлой
   теме сливались бы с var(--bg), так что оживлять их нельзя. */

/* ---------- Admin: triggers ---------- */
.btn.icon.xs{padding:2px 5px;font-size:10px}

/* ---------- Outbox log entries ---------- */
.log-row td{padding:0 14px 8px;background:var(--surface-2)}
.log-entry{font-size:11.5px;color:var(--muted);padding:2px 0;font-family:"IBM Plex Mono",Consolas,monospace}
.log-ts{color:var(--faint);margin-right:4px}
/* Журнал доставки убран под <details>: раньше он был развёрнут ВСЕГДА и
   удваивал высоту таблицы (строка-лог на каждую строку сообщения), хотя
   нужен ровно при разборе одного зависшего сообщения. Нативный <details>
   работает без JS. */
.log-row summary{cursor:pointer;list-style:none;padding:4px 0;font-size:11.5px;color:var(--muted)}
.log-row summary::-webkit-details-marker{display:none}
.log-row summary::marker{content:''}
.log-row summary::before{content:"\25B8 ";color:var(--faint)}
.log-row details[open] summary::before{content:"\25BE "}

/* ---------- Role cards ---------- */
.roles-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:768px){.roles-grid{grid-template-columns:1fr}}
.role-card{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;background:var(--surface-2)}
.role-name{font-weight:700;font-size:14px;margin-bottom:8px}
.role-perms{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.perm-check{display:inline-flex;align-items:center;gap:4px;font-size:12px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:3px 8px;cursor:pointer}
.perm-check input{margin:0}
.perm-check:hover{border-color:var(--primary)}

/* ---------- Accessibility helpers ---------- */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ---------- Table scroll (адаптивность) ---------- */
/* СИСТЕМНЫЙ фикс «таблица вылезает за экран» (жалоба повторялась на
   /admin/incidents, /admin/audit, /directions и многих других страницах).
   История вопроса: раньше по умолчанию здесь стоял overflow-x:visible,
   auto включался ТОЛЬКО ниже 1100px — резон был в position:sticky у
   `<thead>`: overflow-x!=visible на контейнере всегда тянет за собой
   overflow-y:auto (спецификация не разрешает паре «visible + не-visible»
   разъезжаться), из-за чего .table-scroll становится СВОИМ scroll-
   контейнером, и sticky-шапка прилипает к НЕМУ, а не к #main-content.
   Сначала попробовали оставить это как точечное исключение для журнала
   направлений и таблицы схемы процесса (#journal-wrap/#wf-table-wrap) —
   но и это не спасало: #main-content сам держит overflow-y:auto, и по
   той же спецификации его СОБСТВЕННЫЙ overflow-x тоже становится auto
   (даже не будучи задан явно) — широкая таблица внутри переполняла уже
   ЕГО, а не document, и document.scrollWidth оставался чистым, маскируя
   баг под автоматическими проверками, но пользователь по факту всё равно
   таскал вбок ВЕСЬ #main-content (поиск, фильтры, KPI-плитки — не только
   таблицу). Поэтому теперь скролл ВСЕГДА свой, внутри таблицы, без
   исключений — ценой sticky-шапки почти везде (эту жалобу видели куда
   чаще, и по историческому опыту проекта у самой длинной таблицы — 31
   строка на этапах — общая с документом полоса прокрутки тоже когда-то
   была источником путаницы, не только удобством).

   ДОБАВЛЕНО ПОЗЖЕ: тот самый sticky-thead (см. `.tbl thead th` ниже) без
   этого блока был МЁРТВЫМ кодом — раз .table-scroll сам никогда не
   скроллится по Y (высота равна содержимому, весь вертикальный скролл
   уходит наверх, в #main-content), sticky относительно НЕГО никогда не
   срабатывает: шапка просто уезжает вместе с таблицей, как самый обычный
   `<thead>`. Ограничиваем высоту — теперь .table-scroll ДЕЙСТВИТЕЛЬНО
   скроллится сам по себе, и sticky-шапка прилипает по-настоящему, ценой
   отдельной полосы прокрутки у высоких таблиц (не всей страницы — только
   таблицы, что и было целью запроса «шапка должна прилипать»). */
.table-scroll{position:relative;overflow-x:auto;overflow-y:auto;max-height:max(360px, calc(100vh - 220px));
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
.table-scroll .tbl{min-width:640px}
/* Семь колонок, из них пять с полями ввода — уже 520px строка добавления
   схлопывалась в кашу. Таблица листается горизонтально (.table-scroll). */
.table-scroll .est{min-width:820px}

/* Фокус-стиль для кликабельных строк журнала (keyboard nav) */
.tbl tbody tr:focus-within{outline:2px solid var(--primary);outline-offset:-2px}

/* Журнал на мобильном: карточки-строки вместо горизонтального скролла
   (единственная широкая таблица, которую реально листают с телефона —
   админские таблицы горизонтальный скролл устраивает, их открывают
   намного реже и почти всегда с десктопа). Не трогаем .table-scroll .tbl
   в общем виде — только #journal, у него одного есть data-label на <td>. */
@media(max-width:700px){
  #journal-wrap{overflow-x:visible;overflow-y:visible;max-height:none}
  #journal{min-width:0}
  #journal thead{display:none}
  #journal tbody tr{display:block;border-bottom:1px solid var(--border);padding:10px 14px}
  #journal tbody tr:last-child{border-bottom:none}
  #journal td{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:4px 0;border-bottom:none;text-align:right}
  #journal td[data-label]::before{content:attr(data-label);color:var(--faint);font-size:11.5px;font-weight:650;text-transform:uppercase;letter-spacing:.03em;text-align:left;flex:none}
  #journal td[data-label='']::before{display:none}
  #journal td.mono{justify-content:space-between}
  #journal .veh{text-align:right}
  #journal .veh small{display:block}
  /* Флаги на телефоне подписываем словами: title-тултип там не показывается
     вовсе, а иконка без подписи ничего не сообщает (см. flags_html). */
  #journal .flags .sr-only{position:static;width:auto;height:auto;clip:auto;clip-path:none;margin:0;overflow:visible;white-space:normal;font-size:11px;color:var(--muted)}
  #journal .flag{width:auto;padding:2px 7px;gap:4px;display:inline-flex;align-items:center}
}

/* Сетка имени/артикула в смете */
.est .cell-name{font-weight:550}
.est .cell-art{display:block;font-weight:400;color:var(--muted);font-size:11.5px}

/* ---------- Documents: hover + загрузчик ---------- */
.doc{cursor:pointer}
.doc:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:var(--radius-sm)}
.file-input{cursor:pointer;border:1px solid var(--border-2);border-radius:var(--radius-sm);padding:8px 12px;background:var(--surface);font-size:13px;color:var(--muted);transition:.12s;display:inline-flex;gap:7px;align-items:center}
.file-input:hover{background:var(--surface-2);border-color:var(--border-h)}
/* Реальный input скрыт (label.file-input — стилизованный триггер) */
.dropzone input[type=file]{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden}
.file-input-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}

/* ---------- Адаптивность: формы и actionbar на узких экранах ---------- */
@media(max-width:680px){
  /* .addrow — теперь одна кнопка в ячейке <tfoot>, разворачивать нечего;
     на узком экране таблица листается горизонтально (.table-scroll). */
  .est-add .addsub{flex-direction:column;gap:6px}
  .actionbar{flex-direction:column;align-items:stretch}
  .actionbar .sep{display:none}
  /* Формы «перейти на этап» и «завершить до» — это select + кнопка в строку.
     Кнопка получает width:100% (правило ниже), поэтому в одну строку с
     select'ом она не помещалась и её подпись обрезалась до «Изменить эт…».
     На узком экране кладём select и кнопку друг под друга. */
  .actionbar form{width:100%;flex-wrap:wrap}
  .actionbar form .input,.actionbar form select{flex:1 1 100%;min-width:0}
  .actionbar .btn{width:100%;justify-content:center}
  /* Таблицы, выведенные БЕЗ обёртки .table-scroll (версии процесса и т.п.),
     на узком экране молча обрезались по краю карточки — вместе с последней
     колонкой, где живут кнопки действий («Активировать»), и добраться до них
     было нельзя: страница по горизонтали не прокручивается. display:block
     превращает саму таблицу в область прокрутки. */
  .card-body>.tbl,.wrap>.tbl{display:block;overflow-x:auto;max-width:100%}
  .info{grid-template-columns:1fr}
  .kpis{flex-direction:column}
  .login-card{width:calc(100vw - 32px);max-width:360px}
  .wrap{padding:14px}
  .page-head h1{font-size:18px}
}

/* ---------- Печатная версия (опционально) ---------- */
@media print{
  /* .topbar целиком НЕ прячем: после консолидации шапки именно в нём живёт
     <h1> страницы (см. split_page_head в ui.rs) — распечатка журнала/аудита
     выходила вообще без названия. Убираем из него только интерактив. */
  .topbar{position:static;border:none;padding:0 0 8px;min-height:0}
  .topbar .nav-burger,.topbar .spacer,
  .topbar .tenant,.topbar .page-head .btn{display:none!important}
  .sidebar,.nav-scrim,.skip-link,
  .toolbar,.actionbar,.addrow,.est-add,.est-locked,.est .c-act,.dropzone,.toast{display:none!important}
  /* На печати смета — таблица, а не форма: рамки полей только мешают. */
  .est .ed{border:none!important;background:none!important;padding:0;margin:0}
  /* Единая область прокрутки (app-shell, см. .layout ниже) держит высоту
     ровно в 100vh и обрезает всё остальное — на экране так и нужно, но на
     печати это обрубало распечатку до одной страницы. На бумаге прокрутки
     нет, поэтому высоты/overflow снимаем — контент течёт по страницам как
     обычный документ. */
  .layout{height:auto;overflow:visible}
  .main{height:auto}
  #main-content{overflow:visible}
  body{background:var(--surface)}
}


/* ---------- App layout: sidebar ---------- */
/* Раньше .layout был min-height:100vh — страница росла вместе с
   содержимым, и своя вертикальная полоса прокрутки была у ДОКУМЕНТА, а
   ЕЩЁ одна — у любой внутренней области с overflow (например у широкой
   таблицы, .table-scroll). Прокручивать такую страницу означало гадать,
   в какую из двух полос попадёт курсор. Теперь .layout — РОВНО высота
   экрана, ничего лишнего не растёт; единственная область, которая
   реально прокручивается — #main-content ниже (flex:1;overflow-y:auto),
   сайдбар и шапка остаются на месте не через sticky «пока не уедет», а
   просто потому, что не участвуют в прокрутке вообще. */
/* .app-shell — колонка на всю высоту экрана: полоса «Войти как» (если
   активна) занимает свою естественную высоту сверху, ВСЁ ОСТАЛЬНОЕ
   (сайдбар+контент, .layout) — оставшееся место. Раньше полоса рендерилась
   ВНУТРИ <main>, соседом контента одной конкретной страницы — не на всю
   ширину экрана (сайдбар оставался рядом) и не закреплена сверху. */
.app-shell{display:flex;flex-direction:column;height:100vh}
.layout{display:flex;flex:1;min-height:0;overflow:hidden}
/* Full-width полоса поверх .layout (см. .app-shell) — не карточка внутри
   контента: без внутреннего скругления/отступа снизу и с паддингом по
   боковому краю экрана, а не по .wrap контента. */
.user-impersonation{flex:none;margin-bottom:0;border-radius:0;border-width:0 0 1px;padding:10px 22px}
/* 258px — не произвольное число: 20 (паддинг .sidebar) + 20 (паддинг
   .nav-item) + 22 (.nav-ico) + 10 (gap) + 180 (.nav-label max-width) + 6px
   запас. При 224px самый длинный пункт меню («Настройки уведомлений») не
   помещался и текст обрезался ДО того, как достигал собственного
   max-width:180px у .nav-label — панели банально не хватало ширины. */
.sidebar{width:258px;flex:none;background:var(--surface);border-right:1px solid var(--border);padding:14px 10px;height:100%;overflow-x:hidden;overflow-y:auto;display:flex;flex-direction:column;transition:width .32s ease-in-out,padding .32s ease-in-out;will-change:width}
/* Прямые flex-дети .sidebar (лого, заголовки групп, пункты меню, подвал)
   по умолчанию flex-shrink:1 — когда суммарная высота содержимого больше
   доступной (много пунктов меню + низкое окно), flexbox СНАЧАЛА сжимает
   каждого ребёнка, а не пускает .sidebar в прокрутку через overflow-y:auto
   (та включается только когда сжимать уже некуда). На практике это резало
   .nav-group (10px текст + 12px паддинг = 30px) до ~17px — подпись группы
   («Работа»/«Настройки») обрезалась почти целиком. flex-shrink:0 на всех
   прямых детях отдаёт браузеру то же самое место под естественный размер,
   а излишек уходит в скролл самого сайдбара, как и задумано. */
.sidebar>*{flex-shrink:0}
.main{flex:1;min-width:0;display:flex;flex-direction:column;height:100%}
/* min-height:0 — классическая ловушка flex: без него колонка-ребёнок с
   overflow:auto просто растёт под содержимое вместо того, чтобы сжаться
   и реально прокручиваться внутри отведённых .main границ. */
#main-content{flex:1;min-height:0;overflow-y:auto}
/* Логотип бренда — теперь в верхней части сайдбара (см. project brief:
   консолидация appbar+page-head), а не в отдельном appbar. */
.sidebar .brand{display:flex;align-items:center;gap:9px;padding:2px 8px 16px}
/* Лого — сам flex-ряд (значок + подпись «Рабочий стол», см. logo_html):
   раньше это был квадрат display:grid;place-items:center строго под одну
   иконку, подписи в нём было негде встать. Иконка — фиксированный размер
   через .sidebar .brand .logo-svg (ниже), не растягивается на весь .logo. */
.sidebar .brand .logo{display:flex;align-items:center;gap:9px;color:var(--primary);flex:none}
/* Подпись лого — того же стиля, что у обычного пункта меню (.nav-item —
   13.5px/600/var(--slate)), а не крупнее/жирнее — просто первая строка
   списка, ведущая на рабочий стол. Сама иконка — НЕ 20px, как у пунктов
   меню (пробовали: при разворачивании панели лого визуально «сжималось»
   с 26px свёрнутого до 20px — читалось как баг, а не как дизайн). 26px —
   один и тот же размер в обоих состояниях сайдбара, чуть крупнее icoн
   меню (бренд заметен), но без скачка при разворачивании/сворачивании. */
.sidebar .brand .logo-svg{width:26px;height:26px;flex:none}
.logo-svg{width:100%;height:100%;transform:rotate(-8deg)}
/* Подпись рядом с лого — тот же .nav-label, что и у пунктов меню: гаснет
   той же анимацией при сворачивании панели (см. .nav-collapsed .nav-label
   ниже), а не отдельным правилом только для этого места. */
.logo-label{font-weight:600;font-size:13.5px;color:var(--slate)}
.nav-group{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--faint);padding:12px 10px 5px}
/* padding/gap здесь НЕ анимируем: пробовали — вместе с шириной .sidebar
   (тоже animated) на каждой иконке получалось двойное, рассинхронизированное
   движение (сама анимация паддинга плюс пересчёт justify-content от живой
   ширины контейнера каждый кадр) — по многим пунктам сразу это читалось как
   дрожание всего меню. Мгновенная смена паддинга/gap + анимированная ТОЛЬКО
   ширина панели (см. .sidebar) даёт один непрерывный чистый сдвиг. */
.nav-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--radius-xs);color:var(--slate);font-size:13.5px;font-weight:600;margin-bottom:2px}
.nav-item:hover{background:var(--slate-l);text-decoration:none;color:var(--text)}
.nav-item.active{background:var(--primary-l);color:var(--primary-d)}
.nav-ico{width:22px;text-align:center}
.nav-ico .ico{width:20px;height:20px}
.nav-badge{margin-left:auto;background:var(--red);color:var(--bg);border-radius:999px;font-size:11px;font-weight:700;padding:1px 7px}

/* ---------- Блок тарифа (низ сайдбара) — см. docs/11 «Биллинг-минимум» ---------- */
.sidebar-bottom{margin-top:auto;padding-top:10px;display:flex;flex-direction:column;gap:12px}
.plan-card{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;margin-top:10px;margin-bottom:0}
.plan-name{font-size:12px;font-weight:700;color:var(--text)}
.plan-note{font-size:11px;color:var(--faint);margin-top:3px}
.plan-card.warn{background:var(--amber-l);border-color:var(--amber)}
.plan-card.warn .plan-note{color:var(--amber);font-weight:700}
.plan-card.danger{background:var(--red-l);border-color:var(--red)}
.plan-card.danger .plan-note{color:var(--red);font-weight:700}

/* ---------- Мобильное меню навигации (<860px) ---------- */
.nav-burger{display:none;position:relative;appearance:none;border:none;background:none;font-size:20px;line-height:1;cursor:pointer;padding:6px 8px;border-radius:8px;color:var(--muted);font-family:inherit}
.nav-burger:hover{background:var(--surface-2)}
/* На мобильном пункты меню (в т.ч. бейдж «Ленты событий») вне экрана, пока
   панель не открыта бургером — единственное видимое место для бейджа
   непрочитанного тогда сам бургер (см. ui.rs::nav_shell). */
.nav-burger-badge{position:absolute;top:1px;right:1px;min-width:15px;height:15px;padding:0 3px;
  border-radius:999px;background:var(--red);color:var(--bg);font-size:10px;font-weight:700;
  line-height:15px;text-align:center}
.nav-burger-badge.is-empty{display:none}
.nav-scrim{display:none;position:fixed;inset:0;background:rgba(15,23,42,.4);z-index:39}
@media(max-width:860px){
  .sidebar{
    display:flex;flex-direction:column;position:fixed;top:0;left:0;bottom:0;z-index:40;
    height:100vh;box-shadow:var(--shadow-lg);transform:translateX(-100%);transition:transform .18s ease;
  }
  .nav-burger{display:inline-block}
  /* Класс .nav-open переключается кнопкой через app.js (тот же механизм,
     что и у вкладок карточки направления) — надёжнее чекбокс-приёма. */
  body.nav-open .sidebar{transform:none}
  body.nav-open .nav-scrim{display:block}
}

/* ---------- KPI row ---------- */
.kpi-row{display:flex;gap:14px;flex-wrap:wrap;margin-bottom:16px}
.kpi-row .kpi{flex:1;min-width:190px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow);transition:border-color .15s}
.kpi-row .kpi:hover{border-color:var(--border-2)}
.kpi.danger .val{color:var(--red)}
.kpi.warn .val{color:var(--amber)}
.kpi-link{display:block;color:inherit}
.kpi-link:hover{text-decoration:none;box-shadow:var(--shadow-md)}
/* Плитка, соответствующая ТЕКУЩЕМУ фильтру журнала: без неё единственным
   следом выбора после клика оставалась адресная строка. */
.kpi-link.is-active{border-color:var(--primary);box-shadow:0 0 0 2px var(--primary-l)}
.kpi-top{display:flex;align-items:center;justify-content:space-between;gap:8px}
.kpi .lbl{font-size:12px;color:var(--muted);font-weight:700}
.kpi-icon{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;flex:none}
.kpi-icon svg{width:14px;height:14px}
.kpi-icon.total{background:var(--cyan-l);color:var(--cyan)}
.kpi-icon.wait{background:var(--amber-l);color:var(--amber)}
.kpi-icon.work{background:var(--primary-l);color:var(--primary)}
.kpi-icon.over{background:var(--red-l);color:var(--red)}
/* Значение KPI: число НИКОГДА не переносится — вместо переноса уменьшается
   кегль (clamp). Два размера, отскоупленные явно: раньше .kpi .val был
   объявлен дважды с одинаковой специфичностью и 27px «протекали» в
   компактные плитки карточки направления (.kpis .kpi) — сумма там ломалась. */
.kpi .val{font-family:"IBM Plex Mono",monospace;font-weight:600;letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap}
.kpi-row .kpi .val{font-size:clamp(19px,2.4vw,27px);margin-top:10px}
.kpis .kpi .val{font-size:clamp(15px,1.9vw,19px);margin-top:2px}
.kpi-delta{font-size:11.5px;margin-top:5px;color:var(--faint)}

/* ---------- Журнал направлений ---------- */
/* Одна колонка: правая боковая полоса журнала снята — «Мои этапы»
   дублировали вкладку «Мои», лента событий уехала в свой раздел, а
   карточка организации повторяла меню аккаунта. */
.content-grid{display:grid;grid-template-columns:1fr;gap:20px;align-items:start}
.content-grid>*{min-width:0}


@media(max-width:1180px){
    }

/* ---------- Security/Accessibility improvements ---------- */

/* Focus visibility: keyboard navigation outline */
:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.btn:focus-visible{outline:2px solid var(--primary-d);outline-offset:2px}
a:focus-visible{outline:2px solid var(--primary);outline-offset:1px;border-radius:4px}
input:focus-visible,select:focus-visible{outline:2px solid var(--primary-d);outline-offset:1px}

/* Touch targets: минимум 44px для мобильных */
@media(pointer:coarse){
  .btn{min-height:44px;min-width:44px}
  .btn.icon{min-width:44px}
  .perm-check{min-height:44px;padding:8px 12px}
  .tab{padding:14px 16px}
  /* Иконочные контролы вне .btn — раньше оставались мелкой мишенью
     (закрытие тоста, бургер меню). */
  .toast-close{min-width:44px;min-height:44px;display:inline-flex;align-items:center;justify-content:center}
  .nav-burger{min-width:44px;min-height:44px}
}

/* Reduced motion */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
  .toast{animation:none}
}

/* Адаптивная шапка. ВНИМАНИЕ: раньше эти правила были нацелены на .appbar,
   которого в разметке больше нет (шапка консолидирована в .topbar, см.
   ui.rs::nav_shell) — блок был мёртвым, и на 360px в одну flex-строку
   пытались влезть бургер + заголовок + тема + колокол + чип тенанта +
   аватар + логин + ссылки аккаунта. */
@media(max-width:860px){
  .topbar{gap:10px;padding:10px 14px;min-height:56px}
  .topbar .page-head h1,.topbar .page-title{font-size:16px}
}
@media(max-width:640px){
  .topbar .tenant{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;padding:4px 8px}
  /* Заголовок и кнопки страницы — в ОДНУ строку. .page-head наследует от
     десктопного правила flex-wrap:wrap, и на 375px кнопка («CSV», «Добавить»)
     съезжала под заголовок: шапка вырастала до 100px, то есть на каждой
     странице отдавала телефону восьмую часть экрана. Заголовок теперь
     ужимается с многоточием, кнопка остаётся справа. */
  .topbar .page-head{flex-wrap:nowrap;align-items:center;min-width:0;gap:8px}
  /* min-width:0 нужен именно ОБЁРТКЕ заголовка (в разметке это безымянный
     div с .crumbs и h1): по умолчанию у flex-элемента min-width:auto, и
     обёртка отказывалась сжиматься ниже ширины nowrap-заголовка — кнопка
     страницы выезжала за шапку и наползала на колокольчик. */
  .topbar .page-head>*:first-child{flex:1 1 auto;min-width:0}
  /* :not(:first-child) — на страницах без кнопок в шапке единственный
     потомок .page-head одновременно и первый, и последний; без этой оговорки
     flex:none перебивал бы правило выше и длинный заголовок распирал бы
     шапку вместо того, чтобы ужаться многоточием. */
  .topbar .page-head>*:last-child:not(:first-child){flex:none}
  .topbar .page-head h1{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .topbar .page-head .btn{flex:none;padding:5px 9px;font-size:12px}
  /* ДВА и более действия в шапке (карточка направления: «Акт приёмки» +
     «Заказ-наряд») в одну строку с бургером, колокольчиком и аватаром не
     влезают — кнопки наползали на колокольчик, а заголовок ужимался до нуля.
     Такой шапке отдаём кнопкам отдельную строку. Одна кнопка (журнал — «CSV»)
     рядом с заголовком помещается, и шапка остаётся однострочной, поэтому
     условие на количество кнопок, а не на ширину экрана.
     display:contents поднимает детей .page-head на уровень flex-строк .topbar:
     без него перенос происходил бы ВНУТРИ её 199-пиксельной коробки и кнопки
     всё равно вылезали бы за край. */
  /* Тот же перенос нужен и форме фильтра по организации (журнал направлений,
     супер-админ): <select> с длинными названиями организаций + кнопка
     «Показать» — flex:none у последнего ребёнка .page-head (см. выше) не
     давал им сжаться, и «Показать» наезжала на кнопку «?» (обучение) справа
     от .spacer — на 375px перекрытие доходило до ~60% площади кнопки. */
  .topbar:has(.page-head>*:last-child>.btn+.btn),
  .topbar:has(.page-head>*:last-child form){flex-wrap:wrap;row-gap:8px}
  .topbar:has(.page-head>*:last-child>.btn+.btn) .page-head,
  .topbar:has(.page-head>*:last-child form) .page-head{display:contents}
  .topbar:has(.page-head>*:last-child>.btn+.btn) .page-head>*:last-child,
  .topbar:has(.page-head>*:last-child form) .page-head>*:last-child{flex:1 0 100%;order:9}
  /* На своей полноширинной строке форма может занять весь ряд: сама форма и
     <select> внутри неё растягиваются (flex:1;min-width:0), кнопка остаётся
     по своей ширине. Без этого select держит внутреннюю intrinsic-ширину по
     самому длинному <option> и остаётся неоправданно узким/широким пятном
     посреди свободной строки. */
  .topbar .page-head>*:last-child form{flex:1;min-width:0}
  .topbar .page-head>*:last-child form select{flex:1;min-width:0}
  /* Чипы статуса под заголовком дублируют карточку «Текущий этап» ниже —
     на телефоне это лишние три строки в липкой шапке. */
  .topbar .page-head h1+div{display:none}
  /* Плитки по две в ряд. В один столбец пять штук занимали ~620px, и журнал —
     главное на странице — начинался за пределами первого экрана. */
  .kpi-row{gap:10px}
  .kpi-row .kpi{min-width:calc(50% - 5px);padding:11px 12px}
  .kpi-row .kpi .lbl{font-size:11px}
  .kpi-row .kpi .val{margin-top:4px}
  .kpi-row .kpi-delta{margin-top:2px;font-size:11px}
}
@media(max-width:480px){
  .topbar .tenant{display:none}
  .topbar{gap:8px;padding:8px 12px}
}

/* ---------- Супер-адаптивность 360-768px ---------- */
@media(max-width:560px){
  /* Текстовый чип может перенестись: числа внутри уже склеены NBSP (money())
     либо обёрнуты в .nw, поэтому перенос затронет только слова. */
  .chip{white-space:normal}
  .chip .nw,.chip .num,.chip .mono{white-space:nowrap}
  .card-head{flex-wrap:wrap}
  .card-body{padding:12px 14px}
  .tab-row{width:100%}
  .tab-row a,.tab-row button{flex:1;text-align:center}
}
@media(max-width:400px){
  .wrap{padding:10px}
  /* Плитки остаются по две в ряд (правило ≤640px): в одну колонку они
     превращали сводку в пять экранов прокрутки. Ужимаем только отступы. */
  .kpi-row .kpi{padding:10px 11px}
}

/* Modern polish: smoother transitions on interactive elements */
.btn,.tab,.admin-link,.perm-check,.chip{transition:all .15s cubic-bezier(.4,0,.2,1)}
.card{transition:box-shadow .2s ease}
.card:hover{box-shadow:var(--shadow-md)}


/* ---------- Версии процесса: свёрнуты в командной панели таблицы ---------- */
.wf-versions-d{margin:0;border:1px solid var(--border);border-radius:var(--radius-sm);
  background:var(--surface);box-shadow:none}
.wf-versions-sum{display:inline-flex;align-items:center;gap:7px;cursor:pointer;list-style:none;
  padding:7px 12px;font-size:13px;color:var(--muted)}
.wf-versions-sum::-webkit-details-marker{display:none}
.wf-versions-sum:hover{color:var(--text)}
.wf-versions-d[open]{flex-basis:100%;width:100%}
.wf-versions-d[open] .wf-versions-sum{color:var(--text);border-bottom:1px solid var(--border)}
.wf-versions-d .card-body{padding:12px 14px}

/* ---------- Сворачиваемая боковая панель ---------- */
/* Класс на <html>, а не на <body>: состояние применяется в theme-init.js из
   <head>, где <body> ещё не существует. Иначе панель разворачивалась бы на
   полную ширину и схлопывалась уже на глазах, дёргая всю раскладку. */
.sidebar .brand{display:flex;align-items:center;gap:10px;justify-content:space-between}
.brand-text{min-width:0;flex:1}
.nav-collapse{flex:none;display:grid;place-items:center;width:34px;height:34px;
  border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--surface);
  color:var(--muted);cursor:pointer;transition:background .12s,color .12s,border-color .12s}
.nav-collapse:hover{background:var(--surface-2);color:var(--text);border-color:var(--border-h)}
.nav-collapse:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.nav-collapse svg{width:17px;height:17px}
/* Кнопка «развернуть» — бургер (2 линии) ПОД логотипом, видна только в
   свёрнутом состоянии, вместо стрелки «свернуть» (та в этот момент прячется,
   см. .nav-collapsed ниже). Тот же размер, что у стрелки — это одна и та же
   по смыслу кнопка, просто в двух разных состояниях. data-nav-collapse общий
   для обеих — обработчик в app.js один и тот же, просто переключает класс. */
.nav-expand{display:none;flex:none;place-items:center;width:34px;height:34px;
  border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--surface);
  color:var(--muted);cursor:pointer;transition:background .12s,color .12s,border-color .12s}
.nav-expand:hover{background:var(--surface-2);color:var(--text);border-color:var(--border-h)}
.nav-expand:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.nav-expand svg{width:17px;height:17px}

.nav-collapsed .sidebar{width:60px;padding:14px 8px}
.nav-collapsed .brand-text{display:none}
/* Подпись пункта меню и заголовки групп («РАБОТА»/«НАСТРОЙКИ») — НЕ
   display:none, а сжатие до 0 (max-width/max-height) + затухание: тариф и
   меню пользователя переехали в бабл (их это больше не касается), но
   группы-заголовки раньше пропадали мгновенно ровно в момент начала анимации
   ширины панели — заметный «прыжок»/мигание всей панели. max-height нужен
   конкретный (не auto — auto не анимируется), с запасом под самый длинный
   текст группы. */
.nav-label{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px;
  opacity:1;transition:max-width .32s ease-in-out,opacity .2s ease}
.nav-collapsed .nav-label{max-width:0;opacity:0}
.nav-group{overflow:hidden;max-height:30px;opacity:1;
  transition:max-height .32s ease-in-out,opacity .2s ease,padding .32s ease-in-out}
.nav-collapsed .nav-group{max-height:0;opacity:0;padding-top:0;padding-bottom:0}
.nav-collapsed .nav-badge{position:absolute;top:2px;right:6px;transform:scale(.85)}
.nav-collapsed .nav-item{position:relative}
/* Раскладка, которую нельзя анимировать (flex-direction/display меняются
   мгновенно, без переходной геометрии), держится на ОТДЕЛЬНОМ классе
   .nav-collapsed-settled, а не на .nav-collapsed напрямую — тот дают сразу
   по клику и он двигает саму (анимируемую) ширину панели. app.js навешивает
   -settled только ПОСЛЕ окончания анимации ширины при сворачивании и снимает
   его СРАЗУ при разворачивании (см. комментарий там). Без разнесения по
   времени эта раскладка возникала в первый же миг клика — ещё при
   224px-широкой панели уже рисовалась как для 60px, и половину анимации
   всё было не на своих местах: читалось как явное «моргание» всей панели.
   justify-content:flex-start + фиксированный left-padding у пункта меню —
   НЕ center: тот мерил бы середину от суммарной ширины иконки+подписи, а
   подпись угасает своей отдельной анимацией (max-width) — на пересечении
   два независимых движения давали лишний рывок. 11px даёт иконку 22px по
   центру доступных 44px (60px панель − 2×8px паддинг), без него. */
.nav-collapsed-settled .nav-item{justify-content:flex-start;padding:9px 11px;gap:0}
/* Свёрнуто: лого сверху, бургер под ним — колонкой (развёрнуто стрелка
   стоит справа от лого в один ряд, см. .sidebar .brand выше). */
.nav-collapsed-settled .sidebar .brand{flex-direction:column;justify-content:center;align-items:center;gap:8px}
/* .logo сам — flex-ряд «иконка+подпись» с gap:9px (см. .sidebar .brand .logo
   выше). В свёрнутом виде подпись схлопывается до max-width:0, но gap между
   флекс-элементами остаётся и добавляет пустое место СПРАВА от иконки —
   видимый значок оказывается смещён от центра узкой колонки к левому краю.
   Обнуляем gap здесь же, а не трогая .logo напрямую: в развёрнутом виде gap
   между иконкой и подписью нужен как есть. */
/* Обводку/свечение вокруг значка пробовали (радужное кольцо в стиле «AI»)
   и убрали целиком — на реальном размере значка это читалось плохо в любом
   варианте (тонкое кольцо сливалось в пятно, широкое свечение выглядело
   грязно). Размер иконки (26px, см. .sidebar .brand .logo-svg выше) один и
   тот же в обоих состояниях панели — отдельно пробовали делать её 20px
   (как у пунктов меню) именно в развёрнутом виде, но тогда при
   разворачивании панели значок визуально «сжимался» — читалось как баг, не
   как дизайн. */
.nav-collapsed-settled .sidebar .brand .logo{gap:0}
.nav-collapsed-settled .nav-collapse{display:none}
.nav-collapsed-settled .nav-expand{display:grid}
/* Меню пользователя в подвале — тот же приём и та же причина, что у пункта
   меню выше: имя/стрелка прячутся, остаётся аватар-иконка по центру узкой
   панели, 6px даёт 32px-аватар по центру доступных 44px. */
.nav-collapsed-settled .user-menu>summary{justify-content:flex-start;padding:7px 6px}
.nav-collapsed-settled .user-menu .user-name,
.nav-collapsed-settled .user-menu .chev{display:none}
/* Ниже 860px панель и так off-canvas по бургеру — сворачивание там не имеет
   смысла и только мешало бы: сбрасываем (в т.ч. -settled, он там тоже мог
   успеть навеситься до пересечения брейкпоинта). */
@media(max-width:860px){
  .nav-collapsed .sidebar{width:auto;padding:14px 10px}
  .nav-collapsed .brand-text{display:block}
  .nav-collapsed .nav-group{max-height:30px;opacity:1;padding-top:12px;padding-bottom:5px}
  .nav-collapsed .nav-label{max-width:180px;opacity:1}
  .nav-collapsed-settled .nav-item{justify-content:flex-start;padding:9px 10px;gap:10px}
  .nav-collapsed-settled .user-menu>summary{justify-content:flex-start;padding:7px 8px}
  .nav-collapsed-settled .user-menu .user-name{display:inline}
  .nav-collapsed-settled .user-menu .chev{display:inline-flex}
  .nav-collapsed-settled .sidebar .brand{flex-direction:row;justify-content:flex-start}
  .nav-collapse{display:none}
  .nav-collapsed-settled .nav-expand{display:none}
}

/* ---------- Командная панель таблицы сметы ---------- */
.est-toolbar{display:flex;align-items:flex-start;gap:10px;margin-bottom:8px;flex-wrap:wrap}
.est-toolbar-right{margin-left:auto;display:flex;gap:8px;align-items:center}
/* Форма добавления свёрнута: она нужна в момент добавления, а не всегда.
   <summary> стилизован под кнопку — раскрытие работает без JS. */
.est-add-d{flex:1 1 auto;min-width:0}
.est-add-d>summary{display:inline-flex;align-items:center;gap:7px;cursor:pointer;list-style:none}
.est-add-d>summary::-webkit-details-marker{display:none}
.est-add-d[open]>summary{margin-bottom:10px}
.est-add-form{border:1px solid var(--border);border-radius:var(--radius);
  background:var(--surface-2);padding:12px 14px}
.est-add-form .form-actions{margin-top:10px}
@media(max-width:680px){
  .est-toolbar-right{margin-left:0;width:100%}
}

/* ---------- Полоса этапа (.stagebar, см. ui.rs::stage_bar) ---------- */
/* Одна строка вместо прежних двух блоков (воронка 174px + actionbar 160px).
   flex-wrap, а не горизонтальная прокрутка: на узком экране элементы
   переносятся, и ни один не оказывается за краем — кнопку перехода нельзя
   «потерять» так, как теряется столбец в скроллящейся таблице. */
/* ---------- Полоса этапа над карточкой направления ----------
   Одна строка управления процессом: состояние, куда можно уйти назад,
   ТЕКУЩИЙ этап по центру, куда вперёд, кнопки действий этапа и кнопка
   ручного управления этапом (.sb-extra, открывает модалку). Под ней — гейт:
   чего не хватает для перехода. Раньше это были воронка на 31 сегмент,
   .actionbar и целая вкладка «Что дальше»; на вопрос «можно ли уже
   завершать этап» приходилось искать ответ в трёх местах. */
.stagebar{background:linear-gradient(0deg,var(--surface-2),var(--surface));
  border:1px solid var(--border);border-radius:var(--radius);
  padding:10px 14px;box-shadow:var(--shadow)}
.sb-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px}

/* Текущий этап — по центру и единственный, кто тянется: остальное имеет
   естественную ширину, и при нехватке места сжимается именно название.
   Соседи (.sb-side) прижаты к нему с двух сторон и одинаково узкие, чтобы
   центр не «плавал» при смене длины названий. */
.sb-side{display:flex;align-items:center;gap:6px;flex:0 1 auto;min-width:0}
.sb-side:empty{display:none}
.sb-cur{display:flex;align-items:center;gap:8px;flex:1 1 240px;min-width:0;
  padding:5px 12px;border-radius:999px;background:var(--surface);
  border:1px solid var(--border-2);cursor:help}
.sb-code{color:var(--faint);font-weight:700;flex:none}
.sb-title{font-weight:800;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sb-role{flex:none}
.sb-wait{flex:none}
.sb-sla{flex:none;margin-left:auto;padding-left:8px}
.sb-act{display:flex;align-items:center;gap:8px;flex:none}
.sb-act form{display:inline-flex;gap:8px}

/* Соседние этапы: куда разрешают уйти переходы текущего этапа. Кнопка, а не
   ссылка — переход меняет состояние и уходит POST'ом.
   Номер соседа печатаем без «№»: этим знаком помечен ТЕКУЩИЙ этап, и два
   «№» в строке читались бы как два равноправных. */
.sb-jump{display:inline-flex;flex:none}
.sb-step{display:inline-flex;align-items:center;gap:6px;max-width:200px;
  border:1px dashed var(--border-2);background:var(--surface);color:var(--text);
  border-radius:999px;padding:5px 12px;font-size:12.5px;font-family:inherit;
  cursor:pointer;transition:.12s}
.sb-step:hover{border-style:solid;border-color:var(--border-h);background:var(--surface-2)}
.sb-step:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
.sb-step .sb-arrow{color:var(--faint);flex:none}
.sb-step .sb-n{color:var(--faint);flex:none}
.sb-step .sb-t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Ручное управление этапом: одна узкая иконка-кнопка прижата вправо —
   «Изменить этап»/«Завершить до…» переехали в модалку (#stage-manage-modal,
   см. ui.rs::detail), сюда раньше вставали ДВЕ формы с селектами на 31
   этап, распиравшие строку почти всегда впустую. */
.sb-extra{display:flex;align-items:center;margin-left:auto}
.sb-extra:empty{display:none}

/* Гейт перехода: одна строка «готов / не хватает того-то / ждём СК». Стоит
   под полосой, потому что отвечает на вопрос про КНОПКИ в ней. */
.sb-gate{display:flex;align-items:flex-start;gap:8px;margin-top:9px;
  padding:8px 11px;border-radius:var(--radius-sm);font-size:12.5px;
  background:var(--surface-2);border:1px solid var(--border)}
.sb-gate .ico{width:16px;height:16px;flex:none;margin-top:1px}
.sb-gate .msg{display:flex;flex-wrap:wrap;align-items:center;gap:6px;line-height:1.5}
.sb-gate.ok{background:var(--green-l);border-color:transparent;color:var(--green)}
.sb-gate.warn{background:var(--amber-l);border-color:transparent;color:var(--amber)}
.sb-gate.wait{background:var(--primary-l);border-color:transparent;color:var(--primary-d)}
/* Невыполненное условие — чипом: их читают списком, а не фразой. */
.sb-need{display:inline-flex;align-items:center;gap:4px;padding:2px 9px;
  border-radius:999px;background:var(--surface);color:var(--text);font-weight:600}
.sb-need .ico{width:13px;height:13px;color:var(--red)}

/* «Не хватает N условий» — компактный чип+попап ПРЯМО в .sb-cur (не строка
   .sb-gate во всю ширину под полосой) — тот же приём, что у
   .wf-missing-banner на /admin/workflow (задача 73): свёрнуто по
   умолчанию, раскрывается кликом, без JS. */
.sb-blocker{position:relative;flex:none;font-size:12.5px;line-height:1.5}
.sb-blocker summary{display:flex;gap:5px;align-items:center;padding:3px 9px;
  border-radius:999px;background:var(--amber-l);color:var(--amber);
  cursor:pointer;list-style:none;font-weight:600;white-space:nowrap}
.sb-blocker summary::-webkit-details-marker{display:none}
.sb-blocker .ico{width:13px;height:13px;flex:none}
.sb-blocker .chev{display:inline-flex;color:inherit;transition:transform .15s}
.sb-blocker .chev .ico{width:12px;height:12px}
.sb-blocker[open] .chev{transform:rotate(180deg)}
.sb-blocker .msg{position:absolute;left:0;top:calc(100% + 6px);z-index:20;
  width:max-content;max-width:min(360px,90vw);padding:10px 12px;border-radius:var(--radius-sm);
  background:var(--surface);color:var(--text);border:1px solid var(--border);
  box-shadow:var(--shadow-lg);font-weight:400}
.sb-need-list{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}

@media(max-width:860px){
  .stagebar{padding:10px 12px}
  .sb-row{gap:8px}
  .sb-cur{flex-basis:100%;order:-1}
  .sb-step{max-width:150px}
  /* .sb-role дублирует роль, уже напечатанную в .sb-wait («ожидает: <роль>») —
     на телефоне (375px) .sb-cur не резиновая: код+название+роль+чип+SLA не
     помещались в свою строку (373px контента в 329px контейнере), и .sb-sla
     (margin-left:auto, flex:none) уезжала за правый край .layout — там
     overflow:hidden (см. .layout выше), и бейдж «осталось Nд» пропадал
     молча, без скролла и без следа. .sb-role скрываем ТОЛЬКО когда рядом
     есть .sb-wait (то есть текст и так не потерян, просто не дублирован) —
     когда это ход самого зрителя (.sb-wait пуст), .sb-role остаётся
     единственным источником роли и должен показываться. */
  .sb-cur:has(.sb-wait) .sb-role{display:none}
}

/* ---------- Визуальный редактор workflow (см. workflow-graph.js) ---------- */
.wf-legend{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:6px;font-size:11.5px;color:var(--faint)}
.wf-legend span{display:inline-flex;align-items:center;gap:5px}
.wf-legend i{display:inline-block;width:16px;height:0;border-top:2px solid var(--primary);flex:none}
.wf-lg-def{border-top-style:dashed!important}
.wf-lg-lin{border-top:2px solid var(--border-2)!important}
.wf-lg-fork{width:9px!important;height:9px!important;border:none!important;background:var(--amber);transform:rotate(45deg);border-radius:2px}
.wf-lg-port{width:9px!important;height:9px!important;border:none!important;border-radius:50%;background:var(--primary)}
.wf-lg-hint{flex-basis:100%;color:var(--faint)}

/* Карточка таблицы этапов (#wf-table-card) заполняет РОВНО остаток
   вьюпорта чистым flexbox — без JS-замеров (см. соседний
   fitTableScrollHeights в app.js: JS-подход зависел от точного момента
   замера и на страницах с высокой «шапкой» — баннер + переключатель +
   командная панель + легенда схемы, как раз /admin/workflow — не всегда
   успевал пересчитаться к нужному моменту, из-за чего страница ВСЁ РАВНО
   иногда скроллилась целиком поверх уже скроллящейся внутри себя
   таблицы). :has() скоуп — эффект строго для этой страницы (класс
   wrap-fill-table задаёт только ui::admin_workflow), остальные страницы,
   использующие тот же общий .wrap/#main-content, не затронуты.
   #wf-table-wrap: max-height ИЗ .table-scroll (app.css выше) явно
   отключён (`max-height:none`) — здесь высоту считает flex, а не он. */
#main-content:has(.wrap-fill-table){display:flex;flex-direction:column}
.wrap-fill-table{display:flex;flex-direction:column;flex:1;min-height:0}
.wrap-fill-table>*{flex:none}
.wrap-fill-table #wf-table-card{flex:1;min-height:0;display:flex;flex-direction:column}
.wrap-fill-table #wf-table-card>.wfe-head{flex:none}
.wrap-fill-table #wf-table-card>.table-scroll{flex:1;min-height:0;max-height:none}

#wf-graph{border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;background:var(--surface-2)}
.wf-canvas{position:relative;min-height:420px;height:56vh;max-height:640px;background:var(--surface);overflow:hidden}
.wf-svg{width:100%;height:100%;display:block;cursor:grab;touch-action:none;user-select:none}
.wf-svg.is-panning{cursor:grabbing}
.wf-nojs{padding:16px}

/* Узлы */
.wf-node{cursor:pointer}
.wf-node-bg{fill:var(--surface);stroke:var(--border-2);stroke-width:1.5;vector-effect:non-scaling-stroke;filter:drop-shadow(0 1px 1px rgba(16,24,40,.06))}
.wf-node-code{font-size:11px;font-weight:700;fill:var(--faint);font-family:"IBM Plex Mono",monospace}
.wf-node-title{font-size:12.5px;font-weight:650;fill:var(--text)}
.wf-node-role{font-size:10.5px;fill:var(--muted)}
.wf-cond-mark{fill:var(--amber)}
/* Акцент цвета беджа этапа (задача 95) — узкая полоска у левого края узла,
   рисуется только когда у этапа задан badgeColor (см. workflow-graph.js::
   makeNode); pointer-events:none — она НЕ должна перехватывать клик/drag
   узла, это чисто декоративный слой поверх .wf-node-bg. */
.wf-node-accent{pointer-events:none}
.wf-node.condition .wf-node-bg{fill:var(--amber-l);stroke:var(--amber)}
.wf-node.disabled{opacity:.42}
/* Этап не проходит текущий фильтр (#wf-filter, задача 74 — фильтр общий на
   все три режима просмотра) — узел ОСТАЁТСЯ на схеме (см. workflow-graph.js
   makeNode: убирать нельзя, сломает рёбра/раскладку), просто гаснет заметно
   сильнее, чем выключенный этап (.disabled), чтобы два состояния читались
   раздельно даже когда оба класса стоят разом (выключенный этап, который
   ЕЩЁ и не подходит под фильтр) — правило ниже .disabled по каскаду и
   выигрывает при равной специфичности. */
.wf-node.filtered-out{opacity:.22}
.wf-node.selected .wf-node-bg{stroke:var(--primary);stroke-width:2.5}
.wf-node.drop-target .wf-node-bg{stroke:var(--green);stroke-width:2.5;fill:var(--green-l)}
.wf-node:focus{outline:none}
.wf-node:focus .wf-node-bg{stroke:var(--primary);stroke-width:2.5}
.wf-port{fill:var(--surface);stroke:var(--border-2);stroke-width:1.5;vector-effect:non-scaling-stroke;opacity:0;transition:opacity .12s}
.wf-node:hover .wf-port,.wf-node:focus .wf-port,.wf-node.selected .wf-port{opacity:1;fill:var(--primary-l);stroke:var(--primary)}
/* .wf-port-hit — увеличенная (r=14 против видимых r=7) НЕВИДИМАЯ область
   попадания того же порта (см. workflow-graph.js::makeNode), нужна только
   для hit-testing мышью/пальцем. Общий класс .wf-port со видимым кружком
   даёт ей то же поведение opacity/fill по hover — переопределяем прозрачной
   заливкой без обводки всегда, чтобы не рисовать второй, больший кружок
   поверх видимого при наведении. */
.wf-port-hit{fill:transparent!important;stroke:none!important;opacity:1}
.wf-ghost .wf-node-bg{fill:var(--red-l);stroke:var(--red);stroke-dasharray:4 3}
.wf-ghost .wf-node-title,.wf-ghost .wf-node-role{fill:var(--red)}
.wf-ghost{cursor:default}

/* Переходы */
/* vector-effect:non-scaling-stroke — БЕЗ него толщина обводки скейлится
   вместе с #wf-world (см. applyView(): scale(view.k)). При авто-раскладке
   схемы с большим числом этапов view.k уходит далеко ниже 1 (для 31 этапа
   ~0.31), и «1.75px» линия перехода реально рисуется субпиксельной
   (~0.5px) — визуально пропадает, особенно рядом со свежедобавленной
   линией: разница в плотности читалась как «старая стрелка исчезла»,
   хотя в DOM/данных она оставалась (см. отчёт пользователя и разбор в
   истории коммитов). non-scaling-stroke держит толщину линии постоянной
   в экранных px независимо от масштаба схемы — тот же приём, что уже
   применён к области клика порта (.wf-port-hit), только для видимости,
   а не для hit-testing. */
.wf-edge{fill:none;stroke:var(--primary);stroke-width:1.75;vector-effect:non-scaling-stroke}
.wf-edge-hit{fill:none;stroke:transparent;stroke-width:16;cursor:pointer}
.wf-edge-g.default .wf-edge{stroke-dasharray:6 4}
.wf-edge-g.linear .wf-edge{stroke:var(--border-2);stroke-width:1.25}
.wf-edge-g.active .wf-edge{stroke-width:2.5}
.wf-edge-g.selected .wf-edge{stroke:var(--red);stroke-width:2.5}
.wf-arrow{fill:var(--primary)}
.wf-arrow.lin{fill:var(--border-2)}
.wf-arrow.sel{fill:var(--red)}
.wf-edge-label rect{fill:var(--surface);stroke:var(--border)}
.wf-edge-label text{font-size:10.5px;fill:var(--muted);font-family:"IBM Plex Mono",monospace}
.wf-edge-g.selected .wf-edge-label text{fill:var(--red);font-weight:700}
.wf-connect-line{fill:none;stroke:var(--primary);stroke-width:2;stroke-dasharray:5 4;pointer-events:none;vector-effect:non-scaling-stroke}
.wf-connect-line.wf-connect-pending{stroke-width:2.5;stroke-dasharray:none}
.wf-phase rect{fill:var(--surface-2);opacity:.6}
.wf-phase text{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;fill:var(--faint)}

.wf-danger{color:var(--red);font-weight:700}
#wf-status.is-ok{color:var(--green)}
#wf-status.is-err{color:var(--red);font-weight:700}
/* top:0 — относительно #main-content (см. блок «App layout» выше): .topbar
   там больше не участвует в прокрутке вообще (не сосед по scroll-контексту,
   а отдельный flex-блок НАД #main-content), так что «верх» области
   прокрутки и есть верх, сразу под шапкой — компенсировать высоту .topbar
   в top не нужно. */
.tbl thead th{position:sticky;top:0;background:var(--surface);z-index:5}

/* ---------- Настройки пользователя (/account/settings) ---------- */
.settings-row{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;flex-wrap:wrap}
.settings-label{font-size:13.5px;font-weight:650}
.settings-hint{font-size:12px;color:var(--faint);margin-top:4px;max-width:48ch}
.theme-choice{display:flex;gap:8px;flex-wrap:wrap}
.theme-opt{display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border:1px solid var(--border-2);border-radius:var(--radius-sm);background:var(--surface);color:var(--muted);font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:.12s}
.theme-opt:hover{background:var(--surface-2);color:var(--text)}
.theme-opt .ico{width:15px;height:15px}
/* Выбранный вариант подсвечивается ЧИСТЫМ CSS по data-theme на <html>: его
   ставит theme-init.js ещё до первой отрисовки, а «как в системе» — это
   отсутствие атрибута. Без этого пришлось бы ждать app.js в конце <body>,
   и каждая загрузка страницы моргала бы «ничего не выбрано». */
:root[data-theme="light"] .theme-opt[data-theme-choice="light"],
:root[data-theme="dark"] .theme-opt[data-theme-choice="dark"],
:root:not([data-theme]) .theme-opt[data-theme-choice="system"]{background:var(--primary-l);border-color:var(--primary);color:var(--primary-d)}

/* ---------- Интерактивное обучение (coach-marks, см. tour.js) ----------
   Затемнение делает ОДИН элемент .tour-hole огромной тенью (9999px) вокруг
   своего прямоугольника — так подсвеченный блок остаётся полностью читаемым,
   а не обведён поверх общего полотна. Скрим сверху ловит клики, чтобы во
   время обучения нельзя было случайно нажать что-то в затемнённой части.
   z-index'ы: .topbar 51/61(во время тура) < скрим 60 < сайдбар/шапка во
   время тура 61 < дырка 62 < тултип 63 < .toast 100.
   ВАЖНО (см. backlog п.82 — «зашёл на /directions, обучение не даёт
   перейти на рабочий стол»): скрим раньше перекрывал буквально весь экран,
   включая .sidebar и .topbar — сайдбар не участвует в собственном z-index
   (position:static на десктопе), поэтому лежал НИЖЕ скрима в стопке слоёв
   независимо от порядка в HTML, и клик по пункту меню попадал в скрим
   (тот его глушит — см. tour.js, build()), а не в ссылку. Ниже сайдбар и
   шапка НАРОЧНО поднимаются выше скрима (но НИЖЕ дырки/тултипа — подсветка
   элементов внутри них, например .sidebar на первом шаге сценария main,
   выглядит как раньше: тень дырки рисуется поверх). Клик по пункту меню
   больше не доходит до скрима вообще — уходит обычной навигацией, а
   tour.js сам забывает недошедший прогресс на следующей странице (boot()
   выше сравнивает сохранённый шаг с текущим URL). Так тур физически не
   может блокировать переход на другую страницу — не только «main», а
   вообще ЛЮБОЙ запущенный сценарий. */
.tour-scrim{position:fixed;inset:0;z-index:60;background:transparent}
body.tour-on .topbar{z-index:61}
@media(min-width:861px){body.tour-on .sidebar{position:relative}}
body.tour-on .sidebar{z-index:61}
.tour-hole{position:fixed;z-index:62;border-radius:var(--radius-sm);pointer-events:none;
  box-shadow:0 0 0 3px var(--primary),0 0 0 9999px var(--tour-scrim);
  transition:top .18s ease,left .18s ease,width .18s ease,height .18s ease}
.tour-pop{position:fixed;z-index:63;width:min(330px,calc(100vw - 24px));padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg)}
.tour-pop h3{font-size:14px;font-weight:750;margin-bottom:6px}
.tour-pop p{margin:0;font-size:13px;line-height:1.5;color:var(--muted)}
.tour-dots{display:flex;gap:4px;margin-top:12px}
.tour-dot{width:6px;height:6px;border-radius:50%;background:var(--border-2)}
.tour-dot.on{background:var(--primary)}
.tour-foot{display:flex;align-items:center;gap:8px;margin-top:13px;flex-wrap:wrap}
.tour-step{margin-right:auto;font-size:11.5px;color:var(--faint);font-variant-numeric:tabular-nums}
/* Страница под оверлеем не прокручивается: тултип позиционируется от
   getBoundingClientRect(), и прокрутка фона уводила бы «дырку» с цели. */
body.tour-on{overflow:hidden}
@media(max-width:640px){
  /* На телефоне тултип — нижний лист: рядом с подсвеченным блоком места нет,
     а «дырка» и так видна над ним. */
  .tour-pop{left:8px!important;right:8px!important;top:auto!important;bottom:8px!important;width:auto}
}
@media(prefers-reduced-motion:reduce){.tour-hole{transition:none}}

/* Скрытие через свойство element.hidden — единый механизм для всех
   прогрессивных улучшений (условные поля форм, фильтр строк таблицы,
   кнопки, которые без JS ничего не делают). Без !important правило
   проигрывает любому display: в компоненте (.form-grid{display:grid},
   .btn{display:inline-flex}). */
[hidden]{display:none!important}

/* ---------- Табличный редактор этапов (/admin/workflow, «Таблица») ----------
   Префикс .wfe-, а не .wf-: тот уже занят воронкой этапов карточки
   направления и SVG-схемой процесса (в частности .wf-row{display:flex} —
   на <tr> это развалило бы таблицу). Внутри форм редактора .wf-row
   переиспользуется намеренно: там это те же два контрола в строку. */
.wfe-head{flex-wrap:wrap;gap:10px;align-items:flex-start}
.wfe-toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
/* Заголовок «Этапы процесса» + чип недостающих статусов СК в одну строку,
   чуть выше счётчика «N из M этапов» под ней. relative — якорь для
   абсолютно спозиционированной выпадающей панели чипа (см. ниже). */
.wfe-title-row{display:flex;align-items:center;gap:10px;position:relative}

/* Баннер недостающих статусов СК — компактный кликабельный чип рядом с
   заголовком (не отдельная во всю ширину строка, как раньше). Тот же
   визуальный язык, что у .user-menu в шапке: summary+chev — сам чип,
   .msg — абсолютно спозиционированная выпадающая панель (не раздвигает
   заголовок при раскрытии), <details> — без JS, свёрнуто по умолчанию. */
.wf-missing-banner{position:relative;font-size:12.5px;line-height:1.5}
.wf-missing-banner summary{display:flex;gap:6px;align-items:center;padding:4px 10px;
  border-radius:var(--radius-sm);background:var(--amber-l);color:var(--amber);
  cursor:pointer;list-style:none;font-weight:600}
.wf-missing-banner summary:hover,.wf-missing-banner[open] summary{filter:brightness(1.08)}
.wf-missing-banner summary::-webkit-details-marker{display:none}
.wf-missing-banner .chev{display:inline-flex;color:inherit;transition:transform .15s}
.wf-missing-banner .chev .ico{width:14px;height:14px}
.wf-missing-banner[open] .chev{transform:rotate(180deg)}
/* z-index выше sticky-шапки таблицы (.tbl thead th{z-index:5}) — иначе
   попап при раскрытии оказывался ПОД шапкой таблицы этапов, читался как
   обрезанный. */
.wf-missing-banner .msg{position:absolute;left:0;top:calc(100% + 6px);z-index:20;
  width:max-content;max-width:min(420px,90vw);padding:12px 14px;border-radius:var(--radius-sm);
  background:var(--surface);color:var(--text);border:1px solid var(--border);box-shadow:var(--shadow-lg)}
.wf-missing-banner .ico{width:16px;height:16px;flex:none}
.wf-missing-banner b{color:inherit}
/* Один аккуратный столбец (не flex-wrap-сетка) — все чипы одной ширины,
   кнопки выровнены по правому краю в одну линию. */
.wf-missing-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}
/* Раньше был pill (border-radius:999px) — овальная область с обычной
   прямоугольной .btn внутри читалась как смешение двух разных языков формы
   в одном компоненте. Тот же прямоугольный скругление (--radius-sm), что и
   у остальных карточек/чипов-с-кнопкой в приложении (см. .doc — не pill). */
.wf-missing-chip{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:4px 6px 4px 12px;border-radius:var(--radius-sm);background:var(--surface-2);
  color:var(--text);font-weight:600}
.wf-missing-chip form{margin:0}
.wfe-toolbar .search input{min-width:200px}
.wfe-toolbar .input{padding:6px 10px;font-size:12.5px}
/* .wfe-toolbar .input (2 класса) специфичнее .search input (1 класс+тип) —
   без этого переопределения он побеждал и схлопывал padding-left обратно
   к 10px, из-за чего placeholder заезжал под иконку лупы (.search .ico
   стоит абсолютно на left:11px независимо от этого правила). */
.wfe-toolbar .search input{padding-left:34px}
/* Этап/Роль — единственные колонки без явной ширины в table-layout:fixed;
   раньше Роль ужималась/наезжала на SLA при длинных названиях ролей —
   фиксируем ширину и обрезаем содержимое многоточием, а не переносом. */
.wfe-tbl .wfe-c-role{width:140px}
.wfe-tbl .wfe-c-role .chip{max-width:100%;overflow:hidden;text-overflow:ellipsis;
  display:inline-block;vertical-align:bottom}
/* Командная строка над таблицей этапов — «Добавить этап» справа, под
   строкой поиска/фильтра (см. ui.rs::admin_workflow). */
.wfe-toolbar2{display:flex;justify-content:flex-end;margin-top:8px}
.wfe-toolbar2:empty{display:none}

/* table-layout:fixed — колонки только по явной ширине (или равным долям
   остатка), НИКОГДА по содержимому строк. Без него width:1% на состоянии/
   действиях был подсказкой для auto-раскладки, а не гарантией: лишнее
   пространство таблицы browser вправе был раздать любой другой колонке с
   явной шириной непропорционально много (так и произошло — колонка №
   с width:56px разрослась до ~290px, стоило строке фазы принести в
   таблицу ещё один достаточно широкий <td>). При fixed ширины колонок
   берутся строго из ПЕРВОЙ строки (заголовка) — остальные строки, включая
   colspan-строку фазы, содержимым эту раскладку изменить уже не могут:
   не влезло — переносится/обрезается внутри уже выделенной ширины.
   Раньше эту проблему маскировал именно wfe-c-state/wfe-c-act, у которых
   auto-раскладка width:1% обычно давала «ужаться до контента», но это
   везение, а не гарантия. */
.wfe-tbl{table-layout:fixed}
.wfe-tbl th,.wfe-tbl td{padding:9px 12px}
/* Чекбокс массового выбора — своя узкая колонка, как и у остальных
   служебных (код/SLA/состояние/действия): без явной ширины table-layout:fixed
   отдал бы ей долю остатка наравне с Этап/Роль/Настройки. */
.wfe-tbl .wfe-c-sel{width:36px;text-align:center}
.wfe-tbl .wfe-c-code{width:60px}
.wfe-tbl .wfe-c-sla{width:70px}
/* Связь этапа организации с жёстким справочником статусов страховой видна
   списком, а не только внутри этапа — width:1% было подсказкой «ужаться
   до контента» для table-layout:auto, под fixed это буквально 1% ширины
   таблицы (см. комментарий у .wfe-tbl{table-layout:fixed} выше). */
.wfe-tbl .wfe-c-status{width:150px;white-space:nowrap}
.wfe-tbl .wfe-c-state{width:120px;white-space:nowrap}
/* 230px давало ровно 218px контента (Изменить+На схеме+Удалить+зазор) при
   194px доступной ширины (230 - 12 padding-left - 24 padding-right) —
   переполнение съедало весь padding-right, кнопка «Удалить» упиралась в
   самый край ячейки без отступа. +30px даёт запас. */
.wfe-tbl .wfe-c-act{width:260px;white-space:nowrap;text-align:right}
.wfe-tbl .wfe-c-act .btn+.btn{margin-left:6px}
.wfe-inline{display:inline}

.wfe-title{font-weight:650;color:var(--text)}
.wfe-title:hover{color:var(--primary);text-decoration:none}
.wfe-desc{display:block;color:var(--faint);font-size:11.5px;margin-top:2px;
  max-width:52ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wfe-row.is-off{opacity:.55}
.wfe-row.is-open>td{background:var(--primary-l)}
/* Раскрытая строка-редактор — та же разметка stage_editor(), что и в
   модалке (см. комментарий у ui::admin_workflow), но вложенная в <tr>/<td>
   (иначе браузер выносит содержимое из таблицы, см. там же). Собственных
   рамок у .wfe-sec теперь нет (одна секция на весь tab-panel) — фон и
   отступ даёт сама ячейка, как раньше у карточки диалога. */
.wfe-edit-row>td{background:var(--surface-2);padding:14px 16px;border-bottom:2px solid var(--border-2)}
.wfe-counts{display:flex;gap:5px;flex-wrap:wrap}
.wfe-counts .chip{font-size:10.5px;padding:2px 7px}
.wfe-counts .is-zero{opacity:.4}

/* Редактор этапа — вкладки внутри модалки (см. ui.rs::stage_editor): каждый
   раздел свою &lt;section class='wfe-sec'&gt; больше не делит с соседями в
   grid — он один на весь tab-panel. Раньше все пять секций стояли сёстрами
   в grid и приходилось прокручивать, чтобы найти нужную. */
.wfe-sec{background:var(--surface);border:none;padding:0}
.wfe-sec-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.wfe-sec-head h3{margin:0;flex:1}
.wfe-sec .form-grid{gap:10px 12px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}
.wfe-sec .form-actions{margin-top:10px}
.wfe-sec .form-hint{margin:8px 0 0}
.wfe-sec .form-hint code{background:var(--slate-l);border-radius:4px;padding:0 4px}
.wfe-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}

/* Командная панель /admin/workflow (Добавить/+ Группа/Сброс к defaults) —
   общая НАД схемой и таблицей, а не своя у каждого режима просмотра (см.
   ui::admin_workflow). Переполнение — app.js::initToolbarOverflow. */
.wf-cmdbar{margin-bottom:16px}
/* Групповые операции по выделению строк — появляются ПРЯМО в командной
   панели (не отдельным плавающим блоком под таблицей): чекбоксы строк
   связаны с формой удаления атрибутом form="wf-bulk-delete". */
.wf-bulk-actions{display:flex;align-items:center;gap:8px;padding-left:8px;
  border-left:1px solid var(--border-2)}
.wf-bulk-actions form{margin:0}

/* Заголовок фазы — название + счётчик + «Переименовать»/«Разгруппировать»
   (докс/11 «явное управление фазами»). Кнопки видны всегда (не по ховеру):
   на трогскринах ховера не будет никогда. Текстовые стили (капс/трекинг/
   цвет) и паддинг — на .phase-row-in, а не на самой <td> (см. .tbl
   tr.phase-row td выше): <td> — обычная table-cell для верного расчёта
   ширины колонок, всё визуальное оформление строки — на внутреннем div. */
.phase-row-in{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:6px 14px;font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.04em;color:var(--faint)}
.phase-row-title{flex:none}
.phase-row-act{display:flex;gap:6px;margin-left:auto}
.phase-row-act .btn{font-weight:600;text-transform:none;letter-spacing:0}

/* ---------- Роли и доступ (/admin/users, /account/role) ---------- */
/* Право, делающее роль администратором организации, визуально отделено от
   операционных: его не должны проставлять «заодно» со всеми остальными. */
.perm-check.perm-admin{border-color:var(--primary);background:var(--primary-l);color:var(--primary-d);font-weight:650}
/* Пара «подпись — пояснение»: легенда прав и карточка «Моя роль». */
.kv{display:flex;gap:12px;align-items:baseline;padding:7px 0;border-bottom:1px solid var(--border)}
.kv:last-child{border-bottom:0}
.kv b{flex:none;min-width:210px}
/* Супер-админ работает от имени чужой организации — постоянная полоса над
   содержимым: правка чужого тенанта не должна выглядеть как правка своего. */
.org-impersonation{display:flex;gap:10px;align-items:center;margin-bottom:14px;padding:10px 12px;
  border:1px solid var(--amber);border-left-width:4px;border-radius:var(--radius-sm);
  background:var(--amber-l);color:var(--amber);font-size:13px}
.org-impersonation b{color:var(--text)}
.org-impersonation .ico{flex:none}
.org-impersonation a{margin-left:auto;flex-shrink:0}
/* «Войти как» конкретного пользователя — доступ ко всему чужому аккаунту
   целиком (не просто смена фильтра тенанта), акцент строже: красный, а не
   янтарный. form внутри ведёт себя как a (margin-left:auto). */
.user-impersonation{border-color:var(--red);background:var(--red-l);color:var(--red)}
.user-impersonation form{margin-left:auto;flex-shrink:0}
/* Тот же сигнал в переключателе организации в топбаре. */
.topbar .tenant.acting{background:var(--amber-l);color:var(--amber);border-color:var(--amber)}
.topbar .tenant.acting .dot{background:var(--amber)}
/* Глобальный переключатель организации супер-админа (задача 72: раньше
   отдельная форма над содержимым КАЖДОЙ organizационно-зависимой страницы,
   см. org_impersonation_banner про историю) — теперь сам чип в шапке, живой
   на КАЖДОМ экране. <select> вписан в пилюлю без своего фона/рамки — тот же
   визуальный вес, что был у статичного текста чипа. Кнопка «Перейти» —
   фолбэк без JS (data-autosubmit не срабатывает — см. app.js); раньше была
   обычной прямоугольной .btn.sm.ghost ВНУТРИ овальной пилюли — ровно тот
   антипаттерн «овал+прямоугольник», который пользователь явно просил
   искоренить везде (см. .wf-missing-banner выше) — свежая регрессия того
   же класса, найдена аудитом. Теперь тоже без фона/рамки, иконкой —
   вписывается в пилюлю так же, как select. */
.topbar .tenant.tenant-switch{padding:4px 6px 4px 12px}
.topbar .tenant-select{appearance:none;-webkit-appearance:none;border:none;background:transparent;color:inherit;font:inherit;font-weight:700;font-size:12.5px;padding:0 4px 0 0;margin:0;cursor:pointer;max-width:220px;text-overflow:ellipsis}
.topbar .tenant-select:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:var(--radius-xs)}
/* min-width/min-height:20px — ЯВНО перебивает глобальное правило touch-таргетов
   (@media(pointer:coarse) .btn{min-width:44px;min-height:44px}, см. «Security/
   Accessibility improvements» выше): без него на любом touch-устройстве (не
   только узкий вьюпорт — ЛЮБОЙ тач, включая планшет 768px) браузер всё равно
   раздувал эту иконку-кнопку до 44×44 (width/height:20px не перебивают
   min-width/min-height — это разные свойства), кнопка вылезала из пилюли
   квадратом, и пилюля выглядела «кривой» — та же самая проблема
   «овал+прямоугольник», которую этот блок как раз должен был устранить
   (см. коммент выше). Сама пилюля целиком (включая <select>) — куда больший
   тап-таргет, чем этот вспомогательный фолбэк-«Перейти», так что 20px тут
   осознанное исключение, а не общий отказ от touch-таргетов. */
.topbar .tenant-switch .btn{padding:0;margin:0;border:none;background:transparent;color:inherit;width:20px;height:20px;min-width:20px;min-height:20px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;flex:none}
.topbar .tenant-switch .btn:hover{background:var(--surface)}
.topbar .tenant-switch .btn .ico{width:14px;height:14px}
@media(max-width:768px){
  .org-impersonation{flex-wrap:wrap}
  .org-impersonation a{margin-left:0}
  .kv{display:block}
  .kv b{min-width:0;display:block;margin-bottom:2px}
}

/* ---------- Справка по этапам (/help) ----------
   Раньше — 31 одинаковая карточка в колонке 720px без оглавления и поиска:
   найти «что делают на этапе 18» можно было только скроллом. Теперь фазы —
   секции с якорями, а карточка этапа отвечает на четыре вопроса подряд
   (кто / что / что проверят / куда дальше) выровненными подписями слева. */
.help-toc{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}
.help-toc-item{display:inline-flex;align-items:center;gap:7px;padding:6px 12px;
  border-radius:999px;border:1px solid var(--border);background:var(--surface);
  font-size:12.5px;font-weight:650;color:var(--muted)}
.help-toc-item:hover{background:var(--surface-2);color:var(--text);text-decoration:none}
.help-toc-item b{font-family:"IBM Plex Mono",monospace;color:var(--primary);font-size:11.5px}
/* Липкая панель фильтра — под .topbar (z-index 30), поэтому 20 и top:64px. */
.help-toolbar{margin-bottom:14px;position:sticky;top:64px;z-index:20}
.help-tools{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.help-phase{margin-bottom:16px;scroll-margin-top:130px}
.help-stages{padding:0}
.help-stage{display:flex;gap:14px;padding:14px 18px;border-top:1px solid var(--border)}
.help-stage:first-child{border-top:none}
.help-stage.off{opacity:.55}
.help-num{flex:none;padding-top:2px}
.help-main{min-width:0;flex:1}
.help-ttl{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.help-meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.help-desc{margin:9px 0 0;color:var(--muted);font-size:13px;line-height:1.5}
.help-line{margin-top:9px;display:flex;gap:12px;align-items:baseline}
.help-lbl{flex:none;width:200px;font-size:11px;text-transform:uppercase;letter-spacing:.03em;
  color:var(--faint);font-weight:700;line-height:1.4}
.help-ul{margin:0;padding-left:18px;font-size:12.5px;color:var(--muted)}
.help-line>div{min-width:0;flex:1}
@media(max-width:760px){
  .help-toolbar{position:static}
  .help-line{display:block}
  .help-lbl{width:auto;display:block;margin-bottom:3px}
  .help-stage{padding:12px 14px}
  .help-tools .search input{min-width:0;width:100%}
}

@media(max-width:900px){
  .wfe-edit{grid-template-columns:1fr}
  .wfe-desc{display:none}
  /* Шире, чем общий .table-scroll .tbl{min-width:640px}: у строки этапа
     8 колонок, и на 640px счётчики настроек складывались бы в столбик,
     растягивая каждую строку на пять строчек текста. Таблица листается
     горизонтально — это дешевле, чем нечитаемая простыня. */
  .table-scroll .wfe-tbl{min-width:980px}
}

/* ---------- Модальные окна ----------
   Нативный <dialog>: своя подложка, Esc, focus-trap и правильный порядок
   в стеке — всё это браузер уже умеет, и повторять его на div'ах незачем.
   Открывает app.js (initModals) по data-modal. */
.modal{border:none;padding:0;background:transparent;max-width:min(680px,calc(100vw - 32px));width:100%;max-height:calc(100vh - 48px)}
.modal::backdrop{background:rgba(15,23,42,.52);backdrop-filter:blur(2px)}
.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 48px);
  background:var(--surface);color:var(--text);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-lg);overflow:hidden}
.modal-head{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--border)}
.modal-head h2,.modal-title{margin:0;font-size:15.5px;font-weight:700;flex:1;min-width:0}
.modal-body{padding:16px 18px;overflow:auto;flex:1}
.modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 18px;
  border-top:1px solid var(--border);background:var(--surface-2)}
.modal-foot .left{margin-right:auto}
/* Формы внутри модалки не рисуют собственную рамку — её рисует карточка. */
.modal .form{margin:0}
.modal .form-actions{margin:0}
@media(max-width:640px){
  .modal{max-width:100vw;max-height:100vh;width:100vw}
  .modal-card{max-height:100vh;border-radius:0;border:none}
}

/* Заглушка вместо превью: иконка типа файла и короткая подпись. Раньше все
   не-картинки выглядели одинаковой скрепкой — где акт, где смета в Excel,
   а где архив, по галерее понять было нельзя. */
.file-badge{display:flex;flex-direction:column;align-items:center;gap:5px;color:var(--muted)}
.file-badge .ico{width:26px;height:26px;opacity:.75}
.file-kind{font-size:10px;font-weight:800;letter-spacing:.06em}

/* Лайтбокс: картинка занимает столько, сколько даёт экран, и не режется. */
.lightbox{max-width:min(1100px,calc(100vw - 32px))}
.lightbox .modal-body{display:grid;place-items:center;background:var(--slate-l);padding:12px}
.lightbox img{max-width:100%;max-height:calc(100vh - 220px);object-fit:contain;border-radius:var(--radius-xs)}

/* Заметки: свои можно править и удалять — кнопки прижаты к правому краю
   строки и не мешают читать текст. Те же правила действуют в ленте
   активностей (.act-feed), где заметки и напоминания живут вперемешку с
   остальными записями. */
.notes-toolbar{margin-bottom:10px}
.notes li,.act-feed li{align-items:flex-start}
.notes .note-body,.act-feed .note-body{flex:1;min-width:0}
.note-act{display:flex;gap:4px;flex:none;opacity:.35;transition:opacity .12s}
.notes li:hover .note-act,.act-feed li:hover .note-act,.note-act:focus-within{opacity:1}

/* ---------- Вкладка «Активности» ---------------------------------------
   Четыре бывшие вкладки (уведомления / история / заметки / напоминания)
   сведены в одну ленту: фильтр сверху, лента посередине, размер страницы и
   пагинация снизу. */
.act-bar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;justify-content:space-between;margin-bottom:12px}
.act-add{display:flex;gap:8px;flex:none}

/* Мультивыбор пилюлями: выглядит как .tab-row, но активных значений может
   быть несколько сразу — это фильтр «что показывать», а не переключатель
   одного вида из нескольких. Чекбокс настоящий: он остаётся в порядке
   фокуса и доступен скринридеру, просто не рисуется. */
.tab-row-multi{flex-wrap:wrap}
.tab-row-multi label{position:relative;display:inline-flex;align-items:center;gap:6px;
  padding:6px 12px;border-radius:7px;font-size:12.5px;font-weight:700;
  color:var(--muted);cursor:pointer;user-select:none;transition:all .15s cubic-bezier(.4,0,.2,1)}
.tab-row-multi label:hover{color:var(--text)}
.tab-row-multi input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
/* Класс `on` ставит сервер — отбор виден и до того, как выполнится JS, и в
   браузере без поддержки :has(). :has() держим рядом, чтобы состояние было
   верным сразу по клику, ещё до ответа сервера. */
.tab-row-multi label.on,.tab-row-multi label:has(input:checked){background:var(--surface);color:var(--text);box-shadow:var(--shadow)}
.tab-row-multi label.on .tab-n,.tab-row-multi label:has(input:checked) .tab-n{background:var(--primary);color:var(--bg)}
.tab-row-multi label:has(input:focus-visible){outline:2px solid var(--primary);outline-offset:1px}

/* Подвал ленты: слева «строк на странице», справа пагинатор. */
.act-foot{display:flex;gap:14px;align-items:center;flex-wrap:wrap;justify-content:space-between;margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}
.act-size{display:flex;gap:8px;align-items:center}
.act-size select{padding:5px 26px 5px 9px;font-size:12.5px}
.act-pages{display:flex;gap:4px;align-items:center;flex-wrap:wrap;margin:0}
/* Кнопки «Показать» нужны только без JavaScript: с ним фильтр и размер
   страницы применяются сами. Тот же приём, что у .wf-nojs в схеме workflow. */
.act-nojs{flex:none}

/* Строка ленты: маркер непрочитанного, колонка времени, содержимое. */
.act-feed li{align-items:flex-start;gap:8px}
.act-ts{flex:none;width:104px;color:var(--faint);font-size:11.5px;padding-top:5px;
  white-space:nowrap;font-variant-numeric:tabular-nums}
.act-dot{flex:none;width:6px;height:6px;border-radius:50%;background:transparent;margin-top:11px}
.act-dot.on{background:var(--primary)}
/* Непрочитанное — то, что появилось после прошлого открытия вкладки.
   Подсветка фоном, а не рамкой: рамка внутри ленты читалась бы как ещё
   одна граница строки. */
.act-feed li.unread{background:var(--primary-l);border-radius:var(--radius-xs)}
.act-feed li.unread .act-ts{color:var(--primary-d)}
.feed .ic.notif{background:var(--cyan-l)}
.feed .ic.rem{background:var(--amber-l)}

/* «+14» рядом с обычным счётчиком вкладки: сколько записей появилось с
   прошлого открытия. Отдельным кружком, а не вместо .cnt — «сколько всего»
   и «сколько нового» отвечают на разные вопросы. Идёт после .tab.active
   .cnt, поэтому акцентный цвет не сбивается активной вкладкой. */
/* Раньше .cnt-unread был ВТОРЫМ беджем-пилюлей рядом с обычным .cnt (общим
   счётчиком) — два бейджа подряд на одной вкладке читались избыточно.
   Простой текст вместо пилюли: --green уже подобран читаемым в обеих
   темах (тёмно-зелёный на светлом фоне, светло-зелёный на тёмном — см.
   :root/[data-theme] токены), заливка/паддинг не нужны. */
.cnt-unread-text{color:var(--green);font-weight:700;font-size:11px;margin-left:5px}

@media(max-width:760px){
  .act-ts{width:100%;padding-top:0;order:-1}
  .act-feed li{flex-wrap:wrap}
}

/* ---------- Тумблер (одно значение вкл/выкл) ---------------------------
   Чекбокс остаётся настоящим (фокус, Space, скринридер) — рисуется только
   дорожка с бегунком. */
.switch{display:inline-flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;font-weight:650;user-select:none}
.switch input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.switch-track{position:relative;flex:none;width:38px;height:22px;border-radius:999px;
  background:var(--surface-2);border:1px solid var(--border-2);transition:background .15s,border-color .15s}
.switch-knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background:var(--surface);box-shadow:var(--shadow);transition:transform .15s}
.switch input:checked+.switch-track{background:var(--primary);border-color:var(--primary)}
.switch input:checked+.switch-track .switch-knob{transform:translateX(16px)}
.switch input:focus-visible+.switch-track{outline:2px solid var(--primary);outline-offset:2px}
/* Тот же вид (трек+кружок), что у .switch, но без <input> — состояние
   ведёт класс .on на самой ссылке, а не :checked (см. «Группировка по
   разделам» на вкладке «Смета» — обычная ссылка-навигация ?group=, а не
   форма/чекбокс). */
.switch-link{display:inline-flex;align-items:center;gap:10px;font-size:13px;font-weight:650;
  color:inherit;text-decoration:none;cursor:pointer;user-select:none}
.switch-link:hover{text-decoration:none;color:inherit}
.switch-link.on .switch-track{background:var(--primary);border-color:var(--primary)}
.switch-link.on .switch-knob{transform:translateX(16px)}
.switch-link:focus-visible .switch-track{outline:2px solid var(--primary);outline-offset:2px}

/* Модалка порядка группового повтора (/admin/messages) — см. app.js. */
.msg-batch-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.msg-batch-item{display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:13px}
.msg-batch-move{display:flex;gap:4px;flex:none}
.msg-batch-move button{border:1px solid var(--border-2);background:var(--surface);border-radius:6px;
  width:26px;height:26px;cursor:pointer;font-size:13px;line-height:1}
.msg-batch-move button:disabled{opacity:.35;cursor:default}

/* ---------- Выбор даты и времени ---------------------------------------
   Нативный datetime-local заменён своим виджетом: его панель рисует ОС, она
   выглядит по-разному в каждом браузере, не поддаётся оформлению и не даёт
   быстрых вариантов вроде «завтра в 10:00», ради которых напоминание и
   заводят. Значение при этом остаётся тем же («2026-09-10T15:30»). */
.dtp{position:relative}
.dtp-field{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  border:1px solid var(--border-2);background:var(--surface);border-radius:var(--radius-sm);
  padding:8px 11px;font-size:13.5px;font-family:inherit;color:var(--text);cursor:pointer;transition:.12s}
.dtp-field:hover{border-color:var(--primary)}
.dtp-field:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-l)}
.dtp-field .ico{color:var(--faint);flex:none}
.dtp-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dtp-text:empty::before{content:'Выберите дату и время';color:var(--faint)}
/* position:fixed — настоящий всплывающий бабл поверх содержимого, а не
   раскрытие формы в потоке (было так раньше именно из-за модалки:
   .modal-body{overflow:auto} обрезал бы absolute-панель по своей границе).
   fixed позиционируется от viewport и не обрезается overflow предков (пока
   ни у одного предка нет transform/filter — у .modal/.modal-card их нет),
   поэтому обрезание больше не проблема; координаты (top/left) считает
   app.js::open() по getBoundingClientRect() поля при каждом открытии. */
.dtp-pop{position:fixed;z-index:60;max-width:290px;padding:12px;
  background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);
  box-shadow:var(--shadow-lg)}
.dtp-quick{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}
.dtp-quick button{border:1px solid var(--border);background:var(--surface-2);border-radius:999px;
  padding:3px 10px;font-size:11.5px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}
.dtp-quick button:hover{border-color:var(--primary);color:var(--primary-d)}
.dtp-nav{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:6px}
.dtp-nav b{font-size:12.5px;font-weight:700}
.dtp-nav button{border:none;background:transparent;border-radius:var(--radius-xs);padding:4px;
  display:grid;place-items:center;color:var(--muted);cursor:pointer}
.dtp-nav button:hover{background:var(--surface-2);color:var(--text)}
.dtp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.dtp-wd{font-size:10.5px;font-weight:700;color:var(--faint);text-align:center;padding:3px 0}
.dtp-day{border:none;background:transparent;border-radius:var(--radius-xs);padding:6px 0;
  font-size:12.5px;font-family:inherit;color:var(--text);cursor:pointer;font-variant-numeric:tabular-nums}
.dtp-day:hover{background:var(--surface-2)}
.dtp-day.off{color:var(--faint)}
.dtp-day.today{font-weight:800;color:var(--primary-d)}
.dtp-day.on{background:var(--primary);color:var(--bg);font-weight:700}
.dtp-time{display:flex;align-items:center;gap:6px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}
.dtp-time label{font-size:12px;font-weight:650;color:var(--muted);margin-right:auto}
.dtp-time select{padding:5px 22px 5px 8px;font-size:12.5px}

/* Редактор этапа шире обычной формы: в нём пять секций, и переходы с
   кнопками действий в 680px читаются в одну колонку по строчке. */
.modal-wide{max-width:min(980px,calc(100vw - 32px))}
/* Секции редактора внутри модалки не рисуют собственных рамок — их роль
   выполняет карточка диалога. */
.modal .wfe-sec{border:none;padding:0;margin:0 0 18px}
.modal .wfe-sec:last-child{margin-bottom:0}

/* ---------- Поддержка ----------
   Таблица обращений слева, переписка справа: тред читают целиком, а
   переключаются между ними редко. Строка кликабельна целиком (data-href,
   см. app.js), активное обращение — подсвечено тем же приёмом, что и
   выбранная KPI-плитка. */
.sup-grid{display:grid;grid-template-columns:minmax(0,420px) minmax(0,1fr);gap:16px;align-items:start}
.sup-grid .tbl tbody tr.is-active{background:var(--primary-l)}
.sup-thread{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.sup-msg{padding:10px 13px;border-radius:var(--radius-sm);background:var(--surface-2);border:1px solid var(--border)}
/* Ответ поддержки выделен, чтобы в длинном треде было видно, где чей ход. */
.sup-msg.from-support{background:var(--primary-l);border-color:transparent}
.sup-msg-head{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}
.sup-msg-text{white-space:pre-wrap;font-size:13.5px}
.sup-reply{margin-bottom:8px}
.sup-reply textarea{width:100%}
@media(max-width:900px){
  .sup-grid{grid-template-columns:1fr}
}

/* Госномер в заголовке карточки направления — целиком, как настоящий ГРЗ РФ
   (ГОСТ Р 50577): белая табличка, буквы+цифры основной части, разделитель,
   код региона над синим триколором «RUS» (см. ui.rs::plate_display). Фон
   намеренно НЕ токен темы: сам номерной знак всегда белый, вне зависимости
   от темы приложения. */
/* Номер стилизован под реальный госзнак (белый фон/чёрная рамка — нарочно
   БЕЗ привязки к теме, как настоящая табличка). Но чёрная 2px-рамка сама по
   себе на тёмном фоне карточки/строки почти сливается с ним по внешнему
   краю — добавляем тонкое белое кольцо СНАРУЖИ рамки (box-shadow, не
   зависит от overflow:hidden самого беджа), чтобы граница читалась и в
   тёмной, и в светлой теме. */
.plate-badge{display:inline-flex;align-items:stretch;vertical-align:middle;
  background:#fff;color:#111;border:2px solid #111;border-radius:5px;overflow:hidden;
  box-shadow:0 0 0 2px rgba(255,255,255,.85);
  font-family:"IBM Plex Mono",monospace;font-weight:700;letter-spacing:.01em;line-height:1.1}
.plate-badge .pl-main{display:inline-flex;align-items:baseline;gap:1px;padding:2px 6px}
.plate-badge .pl-d{font-size:1.22em}
.plate-badge .pl-l{font-size:1em}
.plate-badge .pl-sep{width:2px;background:#111;flex:none;align-self:stretch;margin:2px 0}
.plate-badge .pl-region{display:flex;flex-direction:column;align-items:center;
  justify-content:center;padding:1px 5px 2px;gap:1px}
.plate-badge .pl-region-code{font-size:.62em;font-weight:800;line-height:1}
/* Флаг-триколор убран: верхняя белая полоса терялась на белом фоне самого
   номера (не читалась) — оставили только подпись «RUS» под кодом региона. */
.plate-badge .pl-rus{font-size:.5em;font-weight:800;letter-spacing:.04em;color:#111;line-height:1}
/* В журнале направлений номер — своя колонка (не мелкая подпись рядом с
   маркой/моделью, как раньше) — крупнее, чтобы читался с расстояния списка,
   а не только на карточке одного направления. Внутренние pl-*
   используют em, поэтому масштабируются пропорционально от родителя. */
#journal .plate-badge{font-size:1.2em}

/* Заголовок группы раздела в смете (см. ui.rs::estimate_tab — группировка
   по «запчасть»/«услуга»/«товар»). */
.est-section-row td{background:var(--surface-2);font-size:11px;font-weight:800;
  text-transform:uppercase;letter-spacing:.04em;color:var(--faint);padding:7px 12px}

/* ---------- Рабочий стол: сетка виджетов, панель настройки (app.js) ---------- */
/* При двух колонках сетка — НЕ CSS Grid auto-flow (тот заполняет ячейки по
   строкам и не позволяет закрепить виджет за конкретной колонкой — «1 слева,
   2 справа» на auto-flow недостижимо), а две независимые вертикальные
   колонки (.dash-col, data-col='1'/'2'). ui.rs::dashboard рендерит их СРАЗУ
   заполненными виджетами по умолчанию (чётность позиции — против FOUC, см.
   коммент там же); app.js дальше переносит виджеты между ними при кастомном
   назначении колонки (см. rsg-dashboard-col-assign) — для содержимого по
   умолчанию это не меняет ничего видимого, узлы просто переезжают в
   собственные (идентично выглядящие) col1/col2 самого app.js. При одной
   колонке — просто flex-столбец, .dash-col не используются. */
.dash-grid{display:flex;flex-direction:column;gap:16px;align-items:stretch}
.dash-grid.cols-2{flex-direction:row;align-items:flex-start}
.dash-col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:16px}
.dash-widget-title{display:flex;align-items:center;gap:8px}
.dash-widget-title h2{margin:0}
#dash-settings-btn[aria-expanded='true']{background:var(--primary-l);color:var(--primary-d)}
/* Разный отступ сверху между колонками (сообщение пользователя) — не баг
   flex-раскладки самой по себе (align-items:flex-start уже выравнивает верх
   обеих .dash-col), а конфликт с общим правилом `.card+.card{margin-top:16px}`
   (см. выше по файлу): оно матчится по ЛЮБОЙ соседней по DOM паре .card,
   включая скрытые атрибутом [hidden] (display:none НЕ убирает элемент из
   дерева для селектора смежных соседей) — если скрытый виджет в колонке
   стоит перед первым видимым, тот получает лишние +16px margin-top, а
   колонка без скрытого виджета перед первым видимым — нет, отсюда и разъезд
   по вертикали между колонками. Здесь `gap` уже даёт весь нужный отступ
   между виджетами, поэтому у самих карточек-виджетов margin-top гасим —
   выше специфичность, чем у `.card+.card` (три класса против двух). */
.dash-grid .dash-widget.card{margin-top:0}
/* Сетка виджетов «ощущалась хаотичной» (прямая жалоба пользователя) даже
   после того, как колонки стали выравненными технически (см. выше) —
   причина не в самой раскладке, а в разбросе высот карточек: у виджета с 4
   строками таблицы и виджета с одной строкой «нет задач» нет ничего общего
   по высоте, из-за чего одна колонка визуально обрывается на середине
   другой, а короткий виджет-пустышка выглядит обрезанным, а не осознанно
   компактным. Две меры без изменения самой раскладки (колонки/drag-n-drop —
   как попросил пользователь раньше в этой же сессии — не трогаем):
   1) пустое состояние виджета («нет задач», «направлений пока нет» и т.п.)
      получает разумную минимальную высоту и центрируется по ней — читается
      как «здесь специально пусто», а не как «виджет что-то не дорисовал»;
   2) внутренний скролл списка/таблицы виджета ограничен потолком заметно
      ниже общего .table-scroll (тот считает от вьюпорта — годится для
      журнала на всю страницу, но не для карточки на дашборде): без потолка
      виджет с большим количеством данных рос бы бесконтрольно и раскачивал
      бы высоты колонок сильнее, чем сейчас, вместо того чтобы становиться
      ровнее.
   Обе меры сужают РАЗБРОС высот между виджетами, не выравнивая их
   принудительно в одну строку (это масштабировало бы саму задачу до полной
   замены раскладки на CSS Grid со строками — непропорционально жалобе). */
.dash-widget .card-body>.empty{min-height:120px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;margin:0}
/* .table-scroll — виджеты «Направления»/«Уведомления»/«Ваши задачи» (таблица);
   .feed — «Журнал событий» (список последних действий, до 15 штук, см.
   main.rs::dashboard_page). Оба способны вырасти намного выше соседей
   («Журнал событий» у активного пользователя — заметно выше «Ваших задач»
   без единой ни одной) — тот же потолок и тот же приём (внутренний скролл),
   которым уже ограничена таблица, применяем и к списку. */
.dash-widget .table-scroll{max-height:280px}
.dash-widget .feed{max-height:280px;overflow-y:auto}
/* Панель настройки (#dash-settings-panel) — список виджетов, каждый со
   своей ручкой перетаскивания (.dash-drag-handle, HTML5 Drag and Drop,
   app.js) — основной способ переставить виджет и перенести его между
   колонками; чекбокс видимости, кнопки «выше»/«ниже» и (при двух колонках)
   пилюли «1 | 2» — равноценный запасной способ: доступны с клавиатуры и на
   сенсорном экране, где у Drag and Drop API нет тач-версии без полифилла. */
#dash-settings-panel{margin-bottom:16px}
.dash-settings-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;
  padding-bottom:10px;margin-bottom:10px;border-bottom:1px solid var(--border)}
.dash-settings-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.dash-settings-cols{display:flex;gap:16px;align-items:flex-start}
.dash-settings-cols>.dash-settings-list{flex:1 1 0;min-width:0}
.dash-settings-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:2px 4px;border-radius:var(--radius-sm);border:1px solid transparent}
.dash-settings-row .perm-check{flex:1;min-width:0}
.dash-drag-handle{flex-shrink:0;display:flex;align-items:center;cursor:grab;color:var(--faint);
  touch-action:none}
.dash-drag-handle .ico{width:16px;height:16px}
.dash-settings-row.dragging{opacity:.4}
.dash-settings-row.drag-over-top{border-top-color:var(--primary)}
.dash-settings-row.drag-over-bottom{border-bottom-color:var(--primary)}
.dash-settings-list.drag-target{outline:1px dashed var(--primary);outline-offset:4px;border-radius:var(--radius-sm)}
/* Пилюли колонки — компактнее обычного .tab-row (кнопки в строке и так
   тесно), но та же палитра/поведение .on, чтобы не изобретать новый стиль. */
.dash-col-switch{flex-shrink:0}
.dash-col-switch button{padding:4px 9px;font-size:11.5px}
@media(max-width:860px){
  /* flex-direction:column одного не хватает: .dash-grid.cols-2 всё ещё несёт
     align-items:flex-start (нужен на широких экранах, где направление row),
     а в колонку он не даёт .dash-col растягиваться на всю ширину — колонка
     сжимается по контенту (см. .table-scroll .tbl{min-width:640px} внутри),
     из-за чего таблица вылезает за пределы экрана на мобильных (375px). */
  .dash-grid.cols-2{flex-direction:column;align-items:stretch}
  .dash-settings-cols{flex-direction:column}
}
