/* Saxap Aura — интерфейс портала */
:root{
  color-scheme:light;
  --bg:#f5f6fb; --surface:#ffffff; --ink:#191b2e; --muted:#5b6080;
  --border:#e8eaf3; --border-strong:#d8dbec;
  --accent:#7c5cff; --accent-2:#ff5c9d; --accent-ink:#5b3fd6;
  --ok:#16a36a; --warn:#c77700; --danger:#e5484d;
  /* ориентир в матрице «Смены»: заливка строки/колонки под курсором и сама подпись */
  --mtx-hl:rgba(124,92,255,.07); --mtx-hl-strong:rgba(124,92,255,.16);
  --grad:linear-gradient(135deg,#7c5cff 0%,#ff5c9d 100%);
  --shadow:0 1px 2px rgba(25,27,46,.04),0 8px 24px rgba(25,27,46,.06);
  --shadow-hover:0 4px 10px rgba(25,27,46,.07),0 16px 40px rgba(124,92,255,.12);
  --radius:14px;
}
/* тёмная тема: атрибут ставит анти-flash скрипт в base.html (localStorage aura_theme,
   дефолт — системная prefers-color-scheme). Градиент бренда остаётся тем же. */
[data-theme="dark"]{
  color-scheme:dark;
  --bg:#0f1020; --surface:#1a1c30; --ink:#e9eaf6; --muted:#979dc4;
  --border:#272a44; --border-strong:#363a5c;
  --accent:#8f73ff; --accent-2:#ff6ea8; --accent-ink:#b5a4ff;
  --ok:#2ec98a; --warn:#ffb224; --danger:#ff6b6f;
  --mtx-hl:rgba(143,115,255,.13); --mtx-hl-strong:rgba(143,115,255,.26);
  --shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px rgba(0,0,0,.35);
  --shadow-hover:0 4px 10px rgba(0,0,0,.35),0 16px 40px rgba(124,92,255,.22);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  background:var(--bg); color:var(--ink);
  -webkit-font-smoothing:antialiased; font-size:14px; line-height:1.45;
}
a{color:inherit; text-decoration:none}
b{font-weight:700}

/* ===== Бренд AURA + раздел ===== */
.brand{display:flex; align-items:center; gap:9px; color:#0a0b0f}
/* логотип AURA из брендбука (03.10.2026, templates/_aura_logo.html): знак + надпись в кривых.
   Надпись — currentColor: Graphite на светлой теме, Frost на тёмной (правило брендбука). */
[data-theme="dark"] .brand{color:#eceef4}
.aura-symbol{display:block; width:22px; height:22px; flex:none}
.aura-symbol.big{width:46px; height:46px; margin:0 auto 14px}
.aura-wordmark{display:block; width:68px; height:auto; flex:none; overflow:visible}
/* название раздела продукта (Finance / CRM / …): разделитель + приглушённая подпись.
   Фундамент под мультиразделы — берётся из nav_section (context_processors). */
.brand-section{font-size:12.5px; font-weight:600; letter-spacing:.2px; color:var(--muted);
  padding-left:9px; border-left:1px solid var(--border-strong)}

/* ===== Лаунчер разделов (waffle-грид) ===== */
.applauncher{margin-right:6px}
.launch-btn{padding:0 10px 0 9px; height:36px; gap:8px; border:1px solid var(--border-strong);
  border-radius:10px; background:var(--surface); color:var(--ink)}
.launch-btn:hover{border-color:var(--accent)}
.applauncher[open] .launch-btn{border-color:var(--accent)}
.waffle{color:var(--accent); flex:none; display:block}
.launch-cur{font-weight:700; font-size:13px; letter-spacing:.2px}
.launch-panel{width:612px; max-width:calc(100vw - 24px); padding:12px}
/* minmax(0,1fr) — колонки РАВНОЙ ширины (иначе 1fr=minmax(auto,1fr) тянется по контенту) */
/* 3×3: девять разделов с Aura AI (26.09) — сеткой, а не столбом */
.launch-grid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px}
.launch-card{display:flex; flex-direction:column; align-items:flex-start; gap:7px; padding:12px 12px 13px;
  border:1px solid var(--border); border-radius:13px; background:var(--surface); transition:border-color .14s, box-shadow .14s, transform .14s}
.launch-tg{min-height:2.5em}   /* резерв под 2 строки — карточки одинаковой высоты и без «рванины» */
.launch-card:hover{border-color:var(--accent); box-shadow:var(--shadow); transform:translateY(-1px)}
.launch-card.is-active{border-color:var(--accent); background:rgba(124,92,255,.06)}
.launch-nm{font-weight:700; font-size:13.5px; display:flex; align-items:center; gap:7px}
.launch-tg{font-size:11.5px; color:var(--muted); line-height:1.3}
/* иконка-плитка раздела (свой градиент на каждый раздел, белый глиф) */
.sec-ic{width:36px; height:36px; border-radius:11px; display:inline-flex; align-items:center; justify-content:center;
  color:#fff; flex:none; box-shadow:0 3px 10px rgba(25,27,46,.14)}
.sec-ic--finance{background:linear-gradient(135deg,#7c5cff,#ff5c9d)}
.sec-ic--crm{background:linear-gradient(135deg,#3aa0ff,#43c6ac)}
.sec-ic--academy{background:linear-gradient(135deg,#ff9f43,#ff5c9d)}
.sec-ic--keyhunter{background:linear-gradient(135deg,#4f7cff,#8f73ff)}
/* ⚠️ Градиент обязан быть у КАЖДОГО раздела из AURA_SECTIONS. Глиф белый (color:#fff
   выше), фона у .sec-ic нет — раздел без своей строки здесь получает белую иконку на
   белой карточке, то есть пустую плитку в переключателе. Так и жили Voice (глиф был,
   градиента нет) и Landings (не было ни того, ни другого). */
.sec-ic--voice{background:linear-gradient(135deg,#e0459c,#4f7cff)}
.sec-ic--landings{background:linear-gradient(135deg,#0fb87f,#8bd94a)}
.sec-ic--board{background:linear-gradient(135deg,#00c2a8,#2f6bff)}
.sec-ic--ai{background:linear-gradient(135deg,#7b5cff,#e05cff)}
.sec-ic--mailings{background:linear-gradient(135deg,#ffb347,#ff5c5c)}
.section-soon{font-size:9px; font-weight:800; text-transform:uppercase; letter-spacing:.5px; color:var(--accent-ink);
  background:rgba(124,92,255,.14); padding:2px 6px; border-radius:999px}
[data-theme="dark"] .section-soon{background:rgba(143,115,255,.2)}

/* ===== Заглушка раздела «в разработке» ===== */
.soon-wrap{min-height:calc(100vh - var(--topbar-h) - 48px); display:flex; align-items:center; justify-content:center; padding:24px}
.soon-card{max-width:520px; text-align:center; display:flex; flex-direction:column; align-items:center;
  padding:44px 40px; border:1px solid var(--border); border-radius:20px; background:var(--surface); box-shadow:var(--shadow)}
.sec-ic-lg{width:64px; height:64px; border-radius:18px; margin-bottom:20px}
.sec-ic-lg svg{width:32px; height:32px}
.soon-badge{font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.6px; color:var(--accent-ink);
  background:rgba(124,92,255,.12); padding:5px 12px; border-radius:999px; margin-bottom:16px}
.soon-title{font-size:34px; font-weight:800; letter-spacing:-.6px; margin:0 0 8px}
.soon-title .grad{background:var(--grad); -webkit-background-clip:text; background-clip:text; color:transparent}
.soon-tagline{font-size:15px; font-weight:600; color:var(--ink); margin:0 0 12px}
.soon-text{font-size:14px; color:var(--muted); line-height:1.55; margin:0 0 26px; max-width:400px}
.soon-btn{text-decoration:none}

/* ===== Логотип клиента (Saxap): инлайн-SVG с фирменным градиентом (одинаков в обеих темах) ===== */
.saxap-logo{display:block; width:108px; height:auto}
/* блок клиента в сайдбаре — над городом */
.side-brand{padding:2px 6px 14px; margin-bottom:8px; border-bottom:1px solid var(--border)}

/* ===== Каркас: топбар (главный) на всю ширину + тихий сайдбар ===== */
:root{--sidebar-w:240px; --topbar-h:60px}
html[data-sidebar="off"]{--sidebar-w:0px}
body.app{display:grid; min-height:100vh;
  grid-template-columns:var(--sidebar-w) minmax(0,1fr);
  grid-template-rows:var(--topbar-h) minmax(0,1fr);
  grid-template-areas:"topbar topbar" "sidebar main"}
.topbar{grid-area:topbar; position:sticky; top:0; z-index:70; display:flex; align-items:center; gap:14px;
  padding:0 20px 0 14px; background:var(--surface);
  border-bottom:1px solid var(--border); box-shadow:0 1px 2px rgba(25,27,46,.03)}
.topbar .brand{margin-right:10px}
.nav-toggle{display:inline-flex; flex-direction:column; justify-content:center; gap:4px;
  width:36px; height:36px; padding:8px; border:0; border-radius:9px; background:none; cursor:pointer}
.nav-toggle:hover{background:var(--bg)}
.nav-toggle-bar{display:block; width:18px; height:2px; border-radius:2px; background:var(--muted)}
.fresh-chip{display:inline-flex; align-items:center; gap:7px; font-size:12px; font-weight:600;
  color:var(--muted); padding:6px 11px; border:1px solid var(--border); border-radius:999px;
  background:var(--bg); white-space:nowrap}
.fresh-dot{width:7px; height:7px; border-radius:50%; background:var(--ok); flex:none}
.theme-btn{width:36px; height:36px; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--border-strong); border-radius:10px; background:var(--surface);
  color:var(--muted); cursor:pointer; flex:none}
.theme-btn:hover{border-color:var(--accent); color:var(--accent-ink)}
/* кнопка состояния системы (топбар). Три состояния, по возрастанию срочности:
   зелёная окантовка — всё в порядке; янтарная — что-то отстало (данные не обновляются),
   мигает красным — связи с сервером нет. Янтарное НЕ мигает намеренно: это «посмотри,
   когда дойдут руки», а не авария. Правила .offline идут ПОСЛЕ .warn — при равной
   специфичности побеждает последнее, и красный перекрывает янтарный. */
.status-btn{width:36px; height:36px; min-height:36px; padding:0; display:inline-flex; align-items:center;
  justify-content:center; border:1.5px solid var(--ok); border-radius:10px; background:var(--surface);
  cursor:pointer; flex:none; list-style:none}
.status-btn::-webkit-details-marker{display:none}
.status-dot{width:9px; height:9px; border-radius:50%; background:var(--ok); box-shadow:0 0 0 3px rgba(22,163,106,.18)}
.status-btn.warn{border-color:var(--warn)}
.status-btn.warn .status-dot{background:var(--warn); box-shadow:0 0 0 3px rgba(199,119,0,.20)}
.status-btn.offline{border-color:var(--danger); animation:statusblink 1.1s ease-in-out infinite}
.status-btn.offline .status-dot{background:var(--danger); box-shadow:0 0 0 3px rgba(229,72,77,.22)}
@keyframes statusblink{0%,100%{border-color:var(--danger)}50%{border-color:rgba(229,72,77,.25)}}
.status-menu{min-width:236px; padding:12px 14px}
/* Уведомления — конверт слева от светофора (apps/portal/notifications.py). Счётчик — пока есть
   непроверенные смены; мигание — только когда среди них есть новая с последнего открытия. */
.notif-btn{position:relative; width:36px; height:36px; min-height:36px; padding:0; display:inline-flex;
  align-items:center; justify-content:center; border:1.5px solid var(--border-strong); border-radius:10px;
  background:var(--surface); color:var(--muted); cursor:pointer; flex:none; list-style:none}
.notif-btn::-webkit-details-marker{display:none}
.notif-btn:hover{color:var(--ink)}
.notif-count{position:absolute; top:-7px; right:-7px; min-width:18px; height:18px; padding:0 5px;
  border-radius:9px; background:var(--accent); color:#fff; font-size:11px; font-weight:700;
  line-height:18px; text-align:center; font-variant-numeric:tabular-nums}
.notif-btn.is-new{color:var(--accent); border-color:var(--accent); animation:notifblink 1.2s ease-in-out infinite}
.notif-btn.is-new .notif-count{background:var(--danger)}
@keyframes notifblink{0%,100%{box-shadow:0 0 0 0 rgba(229,72,77,0)}50%{box-shadow:0 0 0 4px rgba(229,72,77,.28)}}
@media (prefers-reduced-motion:reduce){.notif-btn.is-new{animation:none}}
.notif-ic{width:20px; height:20px; flex:none; display:block}
/* ⚠️ Двойной класс: общие .dd-menu и .dd-item объявлены НИЖЕ в файле и при равной специфичности
   перебивали ширину меню и строку-флекс (число прилипало к тексту). */
.dd-menu.notif-menu{min-width:280px}
.notif-menu .notif-item{display:flex; justify-content:space-between; align-items:center; gap:16px;
  white-space:nowrap}
.notif-item b{font-variant-numeric:tabular-nums; min-width:22px; padding:0 6px; border-radius:9px;
  background:var(--accent); color:#fff; font-size:12px; line-height:19px; text-align:center}
.notif-empty{padding:8px 11px; font-size:13px; color:var(--muted)}
.side-badge{display:inline-block; min-width:18px; padding:0 5px; margin-left:4px; border-radius:9px;
  background:var(--accent); color:#fff; font-size:11px; font-weight:700; line-height:17px; text-align:center}
.status-line{font-weight:700; font-size:13px; margin:0 0 8px; color:var(--ok); display:flex; align-items:center; gap:7px}
.status-line::before{content:'●'; font-size:11px}
.status-line.warn{color:var(--warn)}
.status-line.off{color:var(--danger)}
.status-link{display:inline-block; font-size:12.5px; margin:-2px 0 8px}
.status-row{display:flex; justify-content:space-between; gap:16px; font-size:12.5px; padding:5px 0; align-items:baseline}
.status-row[hidden]{display:none}
.status-k{color:var(--muted)}
/* Кнопка «Офис» и список служб в светофоре — только разработчику (apps/ops) */
.office-btn{position:relative; display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:8px; color:var(--muted)}
.office-btn:hover,.office-btn:focus-visible{color:var(--ink); background:rgba(127,127,127,.12)}
/* Знак «Окно видно только вам» (templates/_only_you.html, разработчик 05.10.2026): треугольник у пункта меню и кнопки
   шапки окна, которое видит только разработчик (у «Офиса» — в углу кнопки), и надпись над заголовком такого окна —
   пилюля .tag.tag-warn (её цвета обеих тем — у .tag-warn). «!» вырезан: сквозь него виден фон под значком. */
.only-you{display:inline-flex; vertical-align:-2px; margin-left:6px; color:var(--warn)}
.only-you svg{display:block}
.office-btn .only-you{position:absolute; top:-1px; right:-2px; margin:0}
.office-btn .only-you svg{width:11px; height:11px}
.tag.only-you-note{display:flex; align-items:center; gap:6px; width:fit-content; margin:0 0 8px; padding:3px 10px 3px 7px;
  font-size:12px; line-height:1.3; white-space:nowrap}
.only-you-note svg{flex:none; color:var(--warn)}
.status-menu:has(.ops-list){min-width:330px}
.ops-list{margin-top:8px; padding-top:6px; border-top:1px solid var(--border); max-height:60vh; overflow:auto}
.ops-group{font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--muted); margin:8px 0 2px}
.ops-row{display:grid; grid-template-columns:8px 1fr auto; gap:8px; align-items:center; font-size:12.5px; padding:3px 0}
.ops-dot{width:8px; height:8px; border-radius:50%; background:var(--ok)}
.ops-dot.warn{background:var(--warn)}
.ops-dot.off{background:var(--muted); opacity:.45}
.ops-v{color:var(--muted); font-variant-numeric:tabular-nums}
.status-v{color:var(--ink); font-weight:600; font-variant-numeric:tabular-nums; white-space:nowrap}
/* контекстное меню по правому клику (static/js/menu.js) — одно на страницу.
   ⚠️ display здесь не задаём: узел скрывается атрибутом hidden, а авторское правило
   с display было бы сильнее браузерного [hidden]{display:none} — и меню не закрылось бы. */
.ctx-menu{position:fixed; z-index:140; min-width:172px; padding:5px;
  border:1px solid var(--border); border-radius:11px; background:var(--surface); box-shadow:var(--shadow-hover)}
.ctx-menu button{display:block; width:100%; text-align:left; padding:8px 10px; font:inherit; font-size:13px;
  color:var(--ink); border:0; border-radius:7px; background:none; cursor:pointer}
.ctx-menu button:hover{background:var(--bg)}
.ctx-menu button.is-danger{color:var(--danger)}
/* пункты-переключатели (быстрый статус «Плана рассылок», 02.10.2026): точка цвета перед подписью, текущий
   пункт отмечен галочкой и неактивен; разделитель групп. Цвет точки задаёт экран (mailings.css). */
.ctx-menu button:disabled{cursor:default}
.ctx-menu button:disabled:hover{background:none}
.ctx-menu button.is-current{font-weight:700}
.ctx-menu button.is-current::after{content:"✓"; float:right; margin-left:12px; color:var(--muted)}
.ctx-menu-dot{display:inline-block; width:9px; height:9px; margin-right:8px; border-radius:50%;
  vertical-align:0; background:var(--muted)}
.ctx-menu-sep{height:1px; margin:4px 6px; background:var(--border)}
/* модальное подтверждение */
.modal-backdrop{position:fixed; inset:0; z-index:120; padding:20px; background:rgba(20,22,40,.5);
  display:flex; align-items:center; justify-content:center}
.modal-backdrop[hidden]{display:none}
.modal{background:var(--surface); border:1px solid var(--border); border-radius:16px;
  box-shadow:var(--shadow-hover); padding:24px; max-width:400px; width:100%}
.modal-title{font-size:18px; font-weight:800; margin:0 0 8px; letter-spacing:-.2px}
.modal-text{color:var(--muted); font-size:14px; line-height:1.5; margin:0 0 20px}
/* ⚠️ align-items и wrap не украшение: у .btn-ghost и .btn-primary разная рамка, и при
   stretch по умолчанию кнопки выходили разной высоты и разъезжались. В узком окне
   вторая кнопка уезжала за край. */
.modal-actions{display:flex; justify-content:flex-end; align-items:center;
  flex-wrap:wrap; gap:10px; margin-top:18px}
.modal-actions>*{flex:0 0 auto}
/* ⚠️ .btn-primary тащит собственный margin-top:22px — она рассчитана на низ формы входа,
   а не на пару кнопок в окне, и роняет «Продать» на 22px ниже «Отмены». Общая
   нормализация в начале файла (.btn-primary{margin-top:0}) не спасает: она объявлена
   ДО самой кнопки, а правило той же силы, объявленное ниже, побеждает. У .form-actions
   этот же margin гасится строкой ниже по файлу — здесь не гасился вовсе.
   Заодно равняем геометрию с .btn-ghost: у них разный padding и кегль, и кнопки
   выходили разной высоты. Правило «кнопки в паре совпадают по высоте» — то же, ради
   которого в проекте заведён класс .btn. */
/* Рамка прозрачная, но есть: у .btn-ghost она рисованная, и без неё главная кнопка
   выходит на 2 пикселя ниже. Тот же приём, что в .btn. */
.modal-actions .btn-primary{margin-top:0; padding:10px 18px; font-size:14px;
  border:1px solid transparent}
/* Окно «Обновить расписание» (разработчик 30.09.2026, smena.js rosterOpen): бегунок → галочка или жёлтый
   треугольник «есть нестыковки» со списком и выбором. Значки — CSS, без картинок: цвет берут токены темы. */
.roster-modal{max-width:520px}
.roster-modal:focus{outline:none}
.roster-head{display:flex; align-items:center; gap:12px; margin:0 0 10px}
.roster-head .modal-title{margin:0}
.roster-icon{flex:none; width:28px; height:28px; position:relative; display:inline-block}
.roster-load{border-radius:50%; border:3px solid var(--border); border-top-color:var(--accent, var(--ok));
  animation:roster-spin .8s linear infinite}
.roster-ok{border-radius:50%; background:var(--ok)}
.roster-ok::after{content:""; position:absolute; left:10px; top:5px; width:7px; height:13px;
  border:solid #fff; border-width:0 3px 3px 0; transform:rotate(45deg)}
.roster-warn, .roster-ask{width:0; height:0; border-left:15px solid transparent; border-right:15px solid transparent;
  border-bottom:26px solid var(--warn)}
.roster-warn::after, .roster-ask::after{content:"!"; position:absolute; left:-3px; top:6px; color:#fff;
  font-weight:800; font-size:16px; line-height:1}
.roster-err{border-radius:50%; background:var(--danger)}
.roster-err::after{content:"!"; position:absolute; inset:0; color:#fff; font-weight:800; font-size:17px;
  line-height:28px; text-align:center}
@keyframes roster-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.roster-load{animation-duration:2.4s}}
.roster-line{font-size:14px; line-height:1.5; margin:0 0 10px}
.roster-list{list-style:none; margin:6px 0 0; padding:0; display:grid; gap:10px; max-height:52vh; overflow:auto}
.roster-conf{border:1px solid var(--border); border-left:3px solid var(--warn); border-radius:10px;
  padding:10px 12px; display:grid; gap:6px}
.roster-why{font-size:13px; line-height:1.45}
.roster-pick{justify-self:start; max-width:100%}
.theme-btn .icon-sun{display:none}
[data-theme="dark"] .theme-btn .icon-sun{display:block}
[data-theme="dark"] .theme-btn .icon-moon{display:none}

/* ===== Сайдбар: второстепенный, сливается с фоном ===== */
.sidebar{grid-area:sidebar; position:sticky; top:var(--topbar-h); height:calc(100vh - var(--topbar-h));
  overflow-y:auto; overflow-x:hidden; display:flex; flex-direction:column;
  padding:14px 12px 10px; border-right:1px solid var(--border); background:transparent}
html[data-sidebar="off"] .sidebar{display:none}
.side-group{display:flex; flex-direction:column; gap:8px; padding-bottom:14px; margin-bottom:6px;
  border-bottom:1px solid var(--border)}
.side-label{font-size:10px; text-transform:uppercase; letter-spacing:.7px; color:var(--muted);
  font-weight:700; margin:12px 6px 2px}
.side-context .side-label{margin-top:0}
.side-city{width:100%; justify-content:space-between; background:var(--surface)}
.side-salon-single{font-weight:600}
.side-city-single{font-weight:700; font-size:15px; padding:2px 6px}
/* у админа один салон — город/салон не кликабельны (просто подпись контекста) */
.side-static{cursor:default; color:var(--ink); pointer-events:none}
span.side-salon-single.side-static{background:transparent; border-color:transparent; box-shadow:none}
.side-nav{display:flex; flex-direction:column; gap:1px}
.side-link{position:relative; padding:8px 10px 8px 14px; border-radius:8px; font-weight:600;
  font-size:13.5px; color:var(--ink)}
.side-link:hover{background:rgba(124,92,255,.06)}
/* пункт со счётчиком (новые заявки): число прижато вправо, как каретка у ветки */
.side-link.side-with-count{display:flex; align-items:center; justify-content:space-between; gap:8px}
.side-link.is-active{background:var(--surface); box-shadow:var(--shadow); color:var(--accent-ink)}
.side-link.is-active::before{content:""; position:absolute; left:0; top:7px; bottom:7px; width:3px;
  border-radius:3px; background:var(--grad)}

/* ===== Главная колонка ===== */
.maincol{grid-area:main; min-width:0}

/* ===== Крошки и правая часть топбара ===== */
.crumbs{display:flex; align-items:center; gap:8px; font-size:14px; min-width:0; overflow:hidden}
.crumb{font-weight:600; white-space:nowrap}
.crumb:first-child{color:var(--muted); font-weight:500}
.crumb-sep{color:var(--border-strong)}
/* Узкое окно (разработчик 29.09.2026): крошки обрезались справа ровно по ТЕКУЩЕМУ экрану — «… / Непроверенные
   смены / С». Теперь место уступает звено города, а последнее (где человек сейчас) не сжимается никогда.
   ⚠️ Только в цепочке со звеном города (.crumb-city, base.html crumb_root; ревью 29.09.2026). Раньше правило
   стояло на «первой крошке» всех экранов: у доски первым идёт «Доски», оно сжималось до «Д…», а вес .001
   запирал название доски (.bo-crumb-name, board.css) — кнопки «Поделиться» и «Журнал» уезжали за край
   .crumbs (overflow:hidden). Без города крошки ведут себя, как до правки. */
.crumbs:has(> .crumb-city) > .crumb{min-width:0; overflow:hidden; flex-shrink:.001}
.crumbs:has(> .crumb-city) > .crumb:last-child, .crumbs:has(> .crumb-city) > .crumb-sep,
.crumbs:has(> .crumb-city) > .crumb-action{flex:none}
/* Город виден и в сайдбаре — его звено уступает место первым: сжатие делится пропорционально
   flex-shrink × ширине, и при весах 1 : 0,001 остальные звенья не теряют ни пикселя, пока город не сжат
   до 2em. Многоточие — только у города: у остальных сжатие на доли пикселя (вес не ноль, чтобы в совсем
   узком окне резались они, а не последнее звено) ставило «…» на целое слово — там обрез без многоточия. */
.crumbs:has(> .crumb-city) > .crumb-city{flex-shrink:1; min-width:2em; text-overflow:ellipsis}
.topbar-user .dd-menu{right:0; left:auto}
.topbar .avatar{width:26px;height:26px}

/* ===== Дропдаун (details/summary) ===== */
.dd{position:relative}
.dd summary{list-style:none; cursor:pointer}
.dd summary::-webkit-details-marker{display:none}
.dd-btn{display:inline-flex; align-items:center; gap:7px; font-weight:600;
  padding:7px 12px; border:1px solid var(--border-strong); border-radius:10px;
  background:var(--surface); transition:border-color .15s, box-shadow .15s}
.dd-btn:hover{border-color:var(--accent); box-shadow:0 0 0 3px rgba(124,92,255,.10)}
.caret{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:5px solid var(--muted); margin-left:2px}
.dd-menu{position:absolute; top:calc(100% + 8px); left:0; min-width:200px;
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  box-shadow:var(--shadow-hover); padding:6px; z-index:60; max-height:60vh; overflow:auto}
.dd-right .dd-menu{left:auto; right:0}
.dd-item{display:block; width:100%; text-align:left; padding:9px 11px; border-radius:8px;
  font-size:14px; color:var(--ink); border:0; background:none; cursor:pointer; font-weight:500}
.dd-item:hover{background:var(--bg)}
.dd-item.is-active{color:var(--accent-ink); font-weight:700; background:rgba(124,92,255,.07)}
.dd-head{padding:8px 11px 6px; font-size:12px; color:var(--muted); border-bottom:1px solid var(--border); margin-bottom:4px}
.dd-danger{color:var(--danger)}
.dd-danger:hover{background:rgba(229,72,77,.08)}

/* ===== Пользователь ===== */
.user-btn{padding:5px 10px 5px 6px}
.avatar{width:26px;height:26px;border-radius:50%; background:var(--grad); color:#fff; overflow:hidden;
  display:inline-flex; align-items:center; justify-content:center; font-weight:700; font-size:12px; flex:0 0 auto}
.avatar img{width:100%; height:100%; object-fit:cover; display:block}
.user-name{max-width:170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* редактор аватара в аккаунте */
.avatar-lg{width:72px; height:72px; font-size:28px}
.avatar-edit{display:flex; align-items:center; gap:16px; margin:4px 0 14px}
.av-pick{display:inline-block; cursor:pointer; padding:8px 14px}
.av-remove{display:inline-flex; align-items:center; gap:5px; margin-left:10px; font-size:13px; color:var(--muted); cursor:pointer}

/* ===== Контейнер ===== */
.content{padding:24px 28px 56px; max-width:none}
.wrap{max-width:1180px; margin:0 auto; padding:28px 24px 64px}
.page-head{display:flex; align-items:flex-end; justify-content:space-between; margin:6px 0 22px}
.h1{font-size:26px; font-weight:700; margin:0; letter-spacing:-.4px}
.h2{font-size:15px; font-weight:700; margin:26px 0 12px; display:flex; align-items:center; gap:9px}
.sub{color:var(--muted); margin:5px 0 0}
.count{font-size:12px; font-weight:700; color:var(--accent-ink); background:rgba(124,92,255,.10);
  padding:2px 9px; border-radius:20px}
.muted{color:var(--muted)}
.small{font-size:12px}
.back{display:inline-block; color:var(--muted); font-weight:600; margin-bottom:6px}
.back:hover{color:var(--accent-ink)}

/* ===== Нормализация контролов (одна высота) и табличные цифры ===== */
.field, .btn, .btn-primary, .btn-ghost, .btn-danger, .dd-btn{min-height:36px}
.btn-primary{margin-top:0}
.auth-form .btn-primary{margin-top:16px}
.tbl td, .kpi-n, .et-row, .fresh-chip{font-variant-numeric:tabular-nums}
.tbl td.num, .tbl th.num{text-align:right}

/* ===== Сегмент-переключатель (пресеты периодов) ===== */
.seg{display:inline-flex; border:1px solid var(--border-strong); border-radius:10px; overflow:hidden; background:var(--surface)}
/* font-family: кнопка сегмента — <button>, а кнопка шрифт страницы не наследует: без этой строки
   пресеты периода рисовались системным шрифтом рядом с Inter соседних полей */
.seg-item{padding:10px 14px; font-family:inherit; font-size:13.5px; font-weight:600; color:var(--muted); border:0; background:none; cursor:pointer; white-space:nowrap}
.seg-item + .seg-item{border-left:1px solid var(--border)}
/* Сегмент бывает и <label> с радио внутри — там, где выбор обязан УЕХАТЬ С ФОРМОЙ.
   Кнопка-отправка шлёт на сервер только себя, поэтому соседние фильтры при клике
   терялись (лента Voice, 22.09.2026). Радио прячем, вид держит сам .seg-item;
   :focus-within — чтобы выбор с клавиатуры был виден, раз сам кружок невидим. */
.seg-item{position:relative}
label.seg-item{user-select:none}
.seg-item > input{position:absolute; width:1px; height:1px; margin:0; padding:0;
  opacity:0; pointer-events:none}
.seg-item:focus-within{outline:2px solid var(--accent); outline-offset:-2px}
.seg-item:hover{color:var(--ink); background:var(--bg)}
.seg-item.is-active{color:#fff; background:var(--accent)}

/* ===== Панель фильтров (.toolbar) — в конце файла, см. «Панель фильтров» ===== */
.delta-chip{display:inline-flex; align-items:center; gap:6px; font-size:12.5px; font-weight:700;
  padding:5px 11px; border-radius:999px; background:var(--bg); border:1px solid var(--border)}
.delta-up{color:var(--ok)} .delta-down{color:var(--danger)}
/* Плашка «к прошлому периоду» «Мастера отчётов» (разработчик 05.10.2026): три показателя — оборот 100%,
   оборот, средний день, между ними тонкая вертикальная черта. Черта — бордер токеном границы (в тёмной теме
   свой), а не символ «│»: в тексте плашки и в копировании её нет. Перед первым показателем черты нет. */
.delta-item + .delta-item{border-left:1px solid var(--border-strong); padding-left:8px; margin-left:2px}
/* Ненулевой долг по ЗП в ведомости. Не красный: долг сам по себе не ошибка —
   в Новосибирске так платят всегда, там зарплату выдают мимо дневной кассы. */
.pay-debt{color:var(--warn); font-weight:600}
/* Долг по ЗП (разработчик 30.09.2026): 0 — серый, больше нуля (мы должны) — оранжевый, меньше (переплатили,
   аванс) — красный. Токены темы: в тёмной --warn и --danger свои, светлее. Жирность — только в таблицах:
   у плашки свой кегль. */
.debt-zero{color:var(--muted)}
.debt-pos{color:var(--warn)}
.debt-neg{color:var(--danger)}
td.debt-pos, td.debt-neg{font-weight:600}
/* пояснение под долгом смены: «из 2 000 · закрыт 21.10», куда ушла выплата */
.debt-sub{display:block; margin-top:2px; font-size:11.5px; font-weight:500; color:var(--muted); white-space:normal}
/* плашка-ссылка («Долг на сегодня» ведёт в ведомость с галочкой «Долг») */
.kpi-link{display:block; color:inherit; transition:border-color .14s, box-shadow .14s}
.kpi-link:hover{border-color:var(--accent); box-shadow:var(--shadow-hover)}
.kpi-link:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
/* Страница человека в «Зарплатах»: дата смены — ссылка на смену, строки выплат, окна «Внести выплату» и «Изменить» */
.wp-daylink{color:var(--accent-ink); font-weight:600}
.wp-daylink:hover{text-decoration:underline}
.wp-row td{background:color-mix(in srgb, var(--accent) 5%, transparent)}
.wp-act{text-align:right; white-space:nowrap}
/* долг смены с пояснением и «куда ушла» выплата — переносятся в своей колонке, а не раздвигают таблицу за край */
#tbl-payroll-days td[data-debt], #tbl-payroll-days td.wp-to{white-space:normal; width:150px; min-width:130px}
/* «Зарплаты»: колонки делят ширину по содержимому. С «Ср. клиентов за смену» и «Ср. чек» (разработчик 06.10.2026) при
   общих 104 px на числовую колонку (.tbl th.num) ведомость на ноутбуке 1280 px с открытым меню выходила за край
   панели, а при 1440–1680 px сжимала имена в две строки. Здесь числа — по содержимому, заголовки переносятся по словам
   (низом в одну линию, как в «Мастерах»; «Ср. клиентов» / «за смену» — половинами, span), на широком экране всё в
   строку, и колонки растут вместе с окном. Замер на копии прода 06.10.2026 (Москва, 319 строк, меню открыто): за край
   — нигде; строк, перенесённых в две, при 1440 px — 212 (было 183 при восьми колонках), при 1680 — 46, при 1920 — 0;
   период через 01.10 (двенадцать колонок) при 1440 px помещается — раньше выходил за край на 162 px. */
#tbl-payroll th{white-space:normal; vertical-align:bottom}
#tbl-payroll th.num, #tbl-payroll td.num{width:auto}
#tbl-payroll th span{white-space:nowrap}
.wp-act .linkbtn{margin-left:0}
.wp-modal .field{width:100%}
.wp-modal .cashfact{display:block; margin:12px 0 5px}
.wp-danger{color:var(--danger)}
.wp-del-open{margin-right:auto}
.wp-del{margin-top:14px; padding:10px 12px; border-radius:10px; font-size:13px;
  background:color-mix(in srgb, var(--danger) 10%, transparent); border:1px solid color-mix(in srgb, var(--danger) 35%, transparent)}
.wp-del[hidden]{display:none}
.wp-del-btns{display:flex; gap:8px; margin-top:8px}
/* Погашение долга смены (разработчик 30.09.2026): погашен целиком — зелёный и «✓ выплачено»; частично — число
   оранжевое, «выплачено X» зелёным, «осталось Y» серым. Зелёный — токен --ok (в тёмной теме свой, светлее). */
.debt-paid{color:var(--ok)}
td.debt-paid{font-weight:600}
.debt-sub.debt-paid-mark, .debt-sub.debt-paid-part{color:var(--ok); font-weight:600}
.debt-sub.debt-left{margin-top:0}
/* колонка «Комментарий»: текст переносится, кнопка «Добавить»/«Править» — ссылкой под ним. Ревью 30.09.2026:
   min-width 120, не 150 — у управляющего на 1280 таблица с колонкой «Действия» иначе шире обёртки на ~20px и
   «Изменить» обрезается; текст — не больше 3 строк (500 знаков в узкой колонке растягивали строку на экран),
   целиком — в подсказке и в окне «Править» */
#tbl-payroll-days td.pn-cell{white-space:normal; min-width:120px; max-width:280px}
.pn-text{display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; line-clamp:3; overflow:hidden;
  color:var(--ink); overflow-wrap:anywhere}
.pn-cell .pn-btn{margin-left:0; padding-left:0; font-size:12.5px}
.pn-by{font-size:11.5px; margin-top:2px}
.wp-modal textarea.field{resize:vertical; min-height:84px; font:inherit}
/* «Дата выплаты» и справа способ «На карту | Наличными» (разработчик 30.09.2026, вечер: «возле даты справа»).
   Сегмент — радио внутри .seg-item (см. «Сегмент-переключатель»); выбранный — по :has, без скрипта: окно
   «Изменить» ставит радио из строки, «Внести» сбрасывает формой на «На карту». Высота сегмента — как у поля. */
/* по верху, а не по низу: под сегментом бывает ошибка способа — по низу поле даты съехало бы к её строке */
.wp-date-row{display:flex; align-items:flex-start; gap:12px}
.wp-date-col{flex:1 1 auto; min-width:0}
.wp-method-col{flex:none}
.wp-method{height:40px}
.wp-method .seg-item{display:inline-flex; align-items:center; padding:0 14px}
.wp-method .seg-item:has(> input:checked){color:#fff; background:var(--accent)}
/* фокус с клавиатуры: рамка .seg-item:focus-within — цвета акцента и на выбранном (акцентном) пункте не видна —
   поэтому кольцо вокруг всего переключателя (снимок стенда 30.09.2026) */
.wp-method:has(input:focus-visible){box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent)}
/* ошибка способа (находка ревью 30.09.2026): сами радио невидимы — красная рамка на сегменте, как у поля даты
   (.dp-native.field-error ~ .dp-trigger); текст ошибки переносится по ширине сегмента и колонку не раздвигает */
.wp-method:has(input.field-error){border-color:var(--danger); box-shadow:0 0 0 3px rgba(229,72,77,.12)}
.wp-method-col .cost-err{width:0; min-width:100%}
/* заголовок «Дата» — кнопка сортировки: выглядит как заголовок, стрелка — направление.
   ⚠️ Имя `th-sortbtn`, а не `th-sort`: `th-sort` вешает sortable.js на КАЖДЫЙ заголовок сортируемой таблицы, и
   `display:inline-flex` под тем именем разваливал шапки всех таких таблиц — ведомость «Зарплат» складывалась в два
   столбика (снимок стенда 30.09.2026; замок tests_payout_method.SortableHeaderClassTests) */
.th-sortbtn{background:none; border:0; padding:0; font:inherit; color:inherit; text-transform:inherit;
  letter-spacing:inherit; cursor:pointer; display:inline-flex; align-items:center; gap:4px}
.th-sortbtn:hover{color:var(--ink)}
.th-sortbtn:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:4px}
.th-sort-ic{font-size:11px}

/* ===== KPI ===== */
.mtd-note{font-size:12px; color:var(--muted); margin:0 0 12px}
.kpis{display:grid; grid-template-columns:repeat(auto-fit,minmax(176px,1fr)); gap:14px; margin-bottom:6px}
.kpi{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:16px 18px; box-shadow:var(--shadow); position:relative; overflow:hidden}
.kpi::before{content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--grad)}
.kpi-n{font-size:25px; font-weight:800; letter-spacing:-.5px; white-space:nowrap}
.kpi-n .rub{font-size:15px; font-weight:600; color:var(--muted)}
.kpi-l{font-size:12px; color:var(--muted); margin-top:3px; font-weight:500}
/* вторая сумма плитки мельче — «оборот» под «оборотом 100%» (карточка доски [23.3]) */
.kpi-s{font-size:12px; color:var(--muted); margin-top:5px; white-space:nowrap; font-variant-numeric:tabular-nums}

/* ===== Карточки салонов ===== */
.cards{display:grid; grid-template-columns:repeat(auto-fill,minmax(248px,1fr)); gap:14px}
.card{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:16px 17px; box-shadow:var(--shadow); transition:transform .14s, box-shadow .14s, border-color .14s;
  display:flex; flex-direction:column; gap:9px; min-height:104px}
.card:hover{transform:translateY(-2px); box-shadow:var(--shadow-hover); border-color:var(--border-strong)}
.card-top{display:flex; align-items:center; justify-content:space-between; gap:10px}
.card-title{font-weight:700; font-size:15px}
.card-addr{font-size:12.5px; color:var(--muted); line-height:1.35}
.card-foot{margin-top:auto; display:flex; gap:14px; font-size:12.5px; padding-top:6px; border-top:1px solid var(--border)}
.pill{background:rgba(124,92,255,.10); color:var(--accent-ink); font-weight:700; font-size:12px;
  padding:2px 9px; border-radius:20px; min-width:26px; text-align:center}
.pill-muted{background:var(--bg); color:var(--muted)}
.empty{grid-column:1/-1; color:var(--muted); padding:24px; text-align:center;
  border:1px dashed var(--border-strong); border-radius:var(--radius)}
.salon-search{max-width:300px; margin:0 0 14px}
/* ===== Главная управляющего: карточка салона (замечания владельца 17.09.2026) =====
   Название и адрес — по строке с многоточием, черта и три строки: администратор в смене, полоса
   дневного плана, оборот (100%). Высота шапки у всех карточек одна, и строки в ряду сетки стоят вровень;
   редкие отметки несохранённых смен — внизу, чтобы не сдвигать строки соседей. Цвет полосы и процента
   один: красный < 25 %, жёлтый < 70 %, зелёный ≤ 100 %, золото с бликом — больше плана (полоса
   упирается в край, процент настоящий).
   ⚠️ Жёлтый процент в светлой теме — тот же жёлтый, но темнее полосы (--plan-yellow-ink): яркий жёлтый
   на белой карточке даёт контраст 2,1:1, и «55 %» читалось хуже соседних зелёного и красного. Полоса
   остаётся яркой — иначе жёлтый уровень выглядит хаки и путается с золотом.
   ⚠️ Золотой процент — металлический градиент (--plan-gold-ink), а не ровный цвет, и в обеих темах, и без
   анимации (prefers-reduced-motion): ровным цветом золото в тёмной теме совпадало с жёлтым, а в светлой
   тёмный жёлтый и золото различаются по тону слишком мало. Блик — отдельный слой поверх градиента. */
:root{--plan-red:var(--danger); --plan-yellow:#e2a900; --plan-yellow-ink:#a37d00; --plan-green:var(--ok);
  --plan-gold:#8f5c00; --plan-gold-ink:linear-gradient(180deg,#c28b16 15%,#7d4f00 85%);
  --plan-gold-shine:rgba(255,243,196,.95);
  --plan-gold-fill:linear-gradient(90deg,#9c6b0c 0%,#d9a531 30%,#ffe7a0 50%,#d9a531 70%,#9c6b0c 100%);
  --plan-gold-glow:0 0 7px rgba(217,165,49,.55)}
[data-theme="dark"]{--plan-yellow:#f5c542; --plan-yellow-ink:#f5c542;
  --plan-gold:#e39a30; --plan-gold-ink:linear-gradient(180deg,#ffe29c 15%,#d6861c 85%);
  --plan-gold-shine:rgba(255,255,255,.95);
  --plan-gold-fill:linear-gradient(90deg,#b8860b 0%,#e9bc4c 30%,#fff1bf 50%,#e9bc4c 70%,#b8860b 100%);
  --plan-gold-glow:0 0 9px rgba(242,201,76,.55)}
.salons-head{display:flex; align-items:center; gap:16px; margin:6px 0 14px}
.salons-head .h2{margin:0}
.salons-head .salon-search{margin:0; margin-left:auto; height:40px; padding-top:0; padding-bottom:0}
.salons-head .btn-ghost{height:40px; padding-top:0; padding-bottom:0; display:inline-flex; align-items:center}
.salons-head .h2 + .btn-ghost{margin-left:auto}
.salon-card{gap:4px; min-height:0}
.salon-card .card-title, .sc-addr{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.sc-none{opacity:.6}
.sc-rows{margin-top:8px; padding-top:11px; border-top:1px solid var(--border); display:flex;
  flex-direction:column; gap:8px; font-size:12.5px}
.sc-row{display:flex; align-items:baseline; justify-content:space-between; gap:10px; color:var(--muted)}
.sc-row b{color:var(--ink); font-weight:700; white-space:nowrap; font-variant-numeric:tabular-nums}
.sc-row b.sc-on{color:var(--ok)}
.sc-row b.sc-off{color:var(--danger)}
.sc-noplan{height:14px; align-items:center; justify-content:flex-start; color:var(--muted); opacity:.75}
.sc-plan{display:flex; align-items:center; gap:10px; height:14px}
.sc-bar{position:relative; flex:1; height:8px; border-radius:4px; background:var(--border); overflow:hidden}
.sc-bar i{position:absolute; left:0; top:0; bottom:0; border-radius:4px; background:var(--muted)}
.sc-pct{min-width:40px; text-align:right; font-size:12.5px; font-weight:800; font-variant-numeric:tabular-nums}
.lvl-red .sc-bar i{background:var(--plan-red)}      .lvl-red .sc-pct{color:var(--plan-red)}
.lvl-yellow .sc-bar i{background:var(--plan-yellow)} .lvl-yellow .sc-pct{color:var(--plan-yellow-ink)}
.lvl-green .sc-bar i{background:var(--plan-green)}  .lvl-green .sc-pct{color:var(--plan-green)}
.lvl-gold .sc-bar{overflow:visible}
.lvl-gold .sc-bar i{background:var(--plan-gold-fill); overflow:hidden; box-shadow:var(--plan-gold-glow)}
.lvl-gold .sc-bar i::after{content:""; position:absolute; top:0; bottom:0; left:0; width:45%;
  background:linear-gradient(100deg,transparent 0%,rgba(255,255,255,.85) 50%,transparent 100%);
  transform:translateX(-120%); animation:scShine 2.8s ease-in-out infinite}
.lvl-gold .sc-pct{color:var(--plan-gold);
  background-image:linear-gradient(100deg,transparent 40%,var(--plan-gold-shine) 50%,transparent 60%),var(--plan-gold-ink);
  background-size:300% 100%,100% 100%; background-position:100% 0,0 0; background-repeat:no-repeat;
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
  animation:scShineText 2.8s ease-in-out infinite}
@keyframes scShine{0%{transform:translateX(-120%)} 55%,100%{transform:translateX(260%)}}
@keyframes scShineText{0%{background-position:100% 0,0 0} 55%,100%{background-position:0 0,0 0}}
@media (prefers-reduced-motion:reduce){
  .lvl-gold .sc-bar i::after{animation:none; display:none}
  .lvl-gold .sc-pct{animation:none; background-image:var(--plan-gold-ink); background-size:100% 100%}}
.sc-marks{display:flex; flex-wrap:wrap; gap:4px; margin-top:auto; padding-top:10px}
.head-actions{display:flex; gap:10px; align-items:center; flex-wrap:wrap}
.row-actions{white-space:nowrap}
/* «Пользователи»: действия строки — столбиком. В строку («изменить · копировать логин и пароль ·
   новый пароль · разблокировать») таблица на ноутбуке 1280–1366 px уезжала за край, и «новый
   пароль»/«разблокировать» не было видно вовсе (аудит 22.09, снимок 11-users-1280). */
#users-table .row-actions{white-space:normal;min-width:7em}
#users-table .row-actions-stack{display:flex;flex-direction:column;align-items:flex-start;gap:4px}
#users-table .row-actions-stack .linkbtn{padding-left:0;margin-left:0;text-align:left}
/* «Пользователи» плотнее обычной таблицы: девять колонок и строка поиска должны влезать в
   ноутбук 1280 px с открытым меню (замер 23.09: было 1055 px при 946 доступных). Точка
   «онлайн» — в ячейке логина, а не отдельной колонкой. */
#users-table{font-size:13px}
#users-table th, #users-table td{padding-left:7px; padding-right:7px}
#users-table .td-name{white-space:nowrap}
#users-table .td-name .pres-dot{margin-right:7px; vertical-align:1px}
#users-table .pres-none{visibility:hidden}     /* место точки у чужих учёток: логины в одну линию */
#users-table .ip-addr{white-space:nowrap}      /* флаг может уйти строкой выше, адрес — целиком */
/* строка поиска под заголовками (users_list.js). Поля узкие и без заглавных букв заголовка;
   заполненное подсвечено, чтобы было видно, почему строк стало меньше. */
#users-table .filter-row th{padding:0 7px 8px; text-transform:none; letter-spacing:0; font-weight:400;
  vertical-align:top}
#users-table .col-filter{min-height:30px; padding:5px 8px; font-size:12.5px; border-radius:8px;
  min-width:0}
#users-table select.col-filter{padding-right:18px; background-position:right 6px center; background-size:9px 6px}
#users-table .col-filter.is-set{border-color:var(--accent); background-color:rgba(124,92,255,.06)}
#users-table .filter-row .linkbtn{margin:6px 0 0; font-size:12.5px}
#users-table tr[hidden]{display:none}
#users-table .seen-at{white-space:nowrap; color:var(--ink)}
#users-table .empty-row .linkbtn{margin-left:0}
.linkbtn{border:0; background:none; color:var(--accent-ink); cursor:pointer; font:inherit; padding:0; margin-left:10px}
.linkbtn:hover{text-decoration:underline}
.day-flag{font-size:11px; font-weight:700; white-space:nowrap}
.day-flag.on{color:var(--ok)}
.day-flag.off{color:var(--border-strong)}

/* ===== Две колонки: специальности + ростер ===== */
.cols{display:grid; grid-template-columns:300px 1fr; gap:16px; align-items:start; margin-top:8px}
.panel{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:16px 18px; box-shadow:var(--shadow)}
.panel .h2{margin-top:4px}
.panel-head{display:flex; align-items:center; justify-content:space-between}
.bars{display:flex; flex-direction:column; gap:2px}
.bar-row{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:7px 8px; border-radius:8px}
.bar-row:hover{background:var(--bg)}
.bar-l{font-size:13px}
.bar-n{font-weight:700; font-size:13px; color:var(--accent-ink); background:rgba(124,92,255,.08);
  padding:1px 8px; border-radius:14px}

/* ===== Таблица мастеров ===== */
.tbl{width:100%; border-collapse:collapse; font-size:13.5px}
.tbl th{text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.5px;
  color:var(--muted); font-weight:700; padding:8px 10px; border-bottom:1px solid var(--border)}
.tbl td{padding:9px 10px; border-bottom:1px solid var(--border)}
.tbl tr:last-child td{border-bottom:0}
.tbl tr:hover td{background:var(--bg)}
/* td-name — обычная table-cell: display:flex на <td> ломает высоту ячейки
   (разрывы фона и «поехавшие» линии-разделители в строках) */
.td-name{font-weight:600}
/* «ещё в: …» под именем в «Мастерах» — салоны других карточек человека (24.09.2026) */
.td-name .also-in{font-weight:400; margin-top:2px}
/* цветная точка мастера: в списках убрана (цвет из легаси красил сетку графика,
   которой у нас нет). Правило оставлено на случай экрана смен, поле color синк пишет. */
.dot{width:9px;height:9px;border-radius:50%; display:inline-block; flex:0 0 auto; box-shadow:0 0 0 1px var(--border-strong)}
.td-name .dot{margin-right:6px; vertical-align:-1px}
/* кликабельное число издержек в отчётах → открывает разбивку */
.cost-link{color:inherit; text-decoration:none; border-bottom:1px dashed var(--border-strong)}
.cost-link:hover{color:var(--accent-ink); border-bottom-color:var(--accent)}
.flags{text-align:right; white-space:nowrap}
.tag{display:inline-block; font-size:11px; font-weight:600; color:var(--muted);
  background:var(--bg); border:1px solid var(--border); padding:1px 7px; border-radius:12px; margin-left:4px}
.tag.tag-warn{color:#8a5600; background:rgba(245,159,0,.12); border-color:rgba(245,159,0,.3)}
.tag.tag-bad{color:#c0392b; background:rgba(220,80,60,.12); border-color:rgba(220,80,60,.3)}
/* На тёмном те же цвета почти не читались (замер 18.09: «нет доступа к салону» 2.74:1 при
   11 px). Светлее и чуть плотнее фон — предупреждение должно быть видно. */
[data-theme="dark"] .tag.tag-warn{color:#ffc257; background:rgba(245,159,0,.18); border-color:rgba(245,159,0,.42)}
[data-theme="dark"] .tag.tag-bad{color:#ff9d8f; background:rgba(220,80,60,.2); border-color:rgba(220,80,60,.45)}
/* Колонки с деньгами: числа вправо, ширина фиксированная — иначе соседние строки
   разъезжаются, потому что «960» и «1 400» занимают разную ширину. */
.tbl th.num, .tbl td.num{text-align:right; white-space:nowrap; width:104px}
.pager{display:flex; align-items:center; justify-content:center; gap:18px; margin-top:14px; font-weight:600}
.pager a{color:var(--accent-ink)} .pager a:hover{text-decoration:underline}
.pager .off{color:var(--border-strong)} .pager .pg{color:var(--muted)}

/* параметры расчёта: одинаковая сетка колонок во всех таблицах городов
   (.tbl.tbl-params — иначе проигрывает более позднему .tbl{min-width:420px}) */
.tbl.tbl-params{table-layout:fixed; min-width:640px}
/* узкие числовые заголовки («ЧАЙ: МАСТЕРУ / АДМИНУ», «НАЛОГ (СТАВКА ГОРОДА)») переносятся, а не наезжают
   на соседние: nowrap общего .tbl th.num в таблице с fixed-раскладкой выпускал их за край колонки */
.tbl-params th.num{white-space:normal}
.tbl-params td{overflow-wrap:break-word}

/* навигация по дате (город за день): стрелки + дата в одну линию */
.datenav{display:flex; align-items:center; gap:8px}
.datenav .field{margin:0; width:auto}
.datenav .btn-ghost{padding:0 12px; text-decoration:none; display:inline-flex; align-items:center; color:var(--ink)}
.datenav .is-off{opacity:.45; pointer-events:none}

/* настройка столбцов (columns.js): кнопка над таблицей + меню */
.cols-bar{display:flex; justify-content:flex-end; margin:-4px 0 8px}
.panel-head{display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 12px}
.panel-head .h2{margin:0}
.cols-btn{display:inline-flex; align-items:center; font-size:12.5px; font-weight:600; color:var(--muted);
  padding:5px 11px; border:1px solid var(--border); border-radius:8px; background:var(--surface); cursor:pointer}
.cols-btn:hover{border-color:var(--accent); color:var(--accent-ink)}
.cols-menu{min-width:240px}
.cols-row{display:flex; align-items:center; gap:2px; padding:3px 6px; border-radius:8px}
.cols-row:hover{background:var(--bg)}
.cols-row label{display:flex; align-items:center; gap:8px; flex:1; font-size:13.5px; cursor:pointer; min-width:0}
.cols-row label span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.cols-move{border:0; background:none; cursor:pointer; color:var(--muted); font-size:13px;
  padding:3px 6px; border-radius:6px; line-height:1}
.cols-move:hover{color:var(--ink); background:var(--border)}
.cols-move:disabled{opacity:.25; cursor:default}
.cols-move:disabled:hover{color:var(--muted); background:none}
.cols-reset{margin-top:4px; border-top:1px solid var(--border); border-radius:0 0 8px 8px; color:var(--muted)}

/* сортируемые заголовки таблиц (sortable.js) */
.th-sort{cursor:pointer; user-select:none}
.th-sort:hover{color:var(--ink)}
.th-sort[aria-sort]{color:var(--accent-ink)}
.th-sort[aria-sort]::after{content:'▲'; font-size:8px; margin-left:4px; vertical-align:1px}
.th-sort[aria-sort="descending"]::after{content:'▼'}

/* свёрнутый список «не закрыли день» под таблицей */
.missing-days{margin-top:12px}
.missing-days summary{cursor:pointer; font-size:13.5px; font-weight:600; color:var(--muted); user-select:none}
.missing-days summary:hover{color:var(--ink)}
.missing-days p{margin:8px 2px 0}

/* Присутствие пользователей (онлайн/офлайн) */
.pres-dot{display:inline-block; width:8px; height:8px; border-radius:50%; flex:0 0 auto}
.pres-on{background:var(--ok); box-shadow:0 0 0 3px rgba(22,163,106,.18)}
.pres-off{background:var(--border-strong)}

/* Город за день: салон не закрыл день — красная строка (видно халтуру) */
.row-unclosed td{background:rgba(229,72,77,.07)}
.row-unclosed .td-name a{color:var(--danger)}
.row-unclosed .td-name a:hover{text-decoration:underline}
.day-unclosed{text-align:left; color:var(--danger); font-weight:600; opacity:.75}
.bad-tag{display:inline-block; font-size:11px; font-weight:700; color:#fff; background:var(--danger);
  padding:1px 8px; border-radius:12px; white-space:nowrap}

/* Издержки по дням: раскрываемые строки */
.cost-days{display:flex; flex-direction:column; gap:6px}
.cost-day{border:1px solid var(--border); border-radius:10px; background:var(--surface); overflow:hidden}
.cost-day > summary{list-style:none; cursor:pointer; display:flex; align-items:center; gap:14px;
  padding:12px 14px; font-weight:600}
.cost-day > summary::-webkit-details-marker{display:none}
.cost-day > summary:hover{background:var(--bg)}
.cost-day .spacer{flex:1}
.cost-day-date{font-variant-numeric:tabular-nums}
.cost-day-meta{font-size:12.5px; font-weight:500}
.cost-day-sum{font-variant-numeric:tabular-nums; font-weight:700}
.cost-day-caret{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;
  border-top:6px solid var(--muted); transition:transform .15s; flex:0 0 auto}
.cost-day[open] .cost-day-caret{transform:rotate(180deg)}
.cost-day .table-wrap{border-top:1px solid var(--border)}
.cost-day-tbl{margin:0; min-width:0; table-layout:fixed}
.cost-day-tbl td, .cost-day-tbl th{padding:8px 14px; overflow:hidden; text-overflow:ellipsis}
.cost-day-tbl td.num, .cost-day-tbl th.num{white-space:nowrap}
/* «По статьям» — те же раскрывающиеся строки (владелец 25.09.2026). Название статьи тянется, а записи,
   доля и сумма стоят колонками фиксированной ширины: у названий разной длины они иначе разъезжались
   бы лесенкой. Строка «Итого» — та же сетка, только не раскрывается. */
.cost-art-name{flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.cost-art-n{flex:0 0 108px; text-align:right; white-space:nowrap}
.cost-art-wait, .cost-art-more{margin:0; padding:10px 15px; border-top:1px solid var(--border)}
.cost-art-pct{flex:0 0 56px; text-align:right; font-variant-numeric:tabular-nums}
.cost-art .cost-day-sum, .cost-art-total .cost-day-sum{flex:0 0 140px; text-align:right}
.cost-art-total{display:flex; align-items:center; gap:14px; padding:12px 15px; font-weight:700}
.cost-art-total .cost-day-caret{visibility:hidden}
/* фильтр «Статья»: галочки в списке; длинное название на кнопке обрезается, целиком — в подсказке */
.cost-types-name{display:inline-block; max-width:170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* список — от правого края кнопки: поле стоит в правой части панели, и от левого края список уходил
   за край экрана */
.cost-types-menu{min-width:240px; max-width:340px}
.cost-types-dd .dd-menu.cost-types-menu{left:auto; right:0}
.cost-types-menu .dd-check{display:flex; align-items:flex-start; gap:9px; font-weight:500}
.cost-types-menu .dd-check input{margin:3px 0 0; flex:none}
/* «Все статьи» и «Применить» прилипают к низу: у Москвы статей десятки, и прокручивать до кнопки
   ради применения — лишний шаг */
.cost-types-foot{display:flex; align-items:center; justify-content:space-between; gap:10px;
  position:sticky; bottom:-6px; margin:4px -6px -6px; padding:8px 12px; background:var(--surface);
  border-top:1px solid var(--border)}
.cost-types-foot .linkbtn{margin-left:0}
.cost-types-foot .btn-primary{margin-top:0; padding:7px 14px}
/* окно «+ Издержка» (finance/costbook.py): ошибка — строкой под своим полем, как в смене подсветка;
   «за весь месяц» — рядом с датой, по её нижнему краю */
.cost-err{display:block; margin-top:4px; font-size:12px; color:var(--danger)}
.cost-err[hidden]{display:none}
.cost-add-date{display:flex; align-items:flex-end; gap:14px}
.cost-add-date .cashfact{flex:1 1 auto}
.cost-add-month{display:flex; align-items:center; gap:6px; min-height:40px; font-size:13px; max-width:55%}
.cost-add-month input{margin:0; flex:none}
.cost-add-month .muted{font-size:12px}
/* сумма так, как её поймёт сервер (costs.js) — под полем, до «Внести» */
.cost-amount-echo{display:block; min-height:16px; margin-top:4px}
/* ошибка даты: само поле спрятано под календарём (datepicker.js) — подсветка на его кнопке */
.dp-native.field-error ~ .dp-trigger{border-color:var(--danger); box-shadow:0 0 0 3px rgba(229,72,77,.12)}
#cost-add .note-box{margin:0 0 6px}
.cost-manual-tbl{table-layout:fixed}
.cost-manual-tbl td{overflow:hidden; text-overflow:ellipsis}
.cost-manual-tbl tfoot td{font-weight:700}

/* ===== Издержки из Excel (разработчик 28.09.2026, finance/views_costimport.py, costimport.js) =====
   Окно загрузки с зоной для файла, контрольный список (красные строки — ошибка, жёлтые — похоже на повтор,
   бледные — «не вносить»), итог внесения и журнал. Цвета — от переменных темы: красный — --danger, жёлтый —
   тон --warn, как у .tag-warn; заливки строк свои, чтобы в тёмной теме не выгорали в пятна. */
:root{--ci-bad-bg:rgba(229,72,77,.055); --ci-dup-bg:rgba(245,159,0,.09); --ci-dup-ink:#8a5600;
  --ci-dup-line:rgba(245,159,0,.35); --ci-ok-bg:rgba(22,163,106,.08); --ci-ok-ink:#0f7a4d}
[data-theme="dark"]{--ci-bad-bg:rgba(255,107,111,.085); --ci-dup-bg:rgba(255,178,36,.09); --ci-dup-ink:#ffc257;
  --ci-dup-line:rgba(255,178,36,.38); --ci-ok-bg:rgba(46,201,138,.12); --ci-ok-ink:#63e2ab}
.ci-modal{max-width:560px}
.ci-modal .modal-text{margin-bottom:10px}
.ci-tpl{margin:0 0 14px; font-size:13px}
.ci-tpl-link{color:var(--accent-ink); font-weight:600; text-decoration:underline; text-underline-offset:3px}
.ci-drop{position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px;
  min-height:150px; padding:22px 18px; border:1.5px dashed var(--border-strong); border-radius:12px;
  background:var(--bg); color:var(--muted); text-align:center; cursor:pointer; transition:border-color .15s, background-color .15s}
/* рамка фокуса — только с клавиатуры (:focus-visible): окно ставит фокус в поле само, и зона с мышью
   выглядела бы уже «наведённой» */
.ci-drop:hover, .ci-drop:has(input:focus-visible){border-color:var(--accent)}
.ci-drop.is-over{border-style:solid; border-color:var(--accent); background:var(--mtx-hl-strong)}
.ci-drop.is-busy{border-style:solid; cursor:progress}
.ci-drop-ic{color:var(--accent); margin-bottom:2px}
.ci-drop-text{font-size:14px; font-weight:600; color:var(--ink); overflow-wrap:anywhere}
.ci-pick{color:var(--accent-ink); text-decoration:underline; text-underline-offset:3px}
.ci-drop-hint{font-size:12px}
.ci-drop input[type=file]{margin-top:6px; font:inherit; font-size:13px; color:var(--ink); max-width:100%}
/* Со скриптом поле — под зоной: щелчок по зоне открывает выбор файла, фокус с клавиатуры виден рамкой зоны
   (:focus-visible); файл уходит сразу, кнопка «Загрузить» не нужна. Без скрипта — видно и поле, и кнопку. */
.ci-upload.is-js .ci-drop input[type=file]{position:absolute; left:0; top:0; width:1px; height:1px; margin:0;
  opacity:0; overflow:hidden; pointer-events:none}
.ci-upload.is-js [data-ci-send]{display:none}
.ci-upload.is-js .modal-actions:not(:has(.btn-ghost)){display:none}
.ci-check > .panel{margin-bottom:16px}
.ci-check > .note-box{margin:0 0 16px}
.ci-head{display:flex; align-items:flex-start; flex-wrap:wrap; gap:14px 18px}
.ci-fld{display:flex; flex-direction:column; flex:0 0 210px}
.ci-fld-wide{flex-basis:300px}
.ci-fld .lbl{margin:0 0 6px}
.ci-head-note{flex:1 1 240px; margin:30px 0 0; line-height:1.45}
.ci-list-head{display:flex; align-items:baseline; flex-wrap:wrap; gap:4px 16px; margin:0 0 10px}
.ci-list-head .h2{margin:4px 0 0}
.ci-list-head .cost-err{margin:0; font-size:13px}
.ci-list-head .cost-err[hidden]{display:none}
.ci-list-dup{margin:0; font-size:13px; color:var(--ci-dup-ink)}
.ci-table{table-layout:fixed}
.ci-table td{vertical-align:top; padding:6px 8px}
.ci-table .field{padding:7px 10px; min-height:34px; font-size:13.5px}
.ci-table select.field{padding-right:32px; text-overflow:ellipsis}
.ci-table .cost-err{line-height:1.35}
.ci-n{color:var(--muted); padding-top:14px !important}
/* строки: полоса слева — цвет состояния, заливка — его тон (и под курсором: общая подсветка .tbl её не перебивает) */
.ci-table .ci-row.is-bad td{background:var(--ci-bad-bg)}
.ci-table .ci-row.is-bad td:first-child{box-shadow:inset 3px 0 0 var(--danger)}
.ci-table .ci-row.is-dup td, .ci-table tr.ci-dupnote td{background:var(--ci-dup-bg)}
.ci-table .ci-row.is-dup td:first-child, .ci-table .ci-dupnote td:first-child{box-shadow:inset 3px 0 0 var(--warn)}
.ci-table .ci-row.is-dup td{border-bottom:0}
.ci-table tr.ci-dupnote td{padding-top:0; font-size:12.5px; color:var(--ci-dup-ink)}
/* «не вносить» — строка бледнеет и уходит из итога; галочка остаётся яркой, чтобы вернуть */
.ci-table .ci-row.is-skip td, .ci-table tr.ci-dupnote.is-skip td{background:none; box-shadow:none}
.ci-table .ci-row.is-skip td:not(.ci-skip), .ci-table .ci-dupnote.is-skip td{opacity:.42}
.ci-table .ci-row.is-skip .field{text-decoration:line-through}
.ci-table .ci-row.is-skip .cost-err{display:none}
.ci-skip, .ci-skip-h{text-align:center !important}
.ci-skip-h{white-space:nowrap}
/* суммы — вправо, как во всех денежных колонках: разряды разных строк встают друг под другом */
.ci-table .ci-amount{text-align:right; font-variant-numeric:tabular-nums}
.ci-table th.ci-sum-h{text-align:right; padding-right:18px}
.ci-skip-box{display:inline-flex; align-items:center; justify-content:center; width:34px; height:34px;
  border-radius:8px; cursor:pointer}
.ci-skip-box:hover{background:var(--bg)}
.ci-skip-box input{width:16px; height:16px; margin:0; cursor:pointer; accent-color:var(--accent)}
.ci-hint{border:0; background:none; padding:0; font:inherit; color:var(--accent-ink); cursor:pointer;
  text-decoration:underline dotted; text-underline-offset:2px}
.ci-hint:hover{text-decoration-style:solid}
.ci-skipped{margin:12px 2px 0}
.ci-foot{display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px 16px;
  margin-top:14px; padding-top:14px; border-top:1px solid var(--border)}
.ci-total{margin:0; font-size:14px; color:var(--muted)}
.ci-total b{color:var(--ink); font-variant-numeric:tabular-nums}
.ci-foot .ci-actions{margin-top:0}
/* .note-box объявлен ниже по файлу — без двойного класса его фон перебивал жёлтый */
.note-box.ci-note-dup{background:var(--ci-dup-bg); color:var(--ci-dup-ink); border-color:var(--ci-dup-line)}
.ci-note-dup a{text-decoration:underline; text-underline-offset:2px}
.ci-title .ci-state{vertical-align:4px; font-size:11.5px; margin-left:8px; letter-spacing:0}
.tag.ci-state.is-done{color:var(--ci-ok-ink); background:var(--ci-ok-bg); border-color:transparent}
.ci-done{display:flex; align-items:center; gap:14px; padding:14px 16px; margin:0 0 14px;
  border-radius:12px; background:var(--ci-ok-bg)}
.ci-done p{margin:0}
.ci-done-ic{color:var(--ok); flex:none}
.ci-done-main{font-weight:700; font-size:15px; margin-bottom:2px !important}
.ci-done-open{margin-left:auto; flex:none}
.ci-done-tbl td{overflow:hidden; text-overflow:ellipsis}
.ci-deleted{margin:10px 2px 0}
.ci-failed > .note-box{margin-bottom:6px}
.ci-failed-tpl{margin:0 0 12px}
.ci-failed .ci-upload{max-width:560px}
.ci-when{white-space:nowrap}
.ci-file{min-width:190px}
.ci-why{display:block; margin-top:4px; font-size:12px; color:var(--muted); line-height:1.35}
.ci-file-name{color:var(--accent-ink); font-weight:600; overflow-wrap:anywhere}
.ci-file-dl{margin-left:10px; font-size:12px; color:var(--muted); text-decoration:underline; text-underline-offset:2px}
.ci-file-dl:hover{color:var(--accent-ink)}
.ci-journal td{vertical-align:top}
.ci-journal-state{min-width:170px}
/* метка состояния переносится: в одну строку «внесено 6 записей на 198 650,50» выталкивала «Сумму» за край */
.ci-journal-state .tag{margin-left:0}
/* «из файла» во «Внесённых вне смены» — метка-ссылка на загрузку */
.tag.ci-mark{color:var(--accent-ink); cursor:pointer}
.tag.ci-mark:hover{border-color:var(--accent)}

/* ===== Сообщения ===== */
.messages{margin-bottom:16px}
.msg{padding:10px 14px; border-radius:10px; margin-bottom:8px; font-weight:500}
.msg-error{background:#fdecec; color:#b42318}
.msg-success{background:#e9f9f1; color:#0f7a4d}
/* warning и info у messages Django были без фона: итоги кнопок («нет карточки…», «YClients не
   ответил…») стояли под плашкой успеха голым текстом (ревью «Кто где сегодня» 17.09) */
.msg-warning{background:#fff4e0; color:#8a5300}
.msg-info{background:#eef6ff; color:#1b4a7a}
/* итог «Уволить» с «Отменить» в строке (templates/catalog/_batch_done.html) */
.batch-done .batch-undo{display:inline}
/* окно «Уволить» (templates/catalog/_dismiss_modal.html, static/js/dismiss.js) — «Мастера» и страница человека:
   карточки с галочками по салонам, у группового — «Имя — салоны»; недоступное — серым с причиной, график YClients —
   предупреждением. Окно длинное у группового увольнения — прокручивается внутри, кнопки уезжают вниз вместе с ним */
.dismiss-modal{max-width:560px; max-height:calc(100vh - 40px); overflow-y:auto}
.dismiss-list{list-style:none; margin:0 0 14px; padding:0}
.dismiss-list li{padding:7px 0; border-bottom:1px solid var(--border); font-size:14px}
.dismiss-list li:last-child{border-bottom:0}
.dismiss-list label{display:flex; align-items:flex-start; gap:10px; cursor:pointer}
.dismiss-list input{flex:none; width:16px; height:16px; margin:2px 0 0}
.dismiss-list li.is-off{color:var(--muted)}
.dismiss-list li.is-off label{cursor:not-allowed}
.dismiss-why{display:block; margin-left:26px; font-size:12.5px; color:var(--muted)}
.dismiss-people .dismiss-why{margin-left:0}
.dismiss-warn{margin:0 0 12px; font-size:13.5px; color:var(--warn)}
.dismiss-note{margin:0 0 12px; font-size:13.5px}
.dismiss-err{margin:0; font-size:13px}
/* «Журнал штата» (templates/portal/staff_journal.html): карточки записи — раскрытием под «Что» */
.sj-when{white-space:nowrap}
.sj-cards{margin-top:4px}
.sj-cards summary{cursor:pointer; font-size:12.5px; color:var(--muted); user-select:none}
.sj-cards summary:hover{color:var(--ink)}
.sj-cards ul{margin:6px 0 2px; padding-left:18px}
.sj-undo{display:inline}
.sj-undo .linkbtn{margin-left:0}

/* ===== Пустой стейт ===== */
.empty-state{max-width:440px; margin:14vh auto 0; text-align:center; display:flex;
  flex-direction:column; align-items:center; gap:6px}
.empty-state .sub{margin-bottom:18px}

/* ===== Логин =====
   Экран входа ВСЕГДА тёмный, независимо от выбранной темы приложения: это порог,
   а не рабочее место. Поэтому здесь СВОЯ палитра, а не переменные --bg/--surface —
   иначе скрипт темы в base.html перекрасил бы экран у тех, у кого светлая.

   Шрифты названы AuraSans/AuraMono намеренно: если объявить их как 'Inter',
   шрифт подхватит ВСЁ приложение (в body он уже указан первым), а это совсем
   другое по масштабу изменение. Здесь — только вход.

   Поля приходят из Python с классом .field, поэтому базовые стили приложения
   перекрываются через обёртку .lg-page (специфичность 0,2,0 против 0,1,0). */
@font-face{font-family:'AuraSans';font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/inter-400-cyrillic.22ca3f6004fb.woff2") format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'AuraSans';font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/inter-400-latin.aedc4605f3b0.woff2") format('woff2')}
@font-face{font-family:'AuraSans';font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/inter-500-cyrillic.22ca3f6004fb.woff2") format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'AuraSans';font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/inter-500-latin.aedc4605f3b0.woff2") format('woff2')}
@font-face{font-family:'AuraSans';font-style:normal;font-weight:600;font-display:swap;src:url("../fonts/inter-600-cyrillic.22ca3f6004fb.woff2") format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'AuraSans';font-style:normal;font-weight:600;font-display:swap;src:url("../fonts/inter-600-latin.aedc4605f3b0.woff2") format('woff2')}
@font-face{font-family:'AuraMono';font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/jetbrains-mono-400-cyrillic.775ea040fc36.woff2") format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'AuraMono';font-style:normal;font-weight:400;font-display:swap;src:url("../fonts/jetbrains-mono-400-latin.73309d4ee2de.woff2") format('woff2')}
@font-face{font-family:'AuraMono';font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/jetbrains-mono-500-cyrillic.775ea040fc36.woff2") format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'AuraMono';font-style:normal;font-weight:500;font-display:swap;src:url("../fonts/jetbrains-mono-500-latin.73309d4ee2de.woff2") format('woff2')}

.lg-page{--lg-bg:#0a0b0f; --lg-ink:#eceef4; --lg-muted:#9c9daa; --lg-faint:#858793;
  --lg-accent:#a88cff; --lg-danger:#ff6b6f;
  --lg-sans:'AuraSans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --lg-mono:'AuraMono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  position:relative; min-height:100dvh; display:flex; flex-direction:column;
  background:var(--lg-bg); color:var(--lg-ink); font-family:var(--lg-sans);
  -webkit-font-smoothing:antialiased}
/* фон страницы за пределами main (overscroll) — тоже тёмный */
html:has(.lg-page), body:has(.lg-page){background:#080a10}

/* Сцена (концепт входа 2.1, ~/Desktop/aura-brandkit/AURA-UI-Concepts/01-Login; внедрён 03.10.2026):
   атмосфера → SVG-контуры с импульсами (registration/_contours.html) → затенение → зерно.
   Импульсы бегут сразу, без скрипта; при «уменьшении движения» в системе стоят на месте. */
.lg-scene{position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; background:#080a10}
.lg-scene > *{position:absolute; inset:0; width:100%; height:100%}
.lg-atmos{background:radial-gradient(ellipse at 6% 10%,rgba(96,70,167,.19),transparent 40%),
  radial-gradient(ellipse at 96% 89%,rgba(117,57,111,.14),transparent 35%),
  linear-gradient(130deg,#0d111a,#090d13 48%,#0c0d17)}
.lg-contours{display:block; isolation:isolate}
/* свечение — CSS-размытием слоя (видеокарта), а не SVG-фильтром: тот в Safari считается процессором каждый кадр
   и на iPhone давал «шаги» (03.10.2026). Радиус — как был stdDeviation 13 и 3.2 в единицах viewBox 1920×1080
   при обрезке slice: масштаб = max(ширина/1920, высота/1080). */
.lg-halo{filter:blur(max(.677vw, 1.204vh))}
.lg-near{filter:blur(max(.1667vw, .2963vh))}
.lg-halo, .lg-near, .lg-core{will-change:transform}
.lg-flow{animation:lgflow 16s linear infinite}
.lg-flow.f-violet{animation-duration:14s; animation-delay:-3.8s}
.lg-flow.f-twin{visibility:hidden; animation-duration:14s; animation-delay:-10.8s}
.lg-flow.f-rose{animation-duration:18s; animation-delay:-10s}
.lg-flow.f-upper{animation-duration:24s; animation-delay:-15s; opacity:.62}
.lg-flow.f-lower{animation-duration:26s; animation-delay:-6.5s; opacity:.48}
@keyframes lgflow{from{stroke-dashoffset:0} to{stroke-dashoffset:1000}}
@media (max-width:700px){.lg-flow.f-twin{visibility:visible}}
@media (prefers-reduced-motion:reduce){.lg-flow{animation-play-state:paused}}
.lg-shade{background:linear-gradient(90deg,transparent 32%,rgba(5,7,12,.11) 68%,transparent),
  linear-gradient(180deg,rgba(4,5,10,.06),transparent 25%,transparent 80%,rgba(4,5,10,.1))}
.lg-grain{opacity:.023; mix-blend-mode:screen;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' opacity='.6' filter='url(%23n)'/%3E%3C/svg%3E")}

.lg-shell{position:relative; z-index:3; flex:1; display:flex; flex-direction:column;
  max-width:1180px; width:100%; margin:0 auto; padding:0 clamp(24px,4.5vw,56px)}
.lg-head{display:flex; align-items:center; justify-content:space-between; padding:27px 0 0; min-height:48px}
.lg-wordmark{display:inline-flex; align-items:center; gap:10px; color:var(--lg-ink)}
.lg-wordmark .aura-symbol{width:24px; height:24px}
.lg-wordmark .aura-wordmark{width:76px}
.lg-main{flex:1; display:grid; grid-template-columns:minmax(0,1fr) minmax(0,392px);
  gap:clamp(32px,6vw,88px); align-items:center; padding:42px 0 80px}
.lg-eyebrow{font-family:var(--lg-mono); font-size:10px; line-height:1.7; letter-spacing:.3em;
  text-transform:uppercase; color:#bcabff; margin:0 0 24px; display:flex; align-items:center; gap:14px;
  white-space:nowrap; text-shadow:0 1px 14px rgba(0,0,0,.6)}
.lg-eyebrow::before{content:''; width:26px; height:1px; flex:none; background:var(--lg-accent);
  box-shadow:0 0 12px rgba(168,140,255,.45)}
.lg-side .saxap-logo{width:190px; filter:brightness(0) invert(1) drop-shadow(0 4px 20px rgba(0,0,0,.2)); opacity:.96}

/* панель — матовое стекло над контурами: свой фон, рамка, тень и подсветка снизу */
.lg-panel{position:relative; padding:30px 28px 28px; border-radius:10px;
  background:linear-gradient(180deg,rgba(20,22,30,.95),rgba(15,17,25,.96));
  border:1px solid rgba(236,238,244,.18);
  box-shadow:0 40px 90px rgba(0,0,0,.46), 0 8px 24px rgba(0,0,0,.33),
             inset 0 1px 0 rgba(255,255,255,.045);
  -webkit-backdrop-filter:blur(20px); backdrop-filter:blur(20px)}
.lg-panel::before{content:''; position:absolute; inset:-32px -18px -22px; z-index:-1; border-radius:22px;
  background:radial-gradient(60% 55% at 50% 45%, rgba(143,115,255,.13), transparent 72%);
  filter:blur(14px); pointer-events:none}
/* приводочные метки вынесены НАРУЖУ: на скруглённой рамке они с ней сливаются */
.lg-tick{position:absolute; width:13px; height:13px; border:1.5px solid var(--lg-accent); pointer-events:none}
.lg-tick.tl{top:-7px; left:-7px; border-right:0; border-bottom:0}
.lg-tick.br{bottom:-7px; right:-7px; border-left:0; border-top:0}
.lg-panel h1{font-size:19px; font-weight:600; line-height:1.25; letter-spacing:-.02em; margin:0 0 24px}
.lg-sub{font-size:13px; line-height:1.55; color:#b0b0bb; margin:-8px 0 20px}

.lg-page .lbl{display:block; font-family:var(--lg-mono); font-size:9.5px; line-height:1.4; letter-spacing:.22em;
  text-transform:uppercase; color:var(--lg-muted); margin:0 0 8px; font-weight:400}
.lg-page .field{display:block; width:100%; min-height:46px; padding:12px 13px; font-size:14px; line-height:20px;
  font-family:var(--lg-sans); color:var(--lg-ink); background:rgba(0,0,0,.3);
  border:1px solid rgba(236,238,244,.15); border-radius:6px; margin-bottom:18px;
  transition:border-color .2s, box-shadow .2s, background .2s}
.lg-page .field::placeholder{color:#777986; opacity:1}
.lg-page .field:focus{outline:none; border-color:#a58aff; background:rgba(143,115,255,.06);
  box-shadow:0 0 0 3px rgba(143,115,255,.14)}
.lg-page .field:-webkit-autofill{-webkit-text-fill-color:var(--lg-ink); box-shadow:0 0 0 1000px #191824 inset}
.lg-page .pw-wrap{position:relative; display:block; margin-bottom:18px}
.lg-page .pw-wrap .field{padding-right:46px; margin-bottom:0}
.lg-page .pw-toggle{position:absolute; right:1px; top:50%; transform:translateY(-50%);
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  border:0; border-radius:6px; background:none; color:#8b8d99; cursor:pointer; transition:color .2s, background .2s}
.lg-page .pw-toggle:hover{color:#c9bcff; background:rgba(143,115,255,.09)}
.lg-page .pw-toggle.on{color:#c9bcff}
.lg-page .eye-slash{display:none}
.lg-page .pw-toggle.on .eye-slash{display:block}
.lg-page .check{display:flex; align-items:center; gap:9px; min-height:28px; width:fit-content;
  font-size:13px; font-weight:400; color:#b0b0bb; cursor:pointer; margin:1px 0 18px}
.lg-page .check input{width:15px; height:15px; margin:0; accent-color:#a78bfa; cursor:pointer}
.lg-page .btn-primary{display:block; width:100%; min-height:44px; margin:0; padding:13px 18px;
  font-family:var(--lg-mono); font-size:11.5px; font-weight:500; line-height:18px; letter-spacing:.22em;
  text-transform:uppercase; color:#0a0b0f; background:var(--lg-ink); border:0; border-radius:6px; cursor:pointer;
  transition:transform .18s, box-shadow .18s, background .18s}
.lg-page .btn-primary:hover{transform:translateY(-1px); background:#fff; box-shadow:0 10px 28px rgba(0,0,0,.3)}
.lg-page .btn-primary:active{transform:none}
.lg-page button:focus-visible, .lg-page a:focus-visible{outline:2px solid #c9bcff; outline-offset:4px}
.lg-alt{margin:13px 0 -3px; text-align:center; font-size:11.5px; line-height:1.4}
.lg-alt a{display:inline-block; padding:6px 2px; color:#b0a0df; text-underline-offset:4px}
.lg-alt a:hover{color:#d1c3ff; text-decoration:underline}

/* ошибка входа — свой класс, а НЕ .auth-err: тот же .auth-err используется
   в настройках аккаунта на светлом фоне, перекрашивать его нельзя */
.lg-err{display:flex; gap:10px; align-items:flex-start; font-size:12.5px; line-height:1.5;
  color:#ffb4b6; background:rgba(255,107,111,.08); border:1px solid rgba(255,107,111,.28);
  border-radius:6px; padding:10px 12px; margin-bottom:20px}
.lg-err::before{content:''; flex:none; width:5px; height:5px; border-radius:50%;
  background:var(--lg-danger); margin-top:6px}
.lg-err b{color:#fff; font-weight:600}
.lg-err a{color:#fff; text-decoration:underline; text-underline-offset:3px}
.lg-page .is-locked{opacity:.5}

.lg-foot{position:relative; z-index:3; max-width:1180px; width:100%; margin:0 auto;
  padding:0 clamp(24px,4.5vw,56px) 22px; display:flex; justify-content:space-between; gap:12px;
  font-family:var(--lg-mono); font-size:9.5px; line-height:1.6; letter-spacing:.13em;
  text-transform:uppercase; color:#9897a6}

.lg-rise{opacity:0; transform:translateY(14px); animation:lgrise .9s cubic-bezier(.16,1,.3,1) forwards}
@keyframes lgrise{to{opacity:1; transform:none}}
.lg-d1{animation-delay:.04s}.lg-d2{animation-delay:.16s}.lg-d3{animation-delay:.3s}
@media (prefers-reduced-motion:reduce){.lg-rise{animation:none; opacity:1; transform:none}}

@media (max-width:900px){
  .lg-shell{max-width:620px}
  .lg-head{padding-top:24px}
  .lg-main{grid-template-columns:1fr; gap:38px; align-content:center; padding:54px 0 64px}
  .lg-side, .lg-panel{width:100%; max-width:460px; justify-self:center}
  .lg-side .saxap-logo{width:150px}
  .lg-eyebrow{margin-bottom:16px; font-size:9px}
  .lg-foot{max-width:620px; font-size:8px; letter-spacing:.09em}
  .lg-page .field{font-size:16px}  /* 16 px — iOS не приближает страницу при фокусе */
  .lg-shade{background:linear-gradient(180deg,transparent,rgba(4,6,12,.12))}
}
@media (max-width:480px){
  .lg-shell{padding:0 24px}
  .lg-head{padding-top:21px}
  .lg-wordmark{gap:9px}
  .lg-wordmark .aura-symbol{width:22px; height:22px}
  .lg-wordmark .aura-wordmark{width:70px}
  .lg-main{padding:42px 0 56px; gap:33px}
  .lg-eyebrow{font-size:8.5px; letter-spacing:.23em; gap:11px}
  .lg-eyebrow::before{width:22px}
  .lg-side .saxap-logo{width:139px}
  .lg-panel{padding:27px 23px 23px}
  .lg-panel h1{margin-bottom:22px}
  .lg-foot{padding:0 24px 19px; font-size:7.5px; letter-spacing:.065em; gap:10px}
}
.lbl{font-size:12px; font-weight:600; color:var(--muted); margin:12px 0 6px}
.field{width:100%; padding:11px 13px; font-size:14px; border:1px solid var(--border-strong);
  border-radius:10px; background-color:var(--surface); transition:border-color .15s, box-shadow .15s;
  font-family:inherit; -webkit-appearance:none; appearance:none; color:var(--ink)}
.field:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(124,92,255,.14)}

/* ===== Пикер даты AURA (свой календарь вместо системного) ===== */
/* нативный input остаётся в потоке (валидация/required/фокус), но невидим под нашим UI */
.dp{position:relative; display:inline-flex; align-items:center; gap:6px; vertical-align:middle}
.dp-native{position:absolute; left:0; top:0; width:100%; height:100%; opacity:0;
  pointer-events:none; border:0; padding:0; margin:0; background:none}
.dp-trigger{display:inline-flex; align-items:center; gap:9px; width:auto; cursor:pointer;
  text-align:left; font-weight:600; font-variant-numeric:tabular-nums; color:var(--ink)}
.dp-trigger.is-empty .dp-txt{color:var(--muted); font-weight:500}
.dp-ic{display:inline-flex; color:var(--muted); flex:none}
.dp-ic svg{width:15px; height:15px; display:block}
.dp-trigger:hover{border-color:var(--accent)}
.dp.is-disabled{opacity:.55; pointer-events:none}
/* навигатор дня («Смена») */
.dp-nav{gap:7px}
.daynav-btn{display:inline-flex; align-items:center; justify-content:center; width:36px; min-height:40px;
  border:1px solid var(--border-strong); border-radius:10px; background:var(--surface);
  color:var(--muted); cursor:pointer; font-size:16px; line-height:1; flex:none}
.daynav-btn:hover:not(:disabled){border-color:var(--accent); color:var(--accent-ink)}
.daynav-btn.is-off,.daynav-btn:disabled{opacity:.35; cursor:default}
.daynav-cur{display:inline-flex; flex-direction:column; align-items:center; gap:1px; cursor:pointer;
  padding:5px 18px; min-height:40px; justify-content:center; min-width:158px;
  border:1px solid var(--border-strong); border-radius:10px; background:var(--surface); color:var(--ink)}
.daynav-cur:hover{border-color:var(--accent)}
.daynav-cur b{font-size:14px; font-weight:700; line-height:1.15}
.daynav-cur span{font-size:10.5px; color:var(--muted); line-height:1.2}
/* поповер календаря */
.dp-pop{position:absolute; top:calc(100% + 8px); left:0; z-index:80; width:264px; padding:12px;
  background:var(--surface); border:1px solid var(--border); border-radius:14px; box-shadow:var(--shadow-hover)}
.dp-pop.dp-up{top:auto; bottom:calc(100% + 8px)}
.dp-nav .dp-pop{left:50%; transform:translateX(-50%)}
.dp-head{display:flex; align-items:center; justify-content:space-between; gap:6px; margin-bottom:10px}
.dp-title{flex:1; padding:5px 8px; border:0; border-radius:8px; background:none; cursor:pointer;
  font:inherit; font-weight:700; font-size:13.5px; color:var(--ink)}
.dp-title:hover{background:var(--bg)}
.dp-nav-btn{width:26px; height:26px; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--border-strong); border-radius:8px; background:var(--surface);
  color:var(--muted); cursor:pointer; font-size:13px; line-height:1; flex:none}
.dp-nav-btn:hover{border-color:var(--accent); color:var(--accent-ink)}
.dp-grid{display:grid; grid-template-columns:repeat(7,1fr); gap:2px; text-align:center}
.dp-grid-m{grid-template-columns:repeat(3,1fr); gap:5px}
.dp-wd{font-size:10px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted);
  font-weight:700; padding:4px 0}
.dp-cell{padding:7px 0; border:0; border-radius:8px; background:none; cursor:pointer;
  font:inherit; font-size:12.5px; color:var(--ink); font-variant-numeric:tabular-nums}
.dp-cell:hover:not(:disabled){background:var(--bg)}
.dp-grid-m .dp-cell{padding:9px 0; font-size:13px}
.dp-day.is-out{color:var(--muted); opacity:.5}
.dp-day.is-we{color:var(--accent-ink)}
.dp-day.is-today{background:var(--grad); color:#fff; font-weight:800}
.dp-cell.is-pick{box-shadow:inset 0 0 0 1px var(--accent); color:var(--accent-ink); font-weight:700}
/* выделение с клавиатуры: показывает, куда применится Enter (значение ещё не изменено) */
.dp-cell.is-cursor{background:var(--bg)}
.dp-cell.is-cursor.is-pick{box-shadow:inset 0 0 0 2px var(--accent)}
.dp-day.is-off{opacity:.28; cursor:default; text-decoration:line-through}
.dp-foot{display:flex; gap:6px; margin-top:10px; padding-top:10px; border-top:1px solid var(--border)}
.dp-chip{flex:1; padding:6px 0; border:1px solid var(--border-strong); border-radius:8px;
  background:none; cursor:pointer; font:inherit; font-size:11.5px; font-weight:700; color:var(--accent-ink)}
.dp-chip:hover{border-color:var(--accent); background:var(--bg)}

/* Кроссбраузерная нормализация date/select (Safari/WebKit рисует их своим нативным
   стилем — плавающая высота дат и стрелки-степпер у селекта). Приводим к общему виду. */
input[type="date"].field, input[type="time"].field{min-height:41px}
input[type="date"].field::-webkit-date-and-time-value{text-align:left; margin:0; padding:0}
input[type="date"].field::-webkit-datetime-edit{padding:0; line-height:1.2}
input[type="date"].field::-webkit-calendar-picker-indicator{opacity:.65; cursor:pointer}
input[type="date"].field::-webkit-calendar-picker-indicator:hover{opacity:1}
select.field{padding-right:36px; cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%239097b8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 13px center; background-size:12px 8px}
select.field::-ms-expand{display:none}
/* (русский формат даты и сам календарь теперь делает datepicker.js — см. блок «Пикер даты AURA») */
/* убираем нативный степпер у числовых полей (тот же «⇅», ввод с клавиатуры остаётся) */
.field::-webkit-outer-spin-button, .field::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
input[type="number"].field{-moz-appearance:textfield}
.btn-primary{margin-top:22px; padding:12px 16px; border:0; border-radius:10px; cursor:pointer;
  font-size:15px; font-weight:700; color:#fff; background:var(--grad);
  box-shadow:0 6px 18px rgba(124,92,255,.32); transition:transform .12s, box-shadow .12s}
.btn-primary:hover{transform:translateY(-1px); box-shadow:0 10px 26px rgba(124,92,255,.40)}
.btn-primary:active{transform:translateY(0)}
.btn-ghost{padding:10px 18px; border:1px solid var(--border-strong); border-radius:10px;
  background:var(--surface); font-weight:600; cursor:pointer}
.btn-ghost:hover{border-color:var(--accent)}
/* Главное действие экрана в рабочих разделах: та же геометрия, что у .btn-ghost, —
   они часто стоят парой («Применить» + «Сбросить») и обязаны совпадать по высоте.
   От .btn-primary отличается тем, что не тащит margin-top:22px и крупный кегль: та
   рассчитана на форму входа и низ формы, а не на панель инструментов.

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

   ⚠️ Класс использовался в 26 местах (Landings, Voice), НЕ БУДУЧИ ОБЪЯВЛЕННЫМ вовсе —
   все эти кнопки рисовались системными средствами браузера. Правило «каждый класс
   кнопки объявлен» держит тест test_every_button_class_is_defined. */
.btn{display:inline-flex; align-items:center; justify-content:center; padding:10px 18px;
  border:1px solid transparent; border-radius:10px; cursor:pointer;
  font-family:inherit; font-size:14px; font-weight:600; color:#fff; background:var(--accent);
  transition:background .12s}
.btn:hover{background:var(--accent-ink)}
.btn:active{opacity:.9}
.auth-err{background:#fdecec; color:#b42318; font-weight:500; font-size:13px;
  padding:10px 13px; border-radius:10px; margin-bottom:6px}
.auth-form.is-locked{opacity:.6}
.field:disabled, .btn-primary:disabled{opacity:.55; cursor:not-allowed; box-shadow:none; transform:none}

/* ===== Параметры расчёта (форма) ===== */
.pf-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin-bottom:14px}
.params-form label{display:flex; flex-direction:column; gap:5px; font-size:12px; font-weight:600; color:var(--muted)}
/* ⚠️ display:flex выше сильнее браузерного [hidden]{display:none}: поле «VAT» городу без модели с VAT (06.10.2026) прячется
   атрибутом hidden — без этого правила оно было бы видно всем (ревью 06.10.2026) */
.params-form label[hidden]{display:none}
/* ⚠️ Галочка с подписью — строка, а не колонка. Общее правило выше ставит подпись ПОД
   элементом: для поля ввода это верно, а для чекбокса даёт квадратик над словом и рвёт
   сетку — ровно то, за что этот экран и переделывали. */
.params-form label.pf-check{flex-direction:row; align-items:center; gap:9px;
  font-size:14px; color:var(--ink); cursor:pointer; width:max-content}
.params-form label.pf-check input{width:16px; height:16px; margin:0; accent-color:var(--brand); cursor:pointer}
.params-form .field{font-weight:400; color:var(--ink)}
.pf-wide{grid-column:1/-1}
.row-eff td{background:rgba(124,92,255,.06)}

/* ===== Сверка (golden master) ===== */
.yearbar{display:flex; flex-wrap:wrap; gap:8px}
.ycell{background:var(--surface); border:1px solid var(--border); border-radius:10px;
  padding:8px 12px; text-align:center; min-width:64px; box-shadow:var(--shadow)}
.yyear{font-size:11px; color:var(--muted); font-weight:600}
.ypct{font-size:17px; font-weight:800; margin-top:2px}
.yhi{color:var(--ok)} .ymid{color:#c98a00} .ylo{color:var(--danger)}
.ok{color:var(--ok); font-weight:700}
.bad{color:var(--danger); font-weight:600; font-size:12.5px; white-space:nowrap}
.page-head .btn-ghost{align-self:center}

/* ===== Блок «на проверку» (пустые салоны) ===== */
.review{margin-top:22px; border-top:1px dashed var(--border-strong); padding-top:14px}
.review summary{cursor:pointer; font-weight:600; color:var(--muted); list-style:none; padding:6px 0}
.review summary::-webkit-details-marker{display:none}
.review summary::before{content:"▸ "; color:var(--accent-ink)}
.review[open] summary::before{content:"▾ "}
.review .cards{margin-top:12px}
.card-dim{opacity:.62}
.card-dim:hover{opacity:1}

/* ===== Обзор сети (плитки городов) ===== */
.netgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:12px; margin-bottom:6px}
.netcard{background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px;
  box-shadow:var(--shadow); display:flex; flex-direction:column; gap:5px; transition:transform .14s, box-shadow .14s, border-color .14s}
.netcard:hover{transform:translateY(-2px); box-shadow:var(--shadow-hover)}
.netcard.is-current{border-color:var(--accent); box-shadow:0 0 0 3px rgba(124,92,255,.12)}
.netcard-name{font-weight:700; font-size:15px; display:flex; align-items:center; gap:8px}
.netcard-here{font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.4px;
  color:var(--accent-ink); background:rgba(124,92,255,.10); padding:2px 7px; border-radius:10px}
.netcard-stats{font-size:12.5px; color:var(--muted)}

/* ===== Свитчер салона в топбаре ===== */
.ctx-sep{color:var(--border-strong); font-weight:400}
.dd-salon{max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}

/* ===== Обёртка таблицы (моб. скролл) + пустые состояния ===== */
.table-wrap{width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch}
.tbl{min-width:420px}
.empty-row{color:var(--muted); text-align:center; padding:18px 10px !important}
/* пояснение под итогом таблицы (почему сумма расходится с отчётами) */
.foot-note td{color:var(--muted); font-weight:400; font-size:12px; padding-top:8px !important}
.card-title{overflow-wrap:anywhere}

/* ввод бизнес-дня */
.dateform{margin-bottom:16px}
.dateform label{font-size:13px; font-weight:600; color:var(--muted); display:inline-flex; gap:8px; align-items:center}
.dateform .field{margin:0; width:auto}
.entry-tbl td{padding:5px 6px; vertical-align:middle}
.entry-tbl .field{padding:6px 8px; font-size:13px; margin:0}
.r-name{min-width:140px}
.entry-inputs{display:grid; grid-template-columns:1fr 1fr; gap:10px 14px}
.entry-inputs label{font-size:12px; font-weight:600; color:var(--muted); display:flex; flex-direction:column; gap:4px}
.entry-inputs .field{padding:8px 10px; margin:0}
.entry-totals{margin-top:16px; padding-top:12px; border-top:1px solid var(--border)}
/* «Терминал» с «+ Добавить» (сложный процент эквайринга, карточка 49): поле тянется, кнопка своей ширины и не
   переносится — в узком окне сжимается поле, а не ломается сетка «Итогов дня». Кнопка ростом с поле, прижата к его
   низу (над полем — подпись). */
.term-wrap{display:flex; align-items:flex-end; gap:8px; min-width:0}
.term-wrap > label{flex:1 1 auto; min-width:0}
.entry-inputs .term-add{flex:none; white-space:nowrap; min-height:0; padding:7px 12px; font-size:13px; line-height:20px}
.term-add[hidden]{display:none}
/* Производное поле (оплаты по картам): только чтение, серое — сумму вносит окно, а не рука */
.entry-inputs .field.is-derived{background:var(--bg); color:var(--muted); cursor:default}
.entry-inputs .field.is-derived:focus{border-color:var(--border-strong); box-shadow:none}
.et-row{display:flex; justify-content:space-between; padding:6px 0; font-size:14px}
/* Строка «План» в итогах дня приходит с атрибутом hidden и показывается скриптом только
   когда план задан. Без этого правила hidden не действовал, и строка висела всегда —
   с пустым значением (авторский display сильнее браузерного [hidden]). */
.et-row[hidden]{display:none}
.et-row b{font-weight:800}
.et-row .et-sub{font-size:12px; color:var(--muted); font-weight:500}   /* «(за счёт салона)» — только проверяющим смены */
/* пояснение под итогами архивной смены (почему налог «—», от чего эквайринг) */
.et-note{margin:10px 0 0; font-size:12px; line-height:1.5; color:var(--muted)}
.rowdel{border:0; background:none; color:var(--danger); cursor:pointer; font-size:14px; padding:4px}
/* индикатор «план выполнен» на смене */
.plan-status{border-radius:8px; padding:6px 10px; margin-top:2px; font-weight:700}
.plan-status.ok{background:rgba(46,160,67,.12); color:var(--ok)}
.plan-status.warn{background:rgba(245,159,0,.14); color:#c07a00}
/* экран «Планы»; узкое поле в строке формы. Селектор базы плана в «Параметрах расчёта» ушёл 01.10.2026 — база плана
   стала частью модели расчёта (.cm-line ниже) */
.field-plan{max-width:110px; text-align:right}
.field-inline{display:inline-block; width:auto; padding:5px 8px}
/* «Параметры расчёта»: выключатель строк «без НДС» в смене (26.09.2026) */
.novat-form{display:flex; align-items:center; flex-wrap:wrap; gap:6px 10px; margin:0 0 10px}
.novat-form .inline-lbl{font-size:13px; color:var(--muted)}
.novat-form .inline-lbl b{color:var(--ink)}
.novat-form .btn-ghost{min-height:0; padding:4px 12px; font-size:13px}
/* «Сложный процент эквайринга» в «Параметрах расчёта» (карточка 49) — в ряд с выключателем «без НДС» */
.acq-card-form, .abon-form, .dep-form{display:flex; align-items:center; flex-wrap:wrap; gap:6px 10px; margin:0 0 10px}
.acq-card-form .inline-lbl, .abon-form .inline-lbl, .dep-form .inline-lbl{font-size:13px; color:var(--muted)}
.acq-card-form .btn-ghost, .abon-form .btn-ghost, .dep-form .btn-ghost{min-height:0; padding:4px 12px; font-size:13px}
/* «Параметры расчёта» → «Модель расчёта» (разработчик 01.10.2026): строка города с «Изменить», журнал версий под ней,
   окно смены модели (static/js/calcmodel.js) — карточки .wz-card, сегмент «С какой даты», прогноз и вопрос .wp-del.
   Приложение только для компьютера (память aura-desktop-only): окно широкое, таблица прогноза — в восемь колонок. */
.cm-line{display:flex; align-items:center; flex-wrap:wrap; gap:6px 10px; margin:6px 0 6px}
.cm-line .inline-lbl{font-size:13px; color:var(--muted)}
.cm-line .inline-lbl b{color:var(--ink)}
.cm-line .btn-ghost{min-height:0; padding:4px 12px; font-size:13px}
.cm-journal{margin:0 0 12px}
.tbl.tbl-cm td{padding-top:6px; padding-bottom:6px}
.modal-wide.cm-modal{max-width:880px}
.cm-cap{color:var(--muted); font-size:13px}
.cm-now{display:inline-block; margin-left:6px; padding:1px 8px; border-radius:999px; font-size:11.5px; font-weight:600;
  color:var(--ok); background:color-mix(in srgb, var(--ok) 12%, transparent)}
.cm-now[hidden]{display:none}
.cm-l{display:block; margin-top:3px; line-height:1.45}
.cm-small{font-size:11.5px}
.cm-modal .cashfact{display:block; margin:14px 0 6px; font-size:13px; font-weight:600}
.cm-since{flex-wrap:wrap}
.cm-since .seg-item:has(> input:checked){color:#fff; background:var(--accent)}
.cm-since .seg-item:has(> input:disabled){opacity:.45; cursor:not-allowed}
.cm-since:has(input:focus-visible){box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent)}
.cm-date{margin-top:8px; max-width:220px}
.cm-date[hidden]{display:none}
.cm-head{margin-bottom:8px; color:var(--ink)}
.cm-sum{font-size:13px; line-height:1.5; margin:0 0 10px}
.cm-fc{max-height:44vh; overflow:auto; margin:0 0 8px}
.cm-tbl{font-size:12.5px; min-width:0}
.cm-tbl td, .cm-tbl th{white-space:nowrap; vertical-align:top}
.cm-tbl tfoot td{font-weight:700; border-top:2px solid var(--border)}
.cm-al{display:block; font-size:12px}
.cm-moved{color:var(--ink); font-weight:600}
.cm-over{color:var(--warn); font-weight:600}
.cm-never{font-size:13px; margin:8px 0 0; color:var(--muted)}

/* ===== Экран «Смена» (матрица + блоки) — десктоп ===== */
.smena-matrix{overflow-x:auto; -webkit-overflow-scrolling:touch; border:1px solid var(--border); border-radius:12px;
  /* Tab/Shift+Tab не уводят поле под липкую колонну подписей (её ширину теперь тянут до 360 px) */
  scroll-padding-left:var(--mtx-rl-w,170px)}
.mtx{border-collapse:separate; border-spacing:0; font-size:13px; min-width:100%}
.mtx th, .mtx td{border-bottom:1px solid var(--border); border-right:1px solid var(--border); padding:0}
/* титульная колонка: ФИКСИРОВАННАЯ ширина — иначе при малом числе мастеров браузер
   растягивал её на свободное место, и подписи строк «ездили» от смены к смене */
.mtx .rl{position:sticky; left:0; z-index:2; background:var(--bg); padding:8px 12px; font-weight:600;
  color:var(--muted); white-space:nowrap; text-align:right; border-right:1px solid var(--border-strong);
  width:var(--mtx-rl-w,170px); min-width:var(--mtx-rl-w,170px); max-width:var(--mtx-rl-w,170px)}
.mtx thead th{position:sticky; top:0; z-index:1; background:var(--bg); border-bottom:2px solid var(--border-strong)}
.mtx thead .rl{z-index:3}
/* бледнее — только цвет текста, не вся ячейка: при opacity угол просвечивал, и под «МАСТЕРА» читались имена
   колонок, уехавших под него при прокрутке вбок (копия шапки держит угол на экране всё время, ревью 02.10.2026) */
.mtx .corner{vertical-align:middle; font-size:10px; text-transform:uppercase; letter-spacing:.05em; color:color-mix(in srgb, var(--muted) 70%, transparent); text-align:center}
.mtx .mcol{padding:8px 10px; min-width:var(--mtx-col-w,152px); vertical-align:top; text-align:center; position:relative}
/* ===== Ширина колонок смены (разработчик 02.10.2026, smena.js «ширина колонок») =====
   Край заголовка тянется мышью: у первой колонны своя ширина (--mtx-rl-w), у колонок мастеров — одна на всех
   (--mtx-col-w, это наименьшая: свободное место таблица по-прежнему раздаёт колонкам мастеров). Текст в одну
   строку. Подпись строки при сужении теряет НАЧАЛО: обёртка прижимает её вправо, а лишнее flex-end выталкивает
   влево, за край («…наличными»). Имя и должность — КОНЕЦ, с многоточием; пока помещаются — по центру.
   ⚠️ width:0 + min-width:100% — чтобы длинный текст не распирал колонку: в ширину таблицы он не входит вовсе,
   а на экране занимает всю ячейку. Без этого колонку не сузить уже текста. */
.mtx .rl-t{display:flex; justify-content:flex-end; width:0; min-width:100%; overflow:hidden; white-space:nowrap}
.mtx .rl-t > span{flex:none}
.mtx .corner .rl-t{justify-content:center; justify-content:safe center}
.mtx thead .mtx-rsz{position:absolute; top:0; right:-1px; bottom:0; width:8px; z-index:2; cursor:col-resize; touch-action:none}
.mtx thead .mtx-rsz::after{content:''; position:absolute; top:6px; bottom:6px; right:2px; width:2px; border-radius:1px}
.mtx thead .mtx-rsz:hover::after{background:var(--accent)}
.is-col-rsz, .is-col-rsz *{cursor:col-resize !important; user-select:none !important}
/* «Отображение» — рядом с «Порядком»: «Вмещать таблицу в экран», «Единая ширина колонок», «Показывать должности», «Сбросить» */
.disp-dd{flex:none}
.ord-dd[hidden] + .disp-dd{margin-left:auto}
.disp-menu{min-width:280px}
.disp-menu .ord-note{max-width:270px}
.mtx-nospec .mtx .mspec{display:none}
/* Шапка смены на виду (разработчик 02.10.2026): копия настоящей шапки под топбаром, пока та уехала вверх
   (smena.js, «шапка на виду»). В отличие от копии «Планов» живая — меню по правому клику и перетаскивание
   колонок работают по ней, поэтому без pointer-events:none. Раскладка фиксированная: ширины колонок скрипт
   снимает с настоящих ячеек. */
.mtx-float{position:fixed; z-index:30; overflow:hidden; background:var(--bg); box-shadow:0 6px 14px rgba(25,27,46,.10)}
.mtx-float[hidden]{display:none}
.mtx-float .mtx{margin:0; min-width:0; table-layout:fixed}
/* липкость настоящей шапки копии не нужна; угол «Мастера» скрипт возвращает на место при прокрутке вбок —
   он лежит поверх имён (z-index из .mtx thead .rl) */
.mtx-float .mtx thead th{position:relative}
@media print{.mtx-float{display:none}}
/* ===== Матрицы планов: таблица как в «Смене» — ячейка к ячейке, без скруглений =====
   Вертикально страница листается сама: своей прокрутки у панели нет, иначе колесо
   мыши сначала прокручивает таблицу и лишь потом страницу. Панель ловит только ширину,
   а шапку при прокрутке прижимает к верху отдельная плавающая копия (matrix.js). */
.matrix-pane{overflow-x:auto; -webkit-overflow-scrolling:touch;
  border:1px solid var(--border); border-radius:12px}
.plans-matrix{border-collapse:separate; border-spacing:0; font-size:12.5px; min-width:100%}
.plans-matrix th, .plans-matrix td{padding:0; white-space:nowrap;
  border-bottom:1px solid var(--border); border-right:1px solid var(--border)}
.plans-matrix thead th{background:var(--bg); padding:5px 8px; font-size:11px;
  text-align:center; font-weight:600}
.plans-matrix thead tr:last-child th{border-bottom:2px solid var(--border-strong)}
.plans-matrix .pm-day{border-left:1px solid var(--border-strong); font-size:11.5px;
  text-transform:none; letter-spacing:0}
.plans-matrix .pm-sub{color:var(--muted); font-size:10px; text-transform:uppercase; letter-spacing:.04em}
.plans-matrix .pm-over{background:rgba(124,92,255,.045)}
/* колонка салонов фиксированной ширины: иначе она «ездит» от города к городу */
.plans-matrix .pm-salon{position:sticky; left:0; z-index:3; background:var(--surface);
  text-transform:none; letter-spacing:0;
  border-right:1px solid var(--border-strong); text-align:left; padding:6px 10px; font-weight:600;
  width:186px; min-width:186px; max-width:186px; overflow:hidden; text-overflow:ellipsis}
.plans-matrix thead .pm-salon{z-index:5; background:var(--bg); font-weight:600}
.plans-matrix td .field-plan{width:100%; min-width:76px; border:0; border-radius:0; background:var(--surface);
  padding:6px 8px; text-align:right; font-size:12.5px; font-variant-numeric:tabular-nums; color:var(--ink)}
.plans-matrix td .field-plan:focus{outline:2px solid var(--accent); outline-offset:-2px;
  position:relative; z-index:1}
.plans-matrix td .field-plan::placeholder{color:var(--muted); opacity:.5}
/* браузерные стрелки «плюс/минус тысяча» в таблице только мешают целиться в ячейку */
.plans-matrix input[type=number]{-moz-appearance:textfield; appearance:textfield}
.plans-matrix input[type=number]::-webkit-outer-spin-button,
.plans-matrix input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
/* «Весь город» и «Бонус админу» — опорные строки: от них отсчитываются все салоны */
.plans-matrix .pm-city td, .plans-matrix .pm-bonus td,
.plans-matrix tr.pm-city:hover td, .plans-matrix tr.pm-bonus:hover td{background:rgba(124,92,255,.06)}
.plans-matrix .pm-city .field-plan, .plans-matrix .pm-bonus .field-plan{background:transparent; font-weight:600}
/* ⚠️ …но у липкой первой колонки фон обязан остаться НЕПРОЗРАЧНЫМ: правило выше идёт
   последним и перекрывало её подложку, поэтому при горизонтальной прокрутке цифры этих
   двух строк проезжали сквозь название салона. Оттенок кладём слоем поверх подложки. */
.plans-matrix .pm-city .pm-salon, .plans-matrix .pm-bonus .pm-salon,
.plans-matrix tr.pm-city:hover .pm-salon, .plans-matrix tr.pm-bonus:hover .pm-salon{
  background-color:var(--surface);
  background-image:linear-gradient(rgba(124,92,255,.06), rgba(124,92,255,.06))}
.plans-matrix .pm-bonus td{border-bottom:2px solid var(--border-strong)}
/* подсветка: заголовок колонки и название салона окрашиваются ОДИНАКОВО —
   это две координаты одной ячейки, и разный вид читался бы как разный смысл */
.plans-matrix th.is-hot, .plans-matrix tr.is-hot .pm-salon{background:var(--accent); color:#fff}
.plans-matrix th.is-hot.pm-sub{color:#fff; opacity:.95}
.plans-matrix td.is-hot .field-plan{background:rgba(124,92,255,.10)}
/* плавающая копия шапки: показывается, когда настоящая уехала вверх */
.matrix-float{position:fixed; z-index:30; overflow:hidden; pointer-events:none;
  box-shadow:0 6px 14px rgba(25,27,46,.10)}
.matrix-float table{margin:0; border-collapse:separate; border-spacing:0; table-layout:fixed}
/* угол «Салон» копии держится на месте при прокрутке вбок (matrix.js сдвигает его обратно) —
   он обязан лежать поверх подписей колонок и быть непрозрачным */
.matrix-float .pm-salon{position:relative; z-index:2}
/* адрес текущей ячейки словами — рядом с кнопкой сохранения */
.pm-addr{font-weight:600; color:var(--accent-ink)}
/* «По направлениям» с 01.10.2026. Сетка жёсткая: колонка фиксированной ширины, в ячейке два поля
   ДРУГ ПОД ДРУГОМ — порог сверху, бонус администратору под ним, как говорит легенда. До 17.09 поля
   стояли в строку по 100% ширины каждое, и второе вылезало в соседнюю колонку поверх её порога.
   Красным — только ПУСТОЕ поле незаданной ячейки (правило «не задано» — salon_thresholds.is_unset,
   класс ячейки скрипт держит по вводу), а не рамка на всю ячейку. */
.pd-matrix{--pd-col:100px; --pd-need:rgba(229,72,77,.09); --pd-need-line:rgba(229,72,77,.38)}
[data-theme="dark"] .pd-matrix{--pd-need:rgba(255,107,111,.13); --pd-need-line:rgba(255,107,111,.45)}
/* таблица своей ширины, а не во всю панель: у города с тремя колонками они не растягиваются */
.pd-pane{width:fit-content; max-width:100%}
.pd-matrix{width:auto; min-width:0}
.pd-matrix th.pm-sub, .pd-matrix td.pd-cell, .pd-matrix td.pd-fill-cell{
  width:var(--pd-col); min-width:var(--pd-col); max-width:var(--pd-col)}
/* граница направления — сплошной линией через шапку и все строки */
.pd-matrix .pm-day{border-left:0; border-right:1px solid var(--border-strong)}
.pd-matrix .pd-ge{border-right:1px solid var(--border-strong)}
.pd-matrix th.pm-sub{padding:5px 4px 4px; white-space:normal; line-height:1.25; vertical-align:top}
.pd-matrix .pd-lvl{display:block}
.pd-matrix .pd-meta{display:block; min-height:12px; margin-top:1px; font-size:9.5px; font-weight:600;
  text-transform:none; letter-spacing:0; white-space:nowrap}
.pd-matrix .pd-off{color:var(--muted)}
.pd-matrix .pd-unset-n{color:var(--danger)}
.plans-matrix th.is-hot .pd-unset-n, .plans-matrix th.is-hot .pd-off{color:#fff}
/* два поля ячейки */
.pd-matrix td .field-plan{display:block; width:100%; min-width:0; max-width:none; height:24px;
  padding:0 8px; line-height:24px}
.pd-matrix td .field-plan.pd-b{height:21px; line-height:21px; font-size:11px; color:var(--muted);
  border-top:1px dashed var(--border)}
.pd-matrix td.pd-cell.is-none .field-plan{color:var(--muted)}
/* подпись «порог / бонус» в пустом поле видна только под курсором: в городе с сотней пустых
   ячеек две сотни подписей превращали матрицу в текст */
.pd-matrix td.pd-cell .field-plan::placeholder{color:transparent; font-size:10.5px}
.pd-matrix td.pd-cell .field-plan:focus::placeholder{color:var(--muted); opacity:.7}
/* пустое поле незаданной ячейки — бледно-красная плашка с отступом от сетки: соседние пустые
   ячейки не сливаются в одно пятно, и линии таблицы остаются видны */
.pd-matrix td.pd-cell.is-unset .field-plan:placeholder-shown{background:var(--pd-need);
  border-radius:5px; box-shadow:inset 0 0 0 2px var(--surface), inset 0 0 0 3px var(--pd-need-line)}
/* «Поставить всем салонам» — третья строка шапки: те же два поля и две короткие кнопки под ними */
.pd-matrix .pd-fill-row > th, .pd-matrix .pd-fill-row > td{background:var(--bg);
  border-bottom:2px solid var(--border-strong); vertical-align:top}
.pd-matrix thead .pd-fill-row .pm-salon{font-size:12.5px; text-align:left; vertical-align:middle}
/* поля заливки — белые на сером ряду: иначе подсказка «самое частое значение» читается как
   уже записанное число, а не как поле */
.pd-matrix .pd-fill-cell .field-plan{background:var(--surface); font-weight:600}
.pd-matrix .pd-fill-cell .field-plan::placeholder{color:var(--muted); opacity:.55; font-weight:400}
.pd-matrix .pd-btns{display:flex; border-top:1px solid var(--border)}
.pd-matrix .pd-btn{flex:1 1 auto; border:0; background:none; padding:0 2px; cursor:pointer;
  font:inherit; font-size:10.5px; font-weight:600; line-height:20px; color:var(--accent-ink); white-space:nowrap}
.pd-matrix .pd-btn + .pd-btn{border-left:1px solid var(--border)}
.pd-matrix .pd-btn:hover{background:var(--mtx-hl-strong)}
.pd-matrix .pd-btn:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
/* в плавающей копии шапки строки полей нет: копия только напоминает, какая это колонка */
.matrix-float .pd-fill-row{display:none}
.pd-counters{margin:0 0 0 auto; font-size:12.5px; color:var(--muted)}
.pd-counters .pd-n-unset{color:var(--danger)}
.pd-legend{display:flex; gap:16px; margin:-4px 0 10px; font-size:12.5px; color:var(--muted)}
.pd-legend-off{margin-left:auto}
.pd-legend-off a{color:var(--accent-ink)}

/* «Планы» в сайдбаре раскрываются деревом: вертикаль вниз и веточки вправо к пунктам.
   Класс side-group тут занят контекстным блоком сверху — отсюда своё имя side-tree. */
.side-tree > .side-sum{display:flex; align-items:center; justify-content:space-between;
  cursor:pointer; list-style:none}
.side-tree > .side-sum::-webkit-details-marker{display:none}
.tree-caret{width:0; height:0; border-left:4px solid transparent; border-right:4px solid transparent;
  border-top:5px solid var(--muted); transition:transform .15s ease}
.side-tree[open] > .side-sum .tree-caret{transform:rotate(180deg)}
.side-kids{margin:2px 0 2px 22px; display:flex; flex-direction:column; gap:1px}
.side-link.side-sub{position:relative; margin-left:9px; padding:7px 10px 7px 10px;
  font-weight:500; font-size:13px}
/* вертикаль: у последнего пункта обрывается на его середине — как в выводе tree */
.side-link.side-sub::before{content:""; position:absolute; left:-9px; top:0; height:50%;
  border-left:1px solid var(--border-strong)}
.side-link.side-sub:not(:last-child)::before{height:100%}
.side-link.side-sub::after{content:""; position:absolute; left:-9px; top:50%; width:7px;
  border-top:1px solid var(--border-strong)}
.side-link.side-sub.is-active::before, .side-link.side-sub.is-active::after{border-color:var(--accent)}
/* меню выгрузки: CSV / Excel / PDF */
.export-dd{display:inline-block; position:relative}
.export-dd .dd-menu{right:0; left:auto; min-width:170px}
.export-dd .dd-item{display:block; width:100%; text-align:left; padding:9px 12px; border-radius:8px;
  font-size:13px; color:var(--ink); background:none; border:0; cursor:pointer}
.export-dd .dd-item:hover{background:var(--bg)}
/* мастер создания пользователя */
.wz{max-width:620px}
.wz-bar{height:4px; border-radius:3px; background:var(--bg); margin-bottom:22px; overflow:hidden}
.wz-fill{display:block; height:100%; background:var(--grad); transition:width .2s}
.wz-h{font-size:18px; font-weight:700; margin:0 0 6px}
.wz-hint{color:var(--muted); font-size:13px; margin:0 0 14px}
.wz-cards{display:grid; gap:10px; margin-bottom:8px}
.wz-card{display:block; padding:14px 16px; border:1px solid var(--border); border-radius:12px; cursor:pointer}
.wz-card:hover{border-color:var(--accent)}
.wz-card input{margin-right:8px}
.wz-card:has(input:checked){border-color:var(--accent); background:rgba(124,92,255,.06)}
.wz-card-t{font-weight:700; font-size:14px}
.wz-card-d{display:block; color:var(--muted); font-size:12.5px; margin-top:4px; padding-left:22px}
.wz-row{display:flex; gap:10px; align-items:center}
.wz-row .field{max-width:220px}
.wz-found{margin-top:12px; font-size:13px; color:var(--accent-ink)}
/* фото «Человека» в находке мастера (24.09.2026) — наша копия, не YClients */
.wz-yc-photo{width:48px; height:48px; border-radius:10px; object-fit:cover; float:left; margin:0 10px 4px 0}
/* 25.09.2026: в поле вставляют ссылку YClients целиком — шире номера; варианты «по имени» с кнопкой */
.wz-row #wz-yc{max-width:420px}
.wz-yc-variant{margin-top:6px; display:flex; gap:8px; align-items:center; flex-wrap:wrap}
.wz-sum{display:grid; grid-template-columns:150px 1fr; gap:8px 14px; margin:0 0 20px; font-size:13.5px}
.wz-sum dt{color:var(--muted)}
.wz-sum dd{margin:0; font-weight:600}
.wz-nav{display:flex; align-items:center; gap:10px; margin-top:24px; padding-top:18px; border-top:1px solid var(--border)}
.wz-spacer{flex:1}
/* ⚠️ Та же болезнь, что в .modal-actions: `.btn-primary` ниже по файлу возвращает себе
   margin-top:22px и другой padding, и «Далее» уезжало вниз, а «Назад» с «Пропустить»
   висели выше него. Гасим ровно так же, вместе с рамкой ради тех же двух пикселей. */
.wz-nav .btn-primary{margin-top:0; padding:10px 18px; font-size:14px; border:1px solid transparent}
/* приглашения: форма создания */
.inv-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:14px; margin-bottom:16px}
.lbl-block{display:flex; flex-direction:column; gap:6px; font-size:12px; color:var(--muted); font-weight:600}
.inv-form .city-checks{margin-bottom:14px}
/* мягкое напоминание про почту */
.msg-hint{display:flex; align-items:center; justify-content:space-between; gap:14px;
  background:rgba(124,92,255,.08); border:1px solid rgba(124,92,255,.25); color:var(--ink)}
/* Без этой строки атрибут hidden на плашке не действовал (авторский display сильнее
   браузерного [hidden]{display:none}): «Скрыть на неделю» ставила куку и hidden, а
   подсказка оставалась на экране и возвращалась после перезагрузки. */
.msg-hint[hidden]{display:none}
.msg-hint .hint-actions{display:flex; align-items:center; gap:8px; flex:none}
.hint-close{border:0; background:none; color:var(--muted); font-size:18px; line-height:1;
  cursor:pointer; padding:2px 6px; border-radius:6px}
.hint-close:hover{background:var(--bg); color:var(--ink)}
/* статьи издержек: форма добавления и построчное редактирование */
.ct-add{display:flex; gap:10px; align-items:center; margin-bottom:16px}
.ct-add .field{max-width:280px}
.field-sort{max-width:90px; text-align:right}
.ct-tbl td .field{padding:6px 9px}
.ct-actions{display:flex; gap:8px; justify-content:flex-end; white-space:nowrap}
.ct-del:hover:not(:disabled){border-color:var(--danger); color:var(--danger)}
.ct-del:disabled{opacity:.45; cursor:not-allowed}
/* ручной набор смены: список штата над матрицей */
.mtx-head{display:flex; align-items:center; gap:10px; margin-bottom:12px}
.mtx-add .field-inline{min-width:230px}
/* список штата с поиском (разработчик 28.09.2026, smena.js): кнопка на месте прежнего select'а,
   окно ложится ПОВЕРХ неё — первая строка окна и есть поле поиска, ровно там, где была кнопка
   (сдвиг -7px = рамка 1px + отступ окна 6px).
   ⚠️ display у .combo-pop и .combo-none не задаём: их прячет атрибут hidden (как .ctx-menu выше).
   У пункта display свой (flex) — поэтому отдельное правило для [hidden]. */
.combo{position:relative; display:inline-block}
.combo .combo-btn{text-align:left; cursor:pointer; padding-right:36px; font-weight:500;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%239097b8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 13px center; background-size:12px 8px}
.combo .combo-btn:hover{border-color:var(--accent)}
.combo-pop{position:absolute; top:-7px; left:-7px; z-index:60; width:360px; min-width:calc(100% + 14px);
  padding:6px; background:var(--surface); border:1px solid var(--border); border-radius:12px;
  box-shadow:var(--shadow-hover)}
.combo .combo-q{padding:5px 8px; min-height:36px}
.combo-list{list-style:none; margin:6px 0 0; padding:0; max-height:min(360px, 50vh); overflow-y:auto}
.combo-item{display:flex; align-items:baseline; gap:8px; padding:7px 10px; border-radius:8px;
  cursor:pointer; color:var(--ink)}
.combo-item[hidden]{display:none}
.combo-item.is-hl{background:var(--mtx-hl-strong)}
.combo-name{font-weight:500}
.combo-spec{color:var(--muted); font-size:12.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.combo-none{margin:6px 0 0; padding:7px 10px; color:var(--muted); font-size:13px}
/* действия по мастеру живут в меню на правый клик (title ставит скрипт, и только
   на открытой смене) — курсор подсказывает, что меню здесь есть */
.mtx .mcol[title]{cursor:context-menu}
/* колонка администратора (только в архивной смене) — отделяем от мастеров */
.mtx .mcol.is-admin{border-left:2px solid var(--border)}
.mtx .mcol.is-admin .mspec{color:var(--accent-ink)}
/* Порядок колонок (разработчик 30.09.2026, smena.js «порядок колонок»): человек без должности — первым и с
   красной пометкой, «чтобы их сразу видно было и могли поправить прямо в начале дня»; перетаскивание шапок —
   полупрозрачная тянущаяся колонка и полоса там, куда она встанет. */
.mtx .mcol.no-grade{box-shadow:inset 0 3px 0 var(--danger)}
.mtx .mnograde{display:inline-block; margin-top:4px; padding:1px 8px; border-radius:999px; font-size:11px;
  font-weight:700; line-height:1.5; color:var(--surface); background:var(--danger);
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:top}
/* обёртка пометки: в ширину колонки не входит, режется по фактической ширине (как .mname-txt) */
.mtx .mng-w{display:block; width:0; min-width:100%}
.mtx .mnograde[hidden], .mtx .mspec[hidden]{display:none}
.mtx .mcol.is-dragging{opacity:.45}
.mtx .mcol.drop-before{box-shadow:inset 3px 0 0 var(--accent)}
.mtx .mcol.drop-after{box-shadow:inset -3px 0 0 var(--accent)}
.mtx .mcol.no-grade.drop-before{box-shadow:inset 0 3px 0 var(--danger), inset 3px 0 0 var(--accent)}
.mtx .mcol.no-grade.drop-after{box-shadow:inset 0 3px 0 var(--danger), inset -3px 0 0 var(--accent)}
/* «Порядок» — окошко как «Столбцы» (.cols-*), справа в строке над матрицей */
.ord-dd{margin-left:auto}
.ord-dd[hidden]{display:none}
.ord-menu{min-width:260px}
.ord-name{flex:1; min-width:0; padding:4px 2px; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.ord-none{color:var(--danger); font-weight:600}
.ord-note{margin:6px 6px 2px; max-width:260px; font-size:12px; line-height:1.4; color:var(--muted)}
.ord-note.ord-err{color:var(--danger)}
.mtx .mname-txt{display:block; font-weight:600; font-size:13px; padding:2px 2px; line-height:1.3}
.mtx .mspec{display:block; font-size:11px; color:var(--muted); margin-top:3px}
/* имя и должность — одной строкой, конец уходит в многоточие (ширина колонок, см. выше) */
.mtx .mname-txt, .mtx .mspec{width:0; min-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.mtx .mdel{position:absolute; top:3px; right:3px; border:0; background:none; color:var(--danger); cursor:pointer; font-size:12px; line-height:1}
.mtx td .cell{width:100%; border:0; border-radius:0; padding:7px 8px; text-align:right; background:var(--surface); font-variant-numeric:tabular-nums}
.mtx td .cell:focus{outline:2px solid var(--accent); outline-offset:-2px; position:relative; z-index:1}
/* поле под копией шапки: браузер прокручивает к нему по Tab, только зная, что верх окна занят (smena.js floatPad) */
.mtx td .cell{scroll-margin-top:var(--mtx-fp,0px)}
/* узкая колонка (до 40 px, 02.10.2026): число, не влезшее в поле, — с многоточием, а не обрезком «250» вместо «2500» */
.mtx td .cell{text-overflow:ellipsis}
.mtx tr.auto td, .mtx tr.auto .rl{background:var(--bg)}
/* ===== Ориентир при вводе: где именно я сейчас пишу =====
   Матрица разрослась (у каждой ставки есть двойник «без НДС»), и между крайней
   колонкой и подписью строки помещается пол-экрана. Поэтому под курсором подсвечены
   подпись строки и имя мастера, а сама строка и колонка залиты бледным — глаз ведёт
   от ячейки к подписи по сплошной полосе.
   Строку берёт на себя браузер (:focus-within), колонку в CSS выразить нечем —
   таблица знает строки, но не столбцы, поэтому ячейки столбца помечает smena.js.
   ⚠️ Красим И ячейку, И поле ввода: поле растянуто на всю ячейку и имеет свой
   непрозрачный фон, так что правило только для td не видно вообще. */
.mtx tbody tr:focus-within > td, .mtx tbody tr:focus-within .cell,
.mtx tbody td.is-col, .mtx tbody td.is-col .cell{background:var(--mtx-hl)}
.mtx thead th.is-col{background:var(--mtx-hl-strong); color:var(--accent-ink)}
/* ⚠️ Титульная колонка ЛИПКАЯ (`.rl`, sticky left:0), а `--mtx-hl-strong` — прозрачная
   rgba: положенная фоном, она снимала сплошную подложку, и сквозь подпись строки были
   видны уезжающие под неё цифры матрицы. Тон кладём СЛОЕМ поверх фона. Та же ловушка
   уже ловилась в матрицах планов (ниже) и на экране начислений. */
.mtx tbody tr:focus-within > .rl{background-color:var(--bg);
  background-image:linear-gradient(var(--mtx-hl-strong), var(--mtx-hl-strong));
  color:var(--accent-ink)}
.mtx .wcell{padding:7px 8px; text-align:right; font-weight:700; font-variant-numeric:tabular-nums}
/* клетки-итоги (Зарплата, Долг по ЗП, За счёт салона) — текст в обёртке без своей ширины: режется с многоточием,
   а не держит колонку (разработчик 02.10.2026: колонки мастеров сужают до 40 px на мониторах 1366 px; smena.js cellText).
   Поля по бокам 8 px, как у полей ввода: цифры встают под цифрами полей, а в 40 px остаётся 24 px — Firefox при 20 px
   не рисовал многоточие и показывал «2 5» вместо «2 500» */
.mtx .cval{display:block; width:0; min-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* «Долг по ЗП» смены новых правил (разработчик 30.09.2026): серая авто-строка, минус — красным; под суммой мелко —
   какими выплатами закрыто (smena.js, paintDebt) */
.mtx .dcell{padding:7px 8px; text-align:right; font-weight:600; font-variant-numeric:tabular-nums}
.mtx .dcell.is-zero{color:var(--muted)}
/* «За счёт салона» — доплата мастеру до выхода, видят только проверяющие смены (разработчик 01.10.2026) */
.mtx .scell{padding:7px 8px; text-align:right; font-weight:600; font-variant-numeric:tabular-nums; color:var(--warn)}
.mtx .scell.is-zero{color:var(--muted); font-weight:500}
/* «VAT» человека (модель «Эмиратская», 06.10.2026) — только показ, с копейками */
.mtx .vcell{padding:7px 8px; text-align:right; font-variant-numeric:tabular-nums; color:var(--ink)}
.mtx .vcell.is-zero{color:var(--muted)}
.mtx .dcell.is-neg{color:var(--danger)}
.mtx .dcell .dsub{display:block; font-size:10.5px; font-weight:500; color:var(--muted); line-height:1.25; white-space:normal}
/* погашение долга смены выплатами мимо кассы (разработчик 30.09.2026): число остаётся «ЗП − нал», слева в той же
   клетке, прижатым к левому краю, — мелкое зелёное «✓ выплачено» или «выплачено X». В одну строку и без своей
   ширины (02.10.2026): не хватает места — первой режется пометка (сжимается в 1000 раз охотнее), сумма — последней;
   погашенный целиком долг — в зелёной рамке, чтобы это было видно и со срезанной пометкой */
.mtx .dcell .dline{display:flex; align-items:baseline}
.mtx .dcell .dpaid{flex:0 1000 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; margin-right:auto;
  font-size:10.5px; font-weight:600; color:var(--ok)}
/* зазор до суммы — содержимым пометки, а не её отступом: срезанная до нуля пометка не отнимает у суммы места */
.mtx .dcell .dpaid::after{content:''; display:inline-block; width:8px}
.mtx .dcell .dsum{flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis}
.mtx tbody td.dcell.is-paid{outline:1.5px solid color-mix(in srgb, var(--ok) 65%, transparent); outline-offset:-2px}
/* Сверхплан мастера: полоска до порога. Шкала — верхний включённый порог, риска — на
   первой ступени; мерить «до следующей невзятой ступени» нельзя, иначе в момент взятия
   первой полоска прыгает назад и рост читается как откат. */
.mtx .spcell{padding:8px 10px; text-align:center; vertical-align:middle}
.spbar{position:relative; display:block; height:6px; border-radius:4px; background:var(--border)}
.spbar i{position:absolute; left:0; top:0; bottom:0; border-radius:4px; background:var(--muted)}
.spbar.is-done i{background:var(--ok)}
.spbar b{position:absolute; top:-3px; width:1px; height:12px; background:var(--border-strong)}
.spcap{display:block; margin-top:5px; font-size:11px; color:var(--muted); white-space:nowrap}
.spcap.is-done{color:var(--ok); font-weight:600}
.sp-none{color:var(--muted)}
/* с 01.10.2026: ступень салону не задана («Планы → По направлениям») — бонус за неё не начислится */
.sp-none.sp-unset, .spcap.sp-unset{color:#c0392b; font-weight:600}
.spcap.sp-unset{margin-top:1px}
/* подписи сверхплана — одной строкой с многоточием, в ширину колонки не входят (ширина колонок, 02.10.2026):
   иначе «✓✓ обе ступени · 1 500 ₽» держала колонку шире всех, и «Вмещать в экран» не вмещало. Полный текст — в title */
.mtx .spcell .spcap, .mtx .spcell .sp-none{display:block; width:0; min-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* ===== Панель «Моя смена»: за что администратор получает то, что получает =====
   Три колонки в строке: название, сумма, пояснение под ними во всю ширину — иначе
   пояснения («план 120 000 · сделали 134 000») ломают выравнивание сумм. */
.ern-block + .ern-block{margin-top:14px; padding-top:12px; border-top:1px solid var(--border)}
.ern-who{font-weight:700; font-size:13px; margin-bottom:8px}
.ern-row{display:grid; grid-template-columns:1fr auto; gap:2px 10px; padding:5px 0}
.ern-t{font-size:13px}
.ern-v{font-weight:700; font-size:13px; font-variant-numeric:tabular-nums; text-align:right}
.ern-n{grid-column:1 / -1; font-size:11.5px; color:var(--muted); line-height:1.4}
.ern-total{display:flex; justify-content:space-between; align-items:baseline; margin-top:10px;
  padding-top:10px; border-top:1px solid var(--border); font-weight:800; font-size:14px}
.ern-total b{font-variant-numeric:tabular-nums}
/* «Бонус за эквайринг» (разработчик 28.09.2026): решение сервера (`data-verdict`) — цветом подписи. Бонус
   есть — зелёная; выше лимита, без оборота и без администратора в смене — как обычная подпись, а сумма 0
   приглушена: «не заработано», а не «ошибка». Пустой терминал сервер считает нулём, как сохранение, —
   своего цвета у него нет (ревью 29.09.2026). */
.ern-row[data-verdict="yes"] .ern-n{color:var(--ok); font-weight:600}
.ern-row[data-verdict="over"] .ern-v, .ern-row[data-verdict="no_gross"] .ern-v,
.ern-row[data-verdict="no_admin"] .ern-v{color:var(--muted); font-weight:500}
.ern-note, .ern-empty{margin:12px 0 0; font-size:12px; color:var(--muted); line-height:1.5}
.ern-empty{margin:0 0 10px}
.mtx tr:last-child td, .mtx tr:last-child .rl{border-bottom:0}
.smena-cols{display:grid; grid-template-columns:1.6fr 1fr; gap:16px; margin-top:16px; align-items:start}
.smena-side{display:flex; flex-direction:column; gap:16px}
.mtx-empty{padding:44px 20px; text-align:center; color:var(--muted); border:1px dashed var(--border-strong); border-radius:12px; margin-top:2px}
.cashfact{display:flex; flex-direction:column; gap:4px; margin-top:14px; font-size:12px; font-weight:600; color:var(--muted)}
.reconcile{margin-top:10px; padding:9px 12px; border-radius:10px; font-weight:700; text-align:center; font-size:14px}
.reconcile.bad{background:rgba(224,49,49,.12); color:var(--danger)}
.reconcile.warn{background:rgba(245,159,0,.14); color:#c07a00}
.reconcile.ok{background:rgba(46,160,67,.12); color:var(--ok)}
.costline{display:grid; grid-template-columns:100px 1fr 1.2fr auto; gap:8px; align-items:center; margin-bottom:8px}
.costline .field{margin:0; padding:8px 10px}
.costdel{border:0; background:none; color:var(--danger); cursor:pointer; font-size:14px; padding:4px}
.costs-total{margin-top:6px; padding-top:10px; border-top:1px solid var(--border); font-weight:700}
.head-actions .dateform{margin-bottom:0}
.head-actions .field{height:40px; padding-top:0; padding-bottom:0}
.head-actions .btn-ghost{height:40px; padding-top:0; padding-bottom:0; display:inline-flex; align-items:center}
/* ⚠️ Шапка смены при сужении окна (разработчик 29.09.2026). Общая .page-head — два flex-элемента, которые
   сжимаются вместе: заголовок «Смена · Город · Салон» ломался на «…Салон / 1», а кнопки справа складывались
   в две неровные строки (дата отдельно, под «+ Продажа»), и на ~1300 px высота шапки скачком менялась с 42 на
   92 px. Теперь у заголовка своя «опорная» ширина (строка названия целиком, см. ниже) и он её не
   отдаёт: когда кнопки рядом не помещаются, flex-wrap переносит их ЦЕЛИКОМ строкой ниже, у правого края —
   там же, где они стояли, только ниже. Внутри группы кнопки переносятся сами (узкое окно + «Я сегодня
   в другом салоне»), тоже от правого края. align-items:flex-start: кнопки держатся верха заголовка, а не
   его низа — строки-подписи под заголовком (сохранена / проверена / подтверждена) их не двигают. */
.smena-head{flex-wrap:wrap; align-items:flex-start; gap:12px 16px}
/* ⚠️ Опорная ширина — не число, а сам заголовок одной строкой (ревью 29.09.2026): 440px было подогнано под
   «Смена · Флорипа-стенд · Салон 1», и с длинным «город · салон» кнопки помещались рядом по 440, а h1 ломался
   на две-три строки (стенд: «Санто-Антонио де Лисбоа · Лагоа да Консейсао» — от 1340 до 1820 px). Теперь
   flex-basis:auto = max-content блока, а подписи под заголовком (сохранена / проверена — длинные) в него не
   входят: contain:inline-size обнуляет их вклад в ширину, их собственный перенос не меняется. Кнопки уходят
   строкой ниже ровно тогда, когда рядом с ЦЕЛЫМ заголовком им нет места. Заголовок шире всей строки — один в
   своём ряду, сжимается (min-width:0) и переносится ровно (balance), горизонтальной прокрутки нет. */
.smena-head > .smena-title{flex:1 1 auto; min-width:0}
.smena-head > .smena-title > :not(.h1){contain:inline-size}
.smena-head .h1{text-wrap:balance}
.smena-head > .head-actions{flex:0 1 auto; margin-left:auto; justify-content:flex-end; max-width:100%}
.smena-head .head-actions > *{flex:none}
.smena-foot{display:flex; align-items:center; justify-content:flex-end; gap:12px; margin-top:18px; padding-top:16px; border-top:1px solid var(--border)}
.smena-foot .btn-primary{margin-top:0; padding:11px 20px}
.smena-foot .btn-ghost{padding:11px 20px}
/* ⚠️ Подвал матриц планов (разработчик 29.09.2026, «кнопка меняется размерами от мыши»): matrix.js
   переписывает «Сейчас правится: …» при НАВЕДЕНИИ на ячейку, и длина строки скачет от «—» до «Салон 3 —
   Центр · Лимит терминала, %». Строка была обычным flex-элементом: в узком окне она отбирала место у кнопки,
   «Сохранить…» переносилась в две строки (39 → 56 px) и ширина кнопки плясала от ячейки к ячейке, а в
   широком — строку прижимало вправо, и она ездила влево-вправо под курсором. Теперь подсказка берёт ТОЛЬКО
   остаток места (flex-basis 0), стоит у левого края и обрезается многоточием; кнопка и состояние черновика
   своей ширины не отдают и не переносятся. */
.smena-foot .pm-hint{flex:1 1 0; min-width:0; margin-right:auto; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.smena-foot > .btn-primary, .smena-foot > .btn-ghost{flex:none; white-space:nowrap}
.smena-foot > .fd-state{flex:0 1 auto; min-width:0}
.confirm-box{display:flex; align-items:center; gap:7px; font-size:13px; font-weight:600}
/* ⚠️ Левая группа подвала смены держит отступ до кнопок ОДНА. Внутри неё флажок «Подтверждено»
   и строка «после сохранения смену меняли», которая включается посреди работы, по опросу: дай
   margin-right:auto им обоим — два auto поделят свободное место пополам и флажок уедет из
   левого края на середину; не дай никому — появившаяся строка толкнёт кнопки влево. Группа
   есть всегда, в том числе у админа, которому флажок не показывается. */
.smena-foot-left{display:flex; align-items:center; gap:12px; min-width:0; margin-right:auto}
.ok-tag{color:var(--ok); font-weight:700}
/* «Подтверждено» финансиста в шапке смены: строка и кнопка в одну линию (approval.py) */
.approve-line{display:flex; align-items:center; flex-wrap:wrap; gap:6px 12px; margin-top:4px}
.approve-form{display:inline-flex; margin:0}
.approve-form .btn-primary,.approve-form .btn-ghost{padding:5px 12px; font-size:13px}

/* ---------- смена: чужие правки, строки об изменениях, ящик остатка ---------- */
/* is-over — ваше значение заменил другой человек (строка наверху, пока её не скрыли);
   is-remote — чужая правка, 3 с после появления; is-hint — поле под курсором, а на сервере уже
   другое. Рамка изнутри (inset): поле не сдвигается под рукой. Правила стоят ниже подсветки
   строки и колонки под курсором и перебивают её при равной силе селектора. */
.mtx tbody td .cell.is-over, .entry-inputs .field.is-over, .cashfact .field.is-over, .costline .field.is-over{box-shadow:inset 0 0 0 2px var(--warn)}
.mtx tbody td .cell.is-remote, .entry-inputs .field.is-remote, .cashfact .field.is-remote, .costline .field.is-remote{background:var(--mtx-hl-strong)}
.mtx tbody td .cell.is-hint, .entry-inputs .field.is-hint, .cashfact .field.is-hint, .costline .field.is-hint{box-shadow:inset 0 0 0 2px var(--accent)}
/* «Забрал наличными» больше «Зарплаты» (smena.js paintTakedOver, разработчик 29.09.2026): мягкий красный
   КОНТУР, не заливка — это вопрос, а не ошибка. outline, а не box-shadow: тенью уже рисуют «занято»/«чужая
   правка», и одно правило затирало бы другое. В фокусе рамка фокуса сама становится красной — видно при вводе. */
.mtx tbody td.wt-over .cell{outline:1.5px solid color-mix(in srgb, var(--danger) 65%, transparent); outline-offset:-2px}
.mtx tbody td.wt-over .cell:focus{outline:2px solid var(--danger)}
/* Пустой «Выход» с подсказкой «по должн. N» (smena.js toWorkAttrs, data-tw-hint): сам не подставился — человеку задали
   должность уже в смене, выход стёрли или он работал не здесь. Оранжевый контур, пока ячейка пуста (:placeholder-shown
   гаснет сам, как только сумму вписали), — чтобы её не пропустили (разработчик 30.09.2026). */
.mtx tbody td .cell[data-tw-hint]:placeholder-shown{outline:1.5px solid color-mix(in srgb, var(--warn) 80%, transparent); outline-offset:-2px}
.mtx tbody td .cell[data-tw-hint]:placeholder-shown:focus{outline:2px solid var(--warn)}
/* Пустые «Клиенты» у человека с суммой в процентной строке (smena.js needClients → data-cl-need, разработчик 05.10.2026):
   тот же оранжевый контур — без клиентов его суммы не войдут в средний чек. Метку ставит и снимает пересчёт итогов: «0»
   тоже пусто, поэтому не :placeholder-shown. */
.mtx tbody td .cell[data-cl-need]{outline:1.5px solid color-mix(in srgb, var(--warn) 80%, transparent); outline-offset:-2px}
.mtx tbody td .cell[data-cl-need]:focus{outline:2px solid var(--warn)}
.mtx thead th.mcol.is-hint, .costline.is-hint{box-shadow:inset 0 0 0 2px var(--accent)}
.mtx thead th.mcol.is-remote, .costline.is-remote{background:var(--mtx-hl-strong)}
/* ⚠️ Отметка строки издержки красится И НА ПОЛЯХ, а не только на контейнере. Три поля строки
   растянуты на всю её высоту и имеют СВОЙ непрозрачный фон (.field{background:var(--surface)}),
   поэтому рамка на .costline видна только в 8-пиксельных зазорах между ними и у крестика — и
   не доходит до поля, в котором стоит курсор. Ровно об эту ловушку предупреждает страж
   apps/portal/tests.py::test_matrix_focus_highlight_reaches_the_input, но он покрывает .mtx. */
.costline.is-hint .field{box-shadow:inset 0 0 0 2px var(--accent)}
.costline.is-remote .field{background:var(--mtx-hl-strong)}
.shift-notes .cn-line{margin:0 0 6px}
.shift-notes .cn-line:last-child{margin-bottom:0}
.shift-notes .cn-block{margin:0 0 8px}
.shift-notes .cn-head{margin:0 0 4px; font-weight:600; color:var(--ink)}
.shift-notes .cn-lines{margin:0; padding-left:18px}
.late-rec{padding-bottom:10px}
.late-rec + .late-rec{padding-top:10px; border-top:1px solid var(--border)}
.late-rec p{margin:0 0 6px}
.late-rec .form-actions{margin-top:4px}
/* подвал: после сохранения смену меняли другие. Без margin-right:auto — он на .smena-foot-left */
.smena-aftersave{font-size:13px}
.smena-aftersave[hidden]{display:none}
#syncnote a{color:inherit; font-weight:600; margin-left:6px}

/* профиль / форма аккаунта */
.kv{display:flex; justify-content:space-between; gap:12px; padding:7px 0; border-bottom:1px solid var(--border)}
.acc-form input{width:100%; padding:10px 12px; font-size:14px; border:1px solid var(--border-strong);
  border-radius:10px; margin-bottom:6px; font-family:inherit; background:var(--surface); color:var(--ink)}
.acc-form input:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(124,92,255,.14)}
.acc-form .helptext{font-size:11px; color:var(--muted); display:block; margin:-2px 0 6px}
.acc-form select.field{width:100%}
.check{display:flex; align-items:center; gap:8px; margin-top:14px; font-weight:600; font-size:14px; color:var(--ink)}
.check input{width:16px; height:16px}

/* пояснение-плашка */
.note-box{background:rgba(124,92,255,.06); border:1px solid var(--border);
  border-radius:12px; padding:12px 16px; font-size:13px; color:var(--muted); margin-bottom:18px; line-height:1.5}
.note-box b{color:var(--ink)}

/* ===== Адаптив ===== */
@media (max-width:900px){
  body.app{grid-template-columns:0 minmax(0,1fr)}
  .sidebar{position:fixed; left:0; top:var(--topbar-h); bottom:0; width:264px; z-index:55; height:auto;
    background:var(--surface); box-shadow:0 18px 50px rgba(25,27,46,.18)}
  html[data-sidebar="off"] .sidebar{display:none}
  .topbar{padding:0 12px}
  .user-name{display:none}
  .fresh-chip{display:none}
  .content{padding:18px 14px 40px}
  .cols{grid-template-columns:1fr}
  .wrap{padding:20px 16px 48px}
}

/* Текущий штат салона */
.staff-groups{display:flex; flex-direction:column; gap:14px}
.staff-list{display:flex; flex-wrap:wrap; gap:8px}
.staff-chip{display:inline-flex; align-items:center; gap:8px; padding:7px 12px;
  background:var(--bg); border:1px solid var(--border); border-radius:999px; font-size:13px}
.staff-chip .dot{width:9px; height:9px; border-radius:50%; flex:none; border:1px solid var(--border-strong)}
.staff-name{font-weight:600; color:var(--ink)}
.staff-seen{color:var(--muted); font-size:11px}

/* a11y: видимый фокус с клавиатуры (WCAG 2.4.7) */
a:focus-visible, button:focus-visible, summary:focus-visible, .card:focus-visible,
.side-link:focus-visible, .dd-item:focus-visible, input:focus-visible, select:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px}
/* закрытое меню-дропдаун не держим в tab-порядке/потоке */
details.dd:not([open]) .dd-menu{display:none}
/* контраст: бледную рамку-как-текст заменяем на серый */
.pager .off, .day-flag.off, .crumb-sep{color:var(--muted)}
.empty-search{padding:18px; color:var(--muted); text-align:center}

/* выравнивание кнопок в шапке (btn-primary имеет margin-top для формы — гасим в шапке) */
.head-actions .btn-primary{margin-top:0}
/* разделители-группы в дропдауне городов (наши/зарубежные/франшизы) */
.dd-group{padding:8px 12px 4px; font-size:10px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); font-weight:700}
.dd-group:not(:first-child){border-top:1px solid var(--border); margin-top:4px}
/* мультивыбор городов в форме пользователя */
.city-checks{display:flex; flex-direction:column; gap:6px; padding:6px 0}

.side-foot-link{padding:6px 10px; font-size:12px; color:var(--muted); border-radius:8px}
.side-foot-link:hover{color:var(--ink); background:var(--bg)}


a.side-city-single:hover{color:var(--accent-ink)}
.side-dd{position:relative}
.side-dd > summary{list-style:none; cursor:pointer}
.side-dd > summary::-webkit-details-marker{display:none}

/* кликабельные крошки в топбаре: всё, куда можно вернуться, — серым, где стоишь — жирным.
   Иначе «Москва / Товары / Химки» не отличала ссылку «Товары» от текущего «Химки» (17.09.2026) */
.crumb-link{color:var(--muted); font-weight:500}
.crumb-link:hover{color:var(--accent-ink)}
.field-error{border-color:var(--danger); box-shadow:0 0 0 3px rgba(229,72,77,.12)}
/* строка кнопок формы (сохранить + отмена) */
.form-actions{display:flex; gap:10px; align-items:center; margin-top:16px}
.form-actions .btn-primary{margin-top:0}
.btn-danger{padding:10px 18px; border:1px solid var(--danger); border-radius:10px; font-weight:600; color:#fff; background:var(--danger); cursor:pointer; white-space:nowrap}
.btn-danger:hover{opacity:.92}
/* поле пароля: глазик + генерация */
.pw-row{display:flex; gap:8px; align-items:center}
.pw-row .field{flex:1}
.pw-hint{margin-top:6px; font-size:13px; color:var(--accent-ink); word-break:break-all}
/* проверка yclients ID при создании салона */
.yc-row{display:flex; gap:8px; align-items:center}
.yc-row .field{flex:1}
.yc-result{margin-top:6px; font-size:13px}
.yc-result.ok{color:var(--ok)}
.yc-result.err{color:var(--danger)}
.yc-result.warn{color:#c98a00}
/* поиск в городских списках (мастера/услуги) */
.masters-search{display:flex; gap:8px; align-items:center; margin-bottom:12px; flex-wrap:wrap}
.masters-search .field{max-width:340px}
.bars a.bar-row{text-decoration:none; border-radius:8px; padding:6px 8px}
.bars a.bar-row:hover{background:var(--bg)}
.bars a.bar-row.is-active{background:var(--bg); font-weight:600; color:var(--accent-ink)}
/* поиск внутри дропдауна городов */
.dd-search{width:100%; margin-bottom:6px; font-size:13px}
.dd-noresult{padding:8px 12px; color:var(--muted); font-size:13px}
.citymenu{max-height:60vh; overflow:auto}
/* топбар: дропдаун юзера прижимаем в правый угол */
.spacer{flex:1 1 auto}
/* кнопка-действие в крошках топбара («+ Создать салон») */
.crumb-action{margin-left:8px; padding:5px 12px; border:1px solid var(--accent); border-radius:8px;
  font-size:13px; font-weight:600; color:var(--accent-ink); white-space:nowrap}
.crumb-action:hover{background:var(--accent); color:#fff}

/* футер сайдбара (о компании/программе) */
.side-foot{margin-top:auto; padding-top:10px; border-top:1px solid var(--border); display:flex; flex-direction:column; gap:2px}
/* дропдаун салонов раскрывается вниз, на ширину сайдбара */
.side-dd .dd-menu{position:absolute; left:0; right:0; min-width:0; z-index:60}
/* «Весь город» — первым пунктом и отдельно от салонов: это выход из салона, а не ещё один салон */
.dd-sep{height:1px; margin:4px 6px; background:var(--border)}

/* ===== Тёмная тема: оверрайды сообщений (фоны захардкожены выше) ===== */
[data-theme="dark"] .msg-error, [data-theme="dark"] .auth-err{background:rgba(229,72,77,.18); color:#ff9a9d}
[data-theme="dark"] .msg-success{background:rgba(46,201,138,.16); color:#63e2ab}
[data-theme="dark"] .msg-warning{background:rgba(255,178,36,.14); color:#ffc661}
[data-theme="dark"] .msg-info{background:rgba(96,165,250,.14); color:#9cc7ff}

/* плашки ввода дня (были инлайн-стилями — вынесены ради тёмной темы) */
.note-warn{background:#fdecec; color:#b42318; border-color:#f3c0c0}
.note-info{background:#eef6ff; color:#1b4a7a; border-color:#cfe0f7}
[data-theme="dark"] .note-warn{background:rgba(229,72,77,.16); color:#ff9a9d; border-color:rgba(229,72,77,.35)}
[data-theme="dark"] .note-info{background:rgba(124,92,255,.14); color:#b5c6ff; border-color:rgba(124,92,255,.35)}

/* подсказка по штату в форме пользователя */
.staff-suggest{border:1px solid var(--border); border-radius:8px; margin-top:4px; overflow:hidden;
  background:var(--surface)}
.staff-suggest-row{display:block; width:100%; text-align:left; padding:8px 10px;
  background:none; border:0; cursor:pointer; font:inherit; color:inherit}
.staff-suggest-row:hover, .staff-suggest-row:focus-visible{background:rgba(124,92,255,.06)}

/* ─────────────────────────────────────────────────────────────────────────────
   VOICE — отзывы и репутация.

   Графики намеренно одноцветные. Раскрашивать серии нечем: проверка палитры
   показала, что красный (--danger) и янтарный (--warn) проекта различаются хуже
   порога — ΔE 4.6 при дейтеранопии и 13.0 даже при нормальном зрении, то есть
   соседние полосы слились бы. Длина полосы, число и подпись несут всё значение,
   а цвет остаётся одиночным акцентом там, где рядом нет конкурента.
   ───────────────────────────────────────────────────────────────────────────── */

/* плитки с числами-заголовками */
.vtiles{display:grid; grid-template-columns:repeat(auto-fit,minmax(184px,1fr)); gap:12px; margin-bottom:16px}
.vtile{background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:14px 16px; display:flex; flex-direction:column; gap:4px; box-shadow:var(--shadow)}
.vtile-k{font-size:12px; color:var(--muted); font-weight:600}
.vtile-v{font-size:26px; font-weight:800; line-height:1.1; display:flex; align-items:baseline; gap:8px}
.vtile-h{font-size:11.5px; color:var(--muted)}
.vtile-pct{font-size:14px; font-weight:700; color:var(--muted)}
.vtile.is-alert .vtile-v{color:var(--danger)}

/* график */
.vchart{margin:0}
.vchart-cap{display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-bottom:8px}
.vchart-t{font-weight:700; font-size:14px}
.vchart-h{font-size:12px; color:var(--muted)}
.vchart-now{margin-left:auto; font-weight:800; font-size:18px}
/* Высоту диктуют атрибуты width/height на самом теге: у SVG появляются собственные
   размеры, и height:auto выводит высоту ровно из них.

   ⚠️ Ни max-width, ни overflow:visible сюда возвращать нельзя. Предел ширины
   разъезжался с высотой — блок считался по одной ширине, рисунок по другой, — а
   overflow:visible не давал лишнему обрезаться, и подписи дат ложились на соседнюю
   панель. Размер на широких мониторах держит вытянутое соотношение viewBox. */
.vchart-svg{width:100%; height:auto; display:block}
.vchart-grid{stroke:var(--border); stroke-width:1}
.vchart-line{fill:none; stroke:var(--accent); stroke-width:2; stroke-linejoin:round; stroke-linecap:round}
.vchart-area{fill:var(--accent); opacity:.09; stroke:none}
.vchart-dot{fill:var(--accent); stroke:var(--surface); stroke-width:2}
.vchart-lbl{fill:var(--muted); font-size:11px}
.vchart-empty{padding:26px 12px; text-align:center; color:var(--muted); font-size:13px;
  border:1px dashed var(--border-strong); border-radius:10px; display:flex; flex-direction:column; gap:4px}

/* полосы салонов */
.vbar-track{flex:1; height:8px; min-width:80px; background:var(--bg); border-radius:6px; overflow:hidden}
.vbar-fill{display:block; height:100%; background:var(--accent); border-radius:6px}

/* сетка из трёх графиков */
.vgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:12px; margin-bottom:16px}

/* отзыв */
.vrev{padding:12px 0; border-bottom:1px solid var(--border)}
.vrev:last-child{border-bottom:none}
.vrev-head{display:flex; align-items:center; gap:10px; flex-wrap:wrap; font-size:12.5px}
.vrev-stars{font-weight:800; font-size:14px; display:inline-flex; align-items:center; gap:2px}
.vrev-star{color:var(--muted); font-size:11px}
.vrev.is-bad .vrev-stars{color:var(--danger)}
.vrev-place{font-weight:600; color:var(--accent-ink); text-decoration:none}
.vrev-place:hover{text-decoration:underline}
.vrev-author,.vrev-date{color:var(--muted)}
/* Метка площадки у отзыва. Свойства площадок разные (у DIKIDI нет ответов вовсе,
   рейтинг считается за полтора года), и читатель должен видеть, откуда отзыв.
   Нейтральная, без цвета-статуса: это не оценка, а происхождение. */
/* ── подсказка на графике ──────────────────────────────────────────────────────
   Перекрестие ищет ближайшую точку по горизонтали: читатель целится в дату, а не
   в двухпиксельную линию. Прицел — прозрачный прямоугольник во всю область данных,
   поэтому попадание не зависит от того, нарисован ли в этом месте кружок. */
.vchart{position:relative}
.vchart-hit{fill:transparent; cursor:crosshair}
.vchart-hov{opacity:0; transition:opacity .08s linear; pointer-events:none}
.vchart-hov.is-on{opacity:1}
.vchart-cross{stroke:var(--muted); stroke-width:1; stroke-dasharray:3 3; opacity:.7}
.vchart-cur{fill:var(--bg); stroke:var(--accent); stroke-width:2.5}
.vchart-read{position:absolute; top:0; right:0; display:none; align-items:baseline; gap:8px;
  padding:3px 9px; border:1px solid var(--line); border-radius:8px;
  background:var(--bg); box-shadow:0 2px 10px rgba(0,0,0,.10); pointer-events:none}
.vchart-read.is-on{display:inline-flex}
/* Значение ведёт, дата следует: у читателя уже есть график, ему нужно число. */
.vchart-read b{font-size:14px; font-weight:700; color:var(--fg); font-variant-numeric:tabular-nums}
.vchart-read span{font-size:11.5px; color:var(--muted); font-variant-numeric:tabular-nums}
/* Пояснение (сколько карточек за средним) — третьей строкой смысла, поэтому
   отделено чертой: это не часть даты. */
.vchart-read-n{border-left:1px solid var(--border); padding-left:8px}
.vchart-svg:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:6px}

.vrev-plat{font-size:11px; font-weight:600; letter-spacing:.02em; color:var(--muted);
  border:1px solid var(--line); border-radius:20px; padding:1px 7px; white-space:nowrap}
[data-theme="dark"] .vrev-plat{color:var(--muted); border-color:var(--line)}
/* Прижим вправо — на ГРУППЕ, а не на метке ответа: у площадок без ответов метки
   нет вовсе, и вместе с ней уезжало выравнивание всей шапки. */
.vrev-right{margin-left:auto; display:inline-flex; align-items:center; gap:10px}
.vrev-answered,.vrev-open{font-size:11.5px; font-weight:700; padding:2px 8px; border-radius:20px}
.vrev-answered{color:var(--ok); background:rgba(22,163,106,.10)}
.vrev-open{color:var(--danger); background:rgba(229,72,77,.10)}
.vrev-text{margin:8px 0 0; font-size:13.5px; line-height:1.5; white-space:pre-line}
.vrev-answer{margin:8px 0 0 14px; padding-left:12px; border-left:2px solid var(--border-strong);
  font-size:12.5px; color:var(--muted); line-height:1.5; white-space:pre-line}
.vrev-answer-k{display:block; font-weight:700; font-size:11px; text-transform:uppercase;
  letter-spacing:.04em; margin-bottom:2px}

/* Ссылка на сам отзыв на площадке. Приглушена нарочно: в ленте она стоит у каждого
   отзыва, и акцентным цветом строй дат и меток превратился бы в рябь. */
.vrev-out{font-size:11.5px; font-weight:600; color:var(--muted); text-decoration:none;
  white-space:nowrap; border-bottom:1px dotted var(--border-strong)}
.vrev-out:hover{color:var(--accent); border-bottom-color:var(--accent)}

/* предупреждение о сбоях сборщика */
.vwarn{border-color:var(--warn); position:relative; padding-right:44px}
.vwarn-list{margin:8px 0 0; padding-left:18px; font-size:12.5px; color:var(--muted)}
/* Крестик закрытия. Место под него выгорожено padding'ом панели, иначе он ложился бы
   на текст длинного сообщения о сбое. */
.vwarn-x{position:absolute; top:10px; right:10px; width:26px; height:26px; padding:0;
  display:inline-flex; align-items:center; justify-content:center;
  font-family:inherit; font-size:13px; line-height:1; color:var(--muted);
  border:0; border-radius:8px; background:none; cursor:pointer}
.vwarn-x:hover{color:var(--ink); background:var(--bg)}

/* Карточка на площадке — единственное действие экрана салона, поэтому кнопка, а не
   строка в подписи. Призрачная и помельче обычной: это уход НАРУЖУ, на чужой сайт, и
   заливать его акцентом — обещать главное дело экрана там, где его нет.
   ⚠️ Классом .btn её не помечаем: в общем файле .btn идёт ПОСЛЕ .btn-ghost и
   перекрывает его заливкой — снаружи панели фильтров пара «btn btn-ghost» выходит
   сплошной фиолетовой (у .toolbar на этот случай своя строка ниже). */
.vcard-btn{align-self:center; display:inline-flex; align-items:center; gap:6px;
  padding:8px 14px; font-size:13px; font-weight:600; text-decoration:none;
  color:var(--ink); background:var(--surface); white-space:nowrap}

.vpager{display:flex; align-items:center; gap:12px; justify-content:center; padding:12px 0}
/* Voice: панели складываются в столбец, между ними нужен воздух. Класс отдельный,
   чтобы не менять отступы .panel на других экранах. */
.vpanel{margin-bottom:16px}
.vpanel:last-child{margin-bottom:0}

/* Совмещение должностей: у человека несколько карточек YClients — по одной на роль.
   Значок рядом с именем говорит, что это тот же человек, а не однофамилец. */
.staff-chip .also{margin-left:.35em; padding:0 .35em; border-radius:3px;
  background:var(--accent); color:#fff; font-size:.72em; font-weight:600; line-height:1.5}

/* ---------- окно продажи товара ---------- */
/* ⚠️ Токены — только из :root. Блок ссылался на --line/--hover/--panel-2, которых нет:
   рамки списка не рисовались, подсветки пункта под курсором не было. */
/* Высота ограничена: в окне и прайс, и все продажи смены — без предела длинное окно
   уходило бы верхом за экран, где до него не добраться. */
.modal-wide { max-width: 520px; max-height: calc(100vh - 40px); overflow-y: auto; }
.sale-list { max-height: 260px; overflow-y: auto; margin: 8px 0; border: 1px solid var(--border); border-radius: 8px; }
.sale-item { display: flex; gap: 10px; align-items: baseline; width: 100%; padding: 8px 10px;
  background: none; border: 0; border-bottom: 1px solid var(--border); cursor: pointer;
  text-align: left; color: inherit; font: inherit; }
.sale-item:last-child { border-bottom: 0; }
.sale-item:hover { background: var(--bg); }
.sale-nm { flex: 1; }
.sale-pr { font-weight: 600; white-space: nowrap; }
.sale-st { color: var(--muted); font-size: .85em; white-space: nowrap; }
.sale-chosen { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 4px; }
/* Строки товарных продаж заполняет окно, а не рука: за ними стоит документ со снимком цен.
   Фон — как у расчётных строк матрицы (tr.auto); подсветка строки и колонки под курсором
   его перекрывает — у неё селектор сильнее. */
.rowauto .cell[readonly] { background: var(--bg); color: var(--muted); }

/* ---------- товары: салоны и остатки ---------- */
.pf-bulk{display:flex; gap:8px}
.salonstock td, .salonstock th{vertical-align:middle}
.salonstock .c-pick{width:34px; text-align:center}
.salonstock .c-pick input{width:16px; height:16px; margin:0; accent-color:var(--brand); cursor:pointer}
.salonstock .salonstock-nm{display:block; cursor:pointer; font-weight:500; color:var(--ink)}
.salonstock .stock-in{width:110px; text-align:right}
/* Снятый салон гаснет целиком: остаток без салона ничей, и держать его ярким — врать */
.salonstock tr.is-off .salonstock-nm{color:var(--muted)}
.salonstock tr.is-off .stock-in{opacity:.45}

/* ---------- счётчик количества в окне продажи ---------- */
.qty-block{display:flex; flex-direction:column; align-items:center; gap:8px; margin:16px 0 4px}
.qty-label{font-size:12px; font-weight:600; color:var(--muted)}
.qty-step{display:flex; align-items:stretch; gap:8px}
.qty-btn{width:42px; border:1px solid var(--border); background:var(--surface); color:var(--ink);
  border-radius:10px; font-size:20px; line-height:1; font-weight:700; cursor:pointer}
.qty-btn:hover{background:var(--bg); border-color:var(--border-strong)}
.qty-btn:active{transform:translateY(1px)}
.qty-in{width:92px; text-align:center; font-size:17px; font-weight:700}
/* стрелки браузера рядом с нашими кнопками — лишний шум */
.qty-in::-webkit-outer-spin-button, .qty-in::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
.qty-in{-moz-appearance:textfield; appearance:textfield}

/* ---------- список проданного за смену ---------- */
.sale-done{margin-top:14px}
.sale-done:empty{display:none}
.sale-done-h{font-size:12px; font-weight:600; color:var(--muted); margin-bottom:6px}
.sale-done-row{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:6px 0; border-top:1px solid var(--border); font-size:13px}
/* строка, которую сейчас правят в форме окна: видно, что именно изменится */
.sale-done-row.is-editing{margin:0 -8px; padding:6px 8px; border-radius:8px; background:var(--mtx-hl-strong)}
/* «Изменить» и «×» (или «Убрать? Да / Нет») не сжимаются: переносится название товара */
.sale-acts{display:flex; align-items:center; gap:4px; flex:none; white-space:nowrap}
.sale-ask{color:var(--ink); font-weight:600}
/* пояснение под строкой, которая при сохранении смены не запишется или не трогается */
.sale-note{display:block; margin-top:2px; font-size:12px; color:var(--warn)}
.sale-acts .linkbtn{margin-left:8px}
.linkbtn.is-danger{color:var(--danger)}

/* ---------- окно оплат по терминалу (сложный процент эквайринга, карточка 49) ---------- */
/* Шире окна продажи: журнал в шесть колонок (№, сумма, карта, %, эквайринг, действия) не должен переноситься. */
.modal-wide.term-modal{max-width:600px}
.term-cards{display:flex; flex-wrap:wrap; gap:8px 18px; margin:12px 0 2px; font-size:14px}
.term-card{display:inline-flex; align-items:center; gap:6px; cursor:pointer}
.term-card input{margin:0; accent-color:var(--accent)}
.term-err{margin:10px 0 0}
.term-list{margin-top:16px; font-size:13px}
.term-list th, .term-list td{padding:6px 8px}
.term-list td.num{text-align:right; white-space:nowrap}
/* ширина колонки действий — под «Править Удалить» и под вопрос «Удалить? Да Нет»: журнал не прыгает */
.term-list .term-act{text-align:right; white-space:nowrap; width:150px}
.term-list .term-act .linkbtn{margin-left:8px}
.term-list tr.is-editing td{background:var(--mtx-hl-strong)}
.term-list .term-empty td{color:var(--muted); text-align:center}
.term-ask{color:var(--ink); font-weight:600}
.term-totals{display:flex; flex-wrap:wrap; gap:4px 18px; margin-top:10px; font-size:13.5px; color:var(--muted)}
.term-totals b{color:var(--ink); font-weight:800; font-variant-numeric:tabular-nums}
/* Окно абонементов (разработчик 07.10.2026) — по образцу окна оплат: форма в строку, список и итог — классы .term-* */
.modal-wide.abon-modal{max-width:640px}
/* .abon-entry, а не .abon-form: .abon-form — форма «Параметров расчёта» (ревью 07.10.2026: правило окна перекрашивало её) */
.abon-entry{display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px 14px; margin:0 0 10px}
.abon-entry > .cashfact{flex:1 1 220px; min-width:0}
.abon-entry > .modal-text, .abon-entry > .note-box, .abon-entry > .modal-actions{flex:1 1 100%}
.abon-list td.abon-who{white-space:normal; min-width:120px}
/* Окно депозитов клиентов (разработчик 07.10.2026): вид — сегмент «Внести депозит | Списать в счёт депозита» (радио внутри
   .seg-item, выбранный — по :has, как способ выплаты .wp-method), сумма, список движений смены и итог. .dep-entry, а не
   .dep-form: .dep-form — форма «Параметров расчёта» */
.modal-wide.dep-modal{max-width:560px}
.dep-entry{display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px 14px; margin:0 0 10px}
.dep-entry > .seg{flex:none; height:40px; max-width:100%}
.dep-kind .seg-item{display:inline-flex; align-items:center; padding:0 14px}
.dep-kind .seg-item:has(> input:checked){color:#fff; background:var(--accent)}
.dep-kind:has(input:focus-visible){box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent)}
.dep-entry > .cashfact{flex:1 1 180px; min-width:0}
.dep-entry > .modal-text, .dep-entry > .note-box, .dep-entry > .modal-actions{flex:1 1 100%}
.dep-list td.dep-what{white-space:nowrap}
/* «из них VAT … · продавцам … · салону …» под «Продано абонементов» — мельче, только проверяющим смены */
.et-row.et-abon-detail{padding-top:0}

/* ---------- «+ Лид» в сайдбаре смены и его окно ---------- */
/* ⚠️ Пункт — <button>, а не ссылка: он открывает окно и никуда не ведёт. .side-link описан
   под <a>, и без сброса кнопку нарисовал бы браузер — серой, чужим шрифтом, по центру.
   Страж классов кнопок (test_every_button_class_is_defined) этого не видит: side-link не btn*. */
.side-link.side-action{display:block; width:100%; border:0; background:none; font:inherit;
  font-weight:600; font-size:13.5px; text-align:left; color:var(--ink); cursor:pointer}
.side-link.side-action:hover{background:rgba(124,92,255,.06)}
.lead-sub{margin-bottom:12px}
.lead-note{margin:0 0 10px}
.lead-dup-acts{display:inline-flex; gap:4px; margin-left:6px; white-space:nowrap}
/* ошибка под полем: словами сервера, рядом с тем, что надо поправить */
.lead-err{display:block; margin-top:4px; font-size:12px; color:var(--danger)}
.lead-err[hidden]{display:none}
.lead-today{margin-top:14px}
.lead-today:empty{display:none}
.lead-today-h{font-size:12px; font-weight:600; color:var(--muted); margin-bottom:6px}
.lead-row{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:6px 0; border-top:1px solid var(--border); font-size:13px}
.lead-st{flex:none; font-size:12px; font-weight:600; color:var(--muted)}
.lead-st.is-sent{color:var(--ok)}
.lead-st.is-lost{color:var(--danger)}

/* ===== Панель фильтров (.toolbar): одна на все экраны с фильтром =====
   Замечание владельца 17.09.2026 («Зарплаты», «Издержки»): кнопки периода, поля дат, «Охват»,
   «Применить» и «Выгрузить» стояли пяти разных высот — 37, 41, 41, 37 и 42 px (замер в Chrome), и
   подписи над ними съезжали вслед. Каждый контрол брал высоту из своих padding, кегля и рамки,
   поэтому здесь она задана ЯВНО, одной переменной, для всего, что кладут в панель; вертикальный
   padding обнулён — текст по центру ставит flex. Отступ между элементами один — gap панели.
   ⚠️ Блок стоит в КОНЦЕ файла намеренно: базовые .btn, .btn-ghost, .dd-btn объявлены выше и той же
   силы по фону и отступам, а input[type="date"].field{min-height:41px} сильнее по специфичности —
   объявленный раньше них, блок молча проигрывал бы (порядок держит tests_filter_bar). */
.toolbar{--tb-h:40px; display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:14px 16px; box-shadow:var(--shadow); margin-bottom:16px}
.toolbar .lbl{display:block; margin:0 0 6px; font-size:12px; line-height:16px; white-space:nowrap}
.tb-field{display:flex; flex-direction:column; min-width:0}
/* подпись-галочка без поля под ней: во всю высоту контрола, иначе она прилипает к низу панели */
.tb-field > .lbl:only-child{display:flex; align-items:center; gap:7px; height:var(--tb-h); margin:0; cursor:pointer}
.tb-actions{display:flex; align-items:flex-end; gap:12px; margin-left:auto}
.toolbar .field, .toolbar .seg, .toolbar .btn, .toolbar .btn-ghost, .toolbar .btn-primary, .toolbar .dd-btn{
  height:var(--tb-h); min-height:0; margin:0; box-sizing:border-box}
.toolbar input.field{min-height:0}
.toolbar .field{padding-top:0; padding-bottom:0; font-size:14px}
/* выбор салона не раздувает панель длинным названием: не влезшее обрежет сам select */
.toolbar select.field{max-width:220px}
/* ⚠️ Одного предела мало: строки flex раскладывает по базовому размеру элемента, а сжимает уже потом.
   С названиями салонов Москвы («Тургеневская procomfort») «Охват» шириной 220 px на экране 1280 px
   выталкивал «Выгрузить» на вторую строку (ревью 17.09.2026). Поэтому поле выбора в строку ставится
   как 130 px (влезает «Весь город»), а свободное место добирает до своей ширины — не больше 220 px и
   не шире самого длинного варианта (max-content): короткие «Все» у «Заявок» не растягиваются. */
.toolbar .tb-field:has(> select.field){flex:1 1 130px; max-width:max-content}
.toolbar .seg{display:inline-flex; align-items:stretch}
/* боковые отступы сегмента и кнопок — 12 и 14 px, а не 14 и 16: «Зарплаты» и «Издержки» (пресеты, две
   даты, «Охват», «Выгрузить») должны встать в строку на 1920 px с масштабом Windows 150 % и полосой
   прокрутки — это 1263 px, и с прежними отступами им не хватало нескольких пикселей */
.toolbar .seg-item{display:inline-flex; align-items:center; padding:0 12px; line-height:1}
.toolbar .btn, .toolbar .btn-ghost, .toolbar .btn-primary, .toolbar .dd-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px; padding:0 14px;
  border-radius:10px; font-family:inherit; font-size:14px; font-weight:600; line-height:1;
  white-space:nowrap; text-decoration:none}
/* .btn-ghost после .btn: у «Сбросить» оба класса, и призрачная кнопка не должна заливаться акцентом */
.toolbar .btn-ghost{border:1px solid var(--border-strong); background:var(--surface); color:var(--ink)}
.toolbar .btn-ghost:hover{border-color:var(--accent); background:var(--surface)}
.toolbar .btn-primary{border:1px solid transparent}
/* стрелки дня («Город за день») — квадрат высотой с поле */
.toolbar .datenav .btn-ghost{width:var(--tb-h); padding:0}
/* строка счётчиков рядом с сегментом («По направлениям»): по центру высоты контрола, а не у низа */
.toolbar > p{margin-top:0; margin-bottom:0; line-height:var(--tb-h)}

/* «Непроверенные смены» (templates/finance/unchecked.html): деньги и «открыть →» — в одну линию, а
   салон и «кто сохранил» (имя, под ним время) переносятся: одной строкой длинные «Фамилия И. О.» и
   тег «закрыт» уводили последнюю колонку под прокрутку на 1280 px (ревью выката C). */
#tbl-unchecked td{white-space:nowrap}
#tbl-unchecked td.td-wrap{white-space:normal; min-width:110px}
#tbl-unchecked .btn-ghost{padding:6px 12px}

/* Переключатель «+ непроверенные» на отчётных экранах (templates/portal/_plus_toggle.html,
   apps/portal/plus.py): ссылка-тумблер; включён — акцентный, чтобы режим не забывался. */
.plus-toggle{display:inline-flex; align-items:center; gap:8px; min-height:36px; padding:0 12px 0 8px;
  border:1.5px solid var(--border-strong); border-radius:10px; background:var(--surface);
  color:var(--muted); font-weight:600; font-size:13px; text-decoration:none; white-space:nowrap}
.plus-toggle:hover{color:var(--ink)}
.plus-knob{position:relative; width:28px; height:16px; border-radius:8px; background:var(--border-strong);
  flex:none; transition:background .15s}
.plus-knob::after{content:''; position:absolute; top:2px; left:2px; width:12px; height:12px;
  border-radius:50%; background:#fff; transition:left .15s}
.plus-toggle.is-on{color:var(--accent-ink); border-color:var(--accent)}
.plus-toggle.is-on .plus-knob{background:var(--accent)}
.plus-toggle.is-on .plus-knob::after{left:14px}
.plus-row{display:flex; justify-content:flex-end; margin:0 0 10px}
.plus-note{color:var(--accent-ink)}

/* Экран отчётности «только просмотр» (финансист, владелец 24.09.2026): поля видно, править нечем.
   ⚠️ Явный цвет и фон: браузер рисует disabled-поле блёклым, а числа плана в нём надо ЧИТАТЬ. */
.ro-form{border:0; margin:0; padding:0; min-width:0}
.ro-form input:disabled, .ro-form select:disabled{color:var(--ink); opacity:1; -webkit-text-fill-color:var(--ink)}
.ro-form .field-plan:disabled{background:transparent; cursor:default}
.ro-note{margin:10px 0 0}

/* «Смены»: оплаты по картам (сложный процент эквайринга, карточка 49) — сводка за месяц и окно оплат дня */
.acqd-open{margin-left:0; white-space:nowrap}
.acqd-head{display:flex; flex-wrap:wrap; align-items:center; gap:8px 18px; margin:0 0 10px}
.acqd-head .h2{margin:0}
.acqd-nav{display:flex; align-items:center; gap:10px; font-size:14px}
.acqd-nav .off{color:var(--muted); opacity:.5}
.acqd-head .export-dd{margin-left:auto}
.acqd-sum{max-width:640px; font-size:13.5px}
.acqd-sum td.num, .acqd-sum th.num, .acqd-list td.num, .acqd-list th.num{text-align:right; white-space:nowrap}
.acqd-sum .acqd-total td{font-weight:700; border-top:1px solid var(--border)}
.acqd-note{margin:8px 2px 0}
.modal-wide.acqd-modal{max-width:560px}
.acqd-list{margin-top:8px; font-size:13px}
.acqd-list th, .acqd-list td{padding:6px 8px}
.acqd-list .acqd-empty td{color:var(--muted); text-align:center}
.acqd-totals{display:flex; flex-wrap:wrap; gap:4px 18px; margin-top:10px; font-size:13.5px; color:var(--muted)}
.acqd-totals b{color:var(--ink); font-weight:800; font-variant-numeric:tabular-nums}

/* «История смены» → «Правки в смене» (журнал правок, разработчик 07.10.2026): группа — время и кто, ниже правки строками */
.shift-edits .se-group + .se-group{margin-top:10px; padding-top:10px; border-top:1px solid var(--border)}
.shift-edits .se-head{margin:0 0 4px; font-size:13.5px}
.shift-edits .se-lines{margin:0; padding-left:18px; font-size:13.5px; line-height:1.55; font-variant-numeric:tabular-nums}

/* «Справка» экрана (apps/tgbot/templatetags/aurahelp.py, 12.10.2026): окно с описанием экрана — тем же, по которому
   отвечает Aura AI; кнопка, поле, пункт меню — <kbd>, как на экране */
.helpdoc{font-size:13.5px; line-height:1.5; padding-right:6px}
.helpdoc h3{font-size:14px; margin:14px 0 6px}
.helpdoc p{margin:6px 0}
.helpdoc ol, .helpdoc ul{margin:4px 0 8px; padding-left:20px}
.helpdoc li{margin:3px 0}
.helpdoc kbd{display:inline-block; padding:0 6px; border:1px solid var(--border-strong); border-bottom-width:2px;
  border-radius:6px; background:var(--surface); color:var(--ink); font:inherit; font-size:12.5px; line-height:1.45;
  white-space:nowrap}
.helpdoc-foot{margin-top:12px; color:var(--muted); font-size:12.5px}
