/* Qarvan — дизайн-система «Спокойный сервис» (design-system/MASTER.md).
   Токены §1-3, shell §4.1-4.2, компоненты §4.3-4.12. Все 8 admin-страниц переведены
   на классы-компоненты (эталон — Приёмка/Скан, первым). Голых тегов без класса
   в разметке страниц быть не должно — новый экран сразу берёт классы отсюда. */

:root {
  /* Ровно одна схема, а не «light dark»: браузер красит по ней скроллбары и прочую нативную хрому,
     и при светлой теме поверх тёмной системы полоса прокрутки оставалась чёрной. */
  color-scheme: light;

  /* Бренд / акцент — индиго (white-label: --brand-surface + --brand-accent) */
  --brand-surface: #221B47;
  --brand-surface-ink: #E4E0F3;
  --brand-accent: #7C3AED;
  --accent: #7C3AED;
  --accent-strong: #6528C4;
  --accent-hover: #58219E;
  --accent-text: #5B21B6;
  --accent-soft: #EFE7FB;
  --on-accent: #FFFFFF;

  /* Нейтрали — прохладный слейт */
  --bg: #E9ECF1;
  --surface: #F8FAFB;
  --surface-muted: #EDF0F4;
  --ink: #1A2230;
  --muted: #5B6472;
  --line: #DDE2EA;
  --line-strong: #C6CDD8;
  --field: #FFFFFF;
  --focus: #7C3AED;

  /* Семантика статусов — отдельно от акцента */
  --ok: #15803D; --ok-bg: #E4F3E9;
  --warn: #9A5B00; --warn-bg: #F8EBD3;
  --danger: #C62828; --danger-bg: #FBE7E7;
  --transit: #3D6E9E; --transit-bg: #E8EEF6;

  /* Типографика */
  --sans: "Nunito", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace;
  --t-h1: 700 28px/1.25 var(--sans);
  --t-h2: 700 22px/1.3 var(--sans);
  --t-h3: 600 18px/1.35 var(--sans);
  --t-body: 400 16px/1.5 var(--sans);
  --t-sm: 400 14px/1.45 var(--sans);
  --t-mono: 500 14px/1.4 var(--mono);

  /* Пространство, радиусы, тени */
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 24px; --space-6: 32px; --space-7: 48px;
  --r-sm: 6px; --r-md: 10px; --r-lg: 16px; --r-pill: 999px;
  --sh-sm: 0 1px 2px rgba(20, 26, 40, .06);
  --sh-md: 0 4px 12px rgba(20, 26, 40, .08);
  --sh-overlay: 0 12px 32px rgba(20, 26, 40, .20);

  /* Плотность — comfortable (дефолт); compact включается классом на body (v2) */
  --ctrl-h: 44px;
  --ctrl-pad: 10px 12px;
}

/* Тёмная тема — обязательна (кладовщики в тусклом подвале).
   Дефолт по prefers-color-scheme; data-theme="dark" форсит независимо от системы (per-instance конфиг). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --brand-surface: #191340; --brand-surface-ink: #DCD8F0;
    --brand-accent: #A78BFA;
    --accent: #A78BFA; --accent-strong: #6D45D6; --accent-hover: #8B6BE8;
    --accent-text: #C0A9FF; --accent-soft: #241A3A; --on-accent: #FFFFFF;
    --bg: #0F141A; --surface: #161D26; --surface-muted: #1D2731;
    --ink: #E6EAF0; --muted: #95A1B1; --line: #26313D; --line-strong: #334150;
    --field: #0E141B; --focus: #A78BFA;
    --ok: #34D399; --ok-bg: #123026; --warn: #FBBF24; --warn-bg: #3A2E12;
    --danger: #F87171; --danger-bg: #3A1E1E; --transit: #7FB0DC; --transit-bg: #16242F;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --brand-surface: #191340; --brand-surface-ink: #DCD8F0;
  --brand-accent: #A78BFA;
  --accent: #A78BFA; --accent-strong: #6D45D6; --accent-hover: #8B6BE8;
  --accent-text: #C0A9FF; --accent-soft: #241A3A; --on-accent: #FFFFFF;
  --bg: #0F141A; --surface: #161D26; --surface-muted: #1D2731;
  --ink: #E6EAF0; --muted: #95A1B1; --line: #26313D; --line-strong: #334150;
  --field: #0E141B; --focus: #A78BFA;
  --ok: #34D399; --ok-bg: #123026; --warn: #FBBF24; --warn-bg: #3A2E12;
  --danger: #F87171; --danger-bg: #3A1E1E; --transit: #7FB0DC; --transit-bg: #16242F;
}

/* Сброс + база */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; } /* авторский display на .btn и др. иначе перебивает атрибут */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Колонка на всю высоту окна: иначе на коротком экране подпись сборки висит сразу под контентом,
     посреди пустоты. dvh — на мобильном адресная строка съезжает и меняет высоту видимой области. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
h1, h2, h3, h4 { text-wrap: balance; margin: 0 0 var(--space-3); }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
code { font-family: var(--mono); font-size: .9em; }
.ic { width: 16px; height: 16px; flex: none; display: inline-block; vertical-align: -2px; }
.ic-sm { width: 14px; height: 14px; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; animation: none !important; } }

/* §4.1 App shell / хедер */
.app-header {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--brand-surface);
  padding: 0 var(--space-4);
  height: 52px;
  position: sticky;
  top: 0;
  z-index: 20;
}
/* Ряд пунктов не скроллится вбок: то, что не помещается, живёт в «Ещё», а на телефоне —
   в нижней панели (.tabbar ниже). Горизонтальной прокрутки в шапке быть не должно. */
.ah-nav { display: flex; align-items: center; gap: 4px; min-width: 0; }
.ah-logo { font: var(--t-h3); font-weight: 800; color: #fff; margin-right: 10px; flex: none; text-decoration: none; }
.ah-user { margin-left: auto; flex: none; color: var(--brand-surface-ink); font-size: 13px; opacity: .85; white-space: nowrap; }
.ah-out { margin-left: 10px; flex: none; }
.ah-out button, .ah-theme {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .25);
  color: var(--brand-surface-ink);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: var(--r-sm);
  cursor: pointer;
}
.ah-out button:hover, .ah-theme:hover { background: rgba(255, 255, 255, .1); }
.ah-theme { margin-left: 10px; flex: none; padding: 6px 8px; display: inline-flex; align-items: center; }
.ah-help { text-decoration: none; font-weight: 700; line-height: 1; cursor: pointer; list-style: none; }
.ah-help::-webkit-details-marker { display: none; }
.ah-helpmenu button.nav-more-item { background: none; border: none; width: 100%; text-align: left; font: inherit; cursor: pointer; }

/* Режим подсказок: рамка на всём, у чего есть описание; карточка — единственный абсолютный элемент */
.help-mode .hint-target { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-sm); }
.help-card {
  position: fixed; z-index: 60; max-width: 22rem; padding: 10px 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-md); box-shadow: var(--sh-overlay);
}
.help-mode-bar {
  position: fixed; left: 50%; translate: -50% 0; bottom: 16px; z-index: 60;
  display: flex; align-items: center; gap: 12px; max-width: calc(100vw - 24px);
  white-space: nowrap; padding: 8px 8px 8px 16px; font-size: 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-pill); box-shadow: var(--sh-overlay);
}
.help-card-label { margin: 0 0 2px; font-weight: 700; }
.help-card-text { margin: 0 0 6px; font-size: 14px; }
.help-card-link { font-size: 13px; color: var(--accent-text); }
.help-toc { list-style: none; padding: 0; }
.help-toc li { margin: 8px 0; }
.help-snippet { margin: 2px 0 0; color: var(--muted); font-size: 14px; }
.help-body img { max-width: 100%; height: auto; border: 1px solid var(--line); border-radius: 8px; }
.help-body table { border-collapse: collapse; margin: 12px 0; max-width: 100%; }
.help-body th, .help-body td { border: 1px solid var(--line); padding: 6px 10px; text-align: left; vertical-align: top; }
.help-body code { background: var(--surface-muted); padding: 1px 5px; border-radius: 4px; }
.app-body { padding: var(--space-5); max-width: 60rem; margin: 0 auto; width: 100%; }
.app-body--wide { max-width: none; }

/* §4.1a Подпись сборки — прижата к низу окна, а не к концу контента */
.build-tag {
  margin-top: auto;
  padding: 10px 16px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}

/* §4.2 Nav-элемент */
.nav-item {
  font-size: 14px;
  font-weight: 600;
  color: var(--brand-surface-ink);
  opacity: .85;
  padding: 7px 12px;
  border-radius: var(--r-pill);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
  transition: background .15s, opacity .15s;
}
.nav-item:hover { opacity: 1; }
.nav-item.is-active { opacity: 1; background: rgba(255, 255, 255, .14); }

/* «Ещё» — нативный <details>, без JS. Маркер треугольника убираем в обоих движках. */
.nav-more { position: relative; }
.nav-more > summary { list-style: none; cursor: pointer; }
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::after {
  content: ""; display: inline-block; margin-left: 2px; opacity: .8;
  border: 4px solid transparent; border-top-color: currentColor; translate: 0 2px;
}
.nav-more[open] > summary { opacity: 1; background: rgba(255, 255, 255, .14); }
.nav-more-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  min-width: 12rem; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-md);
  display: flex; flex-direction: column;
}
.nav-more-item {
  font-size: 14px; font-weight: 600; color: var(--ink);
  padding: 9px 12px; border-radius: var(--r-sm); text-decoration: none; white-space: nowrap;
}
.nav-more-item:hover { background: var(--surface-muted); }
.nav-more-item.is-active { background: var(--surface-muted); color: var(--accent-strong); }
.nav-more-head {
  margin: 2px 0; padding: 0 12px;
  font-size: 11px; font-weight: 800; letter-spacing: .08em;
  color: var(--muted); text-transform: uppercase;
}
.nav-more-head ~ .nav-more-head {
  margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--line);
}
.nav-more-sep { margin: 6px 4px; border-top: 1px solid var(--line); }

/* Раскрывающийся блок в контенте — тот же маркер, что у «Ещё» в навигации, вместо нативного треугольника */
.app-body summary { cursor: pointer; list-style: none; }
.app-body summary::-webkit-details-marker { display: none; }
/* Треугольник рисуем границами: глифа ▾ в Nunito нет, запасной шрифт даёт другой по весу знак */
.app-body details > summary::after {
  content: ""; display: inline-block; margin-left: 6px; vertical-align: middle; opacity: .7;
  border: 4px solid transparent; border-top-color: currentColor; translate: 0 2px;
}
.app-body details[open] > summary::after { rotate: 180deg; translate: 0 -2px; }

/* §4.1b Нижняя панель — только телефон (включается в медиазапросе внизу файла) */
.tabbar { display: none; }

.scr-h1 { font: var(--t-h2); font-weight: 800; margin-bottom: var(--space-4); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Заголовок — флекс-контейнер, и протяжка мыши через зазор между его элементами сбрасывает
   выделение; трек из шапки диктуют и копируют, поэтому он выделяется целиком одним кликом. */
.scr-h1 code { user-select: all; }
.scr-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); }
.scr-head .scr-h1 { margin-bottom: 0; }
.scr-head-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.scr-h2 { font-size: 16px; font-weight: 700; margin: var(--space-5) 0 10px; }
.lbl { display: block; font-size: 12px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }

/* §4.3 Кнопки */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font-family: inherit; font-size: 14px; font-weight: 700;
  height: var(--ctrl-h); padding: 0 16px;
  border-radius: var(--r-sm); border: 1.5px solid transparent;
  cursor: pointer; line-height: 1; text-decoration: none;
  transition: background .15s, border-color .15s;
}
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn[aria-busy="true"] { opacity: .5; cursor: progress; }
.btn--primary { background: var(--accent-strong); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); }
.btn--secondary { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--secondary:hover { background: var(--surface-muted); }
.btn--quiet { background: transparent; color: var(--muted); }
.btn--danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn--danger:hover { background: var(--danger-bg); }
.btn--sm { height: 36px; font-size: 13px; padding: 0 13px; }
.btn--lg { height: 48px; font-size: 15px; padding: 0 22px; }
.btn--block { width: 100%; }

/* §4.4 Поле ввода / скан — общий контракт для input[text|password|date|number|tel], select, textarea */
.field,
select.field,
textarea.field {
  width: 100%; font-family: inherit; font-size: 15px;
  background: var(--field); border: 1.5px solid var(--line-strong);
  border-radius: var(--r-sm); padding: var(--ctrl-pad); color: var(--ink);
}
input.field, select.field { height: var(--ctrl-h); }
select.field { cursor: pointer; }
textarea.field { min-height: calc(var(--ctrl-h) * 2.2); line-height: 1.4; resize: vertical; }
.field::placeholder { color: color-mix(in srgb, var(--muted) 75%, transparent); }
.field:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-color: var(--brand-accent); }
.field:disabled { opacity: .6; cursor: not-allowed; background: var(--surface-muted); }
.field[readonly] { background: var(--surface-muted); color: var(--muted); cursor: default; }
.field--scan { font-family: var(--mono); font-size: 20px; font-weight: 500; padding: 16px; border-width: 2px; box-shadow: var(--sh-sm); }
/* Моноширинный нужен трек-номеру, а не подсказке: в моно она разъезжается на всю строку. */
.field--scan::placeholder { font-family: var(--sans); font-size: 17px; }
.scan-row { display: flex; gap: 10px; align-items: center; margin-top: var(--space-3); flex-wrap: wrap; }

/* Чекбокс / радио — нативный контрол, themed через accent-color (color-scheme в :root красит и остальную нативную хрому) */
.chk { inline-size: 20px; block-size: 20px; flex: none; accent-color: var(--accent-strong); cursor: pointer; }
.chk:disabled { cursor: not-allowed; opacity: .6; }

/* Файл — контур как .field, кастомизируем только кнопку выбора */
input[type="file"].field { padding: 6px 10px; cursor: pointer; }
input[type="file"].field::file-selector-button {
  font-family: inherit; font-size: 13px; font-weight: 700;
  height: 32px; padding: 0 14px; margin-right: 12px;
  border-radius: var(--r-sm); border: none;
  background: var(--accent-strong); color: var(--on-accent); cursor: pointer;
}
input[type="file"].field::file-selector-button:hover { background: var(--accent-hover); }

/* Свой выбор файла: нативную подпись («Choose File») задаёт браузер и локализовать её нечем,
   поэтому input прячем, а диалог открывает <label for> — без JS. */
.file-field { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.file-field__input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.file-field label.btn { cursor: pointer; }
.file-field__input:focus-visible + .btn { outline: 2px solid var(--focus); outline-offset: 2px; }
.file-field__name { font-size: 13px; color: var(--muted); }

/* Раскладка форм */
.form-row { max-width: 26rem; margin-bottom: var(--space-4); }
.form-row:last-child { margin-bottom: 0; }
.form-grid { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: flex-start; margin-bottom: var(--space-4); }
.form-grid .form-row { flex: 1 1 200px; max-width: 20rem; margin-bottom: 0; }
/* Кнопка — прямой child form-grid без .lbl над собой; сдвигаем вниз на высоту метки (18px строка + 6px отступ),
   чтобы верх кнопки совпал с полем, а не с низом колонки (колонки бывают разной высоты — напр. .field-help). */
.form-grid > .btn { margin-top: 24px; }
/* Фильтры списка: на телефоне за раскрытием «Фильтры», на десктопе развёрнуты в ту же строку без переключателя.
   Закрытый <details> прячет содержимое через ::details-content — где псевдоэлемента нет, остаётся переключатель. */
.filters, .filters::details-content { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; flex: 1 1 auto; }
@media (max-width: 719px) {
  .filters, .filters::details-content { flex-basis: 100%; }
  .filters:not([open])::details-content { display: none; }
}
@media (min-width: 720px) {
  @supports selector(details::details-content) {
    .filters > summary { display: none; }
    .filters:not([open])::details-content { content-visibility: visible; }
  }
}
.form-grid .form-row--period { max-width: 26rem; flex-basis: 20rem; }
.form-row--period .field-row { flex-wrap: nowrap; }
.form-row--period .field { flex: 1 1 8rem; min-width: 0; }
.period-lbl { font-size: 13px; color: var(--muted); text-transform: lowercase; }
/* Длинная форма настроек: секции с заголовками, поля колонками. Обычный .form-grid здесь не
   годится — он flex-обёртка для коротких форм, и заголовок секции встаёт в одну строку с полями,
   между ними. Заголовки, подсказки и кнопка занимают всю ширину, поля выстраиваются сеткой. */
.form-sections {
  display: grid; gap: var(--space-3) var(--space-4); align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 20rem));
  margin-bottom: var(--space-4);
}
.form-sections > .scr-h2,
.form-sections > .field-help,
.form-sections > .btn-row { grid-column: 1 / -1; }
/* Заголовок отделяет секцию от предыдущей, но не отрывается от своей: сверху больше, снизу мало. */
.form-sections > .scr-h2 { margin: var(--space-4) 0 0; }
.form-sections > .scr-h2:first-child { margin-top: 0; }
.form-sections .form-row { margin-bottom: 0; }
/* Форма из абзацев, а не из коротких полей (база знаний): рубрика занимает строку целиком.
   В колонках по 20rem текст рубрики режется до ширины, на которой его не вычитать. */
.form-sections--rows { grid-template-columns: minmax(0, 48rem); }
.form-sections--rows .form-row { max-width: none; }
/* Поле растёт под свой текст: рубрику дописывают годами, и фиксированная высота либо
   держит пустоту у коротких, либо прячет длинные в собственную прокрутку. field-sizing
   поддерживают не все браузеры — там остаётся rows и ручное растягивание за уголок. */
.form-sections--rows textarea.field { field-sizing: content; max-height: 70vh; }
/* Подсказка под своим полем, а не отдельной строкой во всю ширину. */
.form-sections .form-row .field-help { grid-column: auto; margin-top: 6px; }
/* Поле цвета — образец, а не строка ввода: во всю колонку оно читается как заливка экрана. */
.form-sections input[type="color"] { width: 5.5rem; height: 40px; padding: 4px; cursor: pointer; }

.field-help { display: block; font-size: 13px; color: var(--muted); margin-top: 4px; }
/* Значение, требующее вмешательства (просроченный карантин, мёртвая очередь) — цвет поверх текста,
   а не баннер: баннер в строке метрики читается как отдельное сообщение. */
.is-alarm { color: var(--danger); }
.field-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* Базис, а не flex:1 — с нулевым базисом поле схлопывалось почти в ноль рядом с широкой
   кнопкой (ссылка кабинета на /desk/clients с телефона). Не влезло — кнопка уходит вниз. */
.field-row .field { flex: 1 1 12rem; }

/* Радио/чекбокс-варианты друг под другом, а не в строку */
.radio-col { display: flex; flex-direction: column; gap: var(--space-2); }
/* Группа радиокнопок занимает строку формы целиком: в общей колонке метки переносятся вразнобой. */
.form-grid > .radio-col { flex-basis: 100%; max-width: none; }
.radio-col label { display: flex; align-items: center; gap: 8px; cursor: pointer; }

/* График недели: день — открытие — закрытие, семь строк одной сеткой */
.hours-week { display: grid; grid-template-columns: 2.5rem 1fr 1fr; gap: 8px; align-items: center; margin-top: var(--space-2); }
.hours-day { font-size: 13px; color: var(--muted); }

/* Ряд кнопок под контентом (напр. над «Отправить») */
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--space-4); }
.btn-row .btn { white-space: nowrap; }

/* §4.5 Фид-таблица */
.feed-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-md); }
.feed { width: 100%; border-collapse: collapse; font-size: 14px; }
.feed thead th {
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); padding: 10px 12px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.feed tbody td { padding: 10px 12px; border-bottom: 1px solid var(--line); }
.feed tbody tr:last-child td { border-bottom: 0; }
.feed tbody tr:hover { background: var(--surface-muted); }
.feed .trk { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.feed .trk a { color: var(--accent-strong); font-weight: 700; text-decoration: none; }
.feed .trk a:hover { text-decoration: underline; }
.feed .cli { font-family: var(--mono); color: var(--accent-strong); font-weight: 700; }
.feed .t { color: var(--muted); font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.feed .none { color: var(--warn); display: inline-flex; align-items: center; gap: 5px; }
.feed .none .ic { width: 13px; height: 13px; }
.feed tr.is-warn td { background: var(--warn-bg); }
.feed .feed-empty { color: var(--muted); text-align: center; padding: var(--space-5) var(--space-3); }
.feed .weight-by-hand { display: block; font-size: 11px; color: var(--muted); }
/* Колонка чисел: деньги и веса сравнивают взглядом, поэтому моноширинно и по правому краю; заголовок — только по правому краю */
.feed td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
.feed th.num { text-align: right; }
/* Сортируемый заголовок: направление — треугольник границами, глифов ▲▼ в Nunito нет */
.feed th .sort.is-asc::after, .feed th .sort.is-desc::after {
  content: ""; display: inline-block; margin-left: 4px; vertical-align: middle; border: 4px solid transparent;
}
.feed th .sort.is-asc::after { border-bottom-color: currentColor; translate: 0 -2px; }
.feed th .sort.is-desc::after { border-top-color: currentColor; translate: 0 2px; }
/* Ячейка действий: несколько <form> в один ряд */
.row-actions { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }
.row-actions > form { display: flex; gap: 8px; align-items: center; margin: 0; }
.row-panel { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin-top: 8px; min-width: 16rem; }

/* §4.5b Панель строки — правка сущности рядом с таблицей, а не поверх неё.
   Нативный <dialog>: Esc, ловушка фокуса и затемнение уже в браузере. */
.drawer {
  position: fixed; inset: 0 0 0 auto; margin: 0;
  width: min(480px, 100%); max-width: 100%; height: 100dvh; max-height: 100dvh;
  overflow: auto; padding: 0;
  background: var(--surface); color: var(--ink);
  border: 0; border-left: 1px solid var(--line); box-shadow: var(--sh-overlay);
}
.drawer::backdrop { background: rgba(20, 26, 40, .45); }
.drawer-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-4); background: var(--surface); border-bottom: 1px solid var(--line);
}
.drawer-title { font: var(--t-h3); }
.drawer-body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
.drawer-body .form-row { max-width: none; }
@media (max-width: 719px) { .drawer { inset: 0; width: 100%; border-left: 0; } }

/* Ячейка «События» диагностики: <details> без своего JS, как везде на станции */
.feed-events > summary { cursor: pointer; list-style: none; font-variant-numeric: tabular-nums; }
.feed-events > summary::-webkit-details-marker { display: none; }
.feed-events-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); font: var(--t-mono); }
.feed-events-error { color: var(--danger); margin: var(--space-2) 0 0; font-size: 13px; }

/* §4.6 Статус-пилюля */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 700; padding: 2px 10px;
  border-radius: var(--r-pill); white-space: nowrap; line-height: 1.7;
}
.pill .ic { width: 13px; height: 13px; }
.pill--ok { background: var(--ok-bg); color: var(--ok); }
.pill--warn { background: var(--warn-bg); color: var(--warn); }
.pill--danger { background: var(--danger-bg); color: var(--danger); }
.pill--transit { background: var(--transit-bg); color: var(--transit); }

/* Итог к сбору на выдаче: главная цифра экрана — крупнее строки таблицы */
.due { font-size: 18px; margin: 10px 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.due #due-val { font-size: 28px; font-weight: 800; font-variant-numeric: tabular-nums; }
.usd { font: var(--t-sm); font-family: inherit; color: var(--muted); }

/* Расчёт под итогом: начислено и остаток — справка, к доплате — сумма, которую берут с клиента */
.settlement { margin: var(--space-2) 0; font-size: 14px; color: var(--muted); }
.settlement #settlement-due { color: var(--ink); font-weight: 800; font-variant-numeric: tabular-nums; }

/* Снятая галочка выводит посылку из выдачи — строка должна это показывать, а не только итог */
.feed tr:has(.chk:not(:checked)) td { opacity: .5; }
.feed tr:has(.chk:not(:checked)) .trk { text-decoration: line-through; }

/* Оплата на выдаче: счёт, сумма и фото подтверждения */
.pay {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3);
  margin: var(--space-4) 0; padding: var(--space-4);
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface);
}
.pay legend {
  padding: 0 6px; font-size: 12px; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--muted);
}
.pay label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.pay .lbl { display: block; margin: 0; cursor: default; }
.pay .lbl .field { display: block; max-width: 12rem; margin-top: var(--space-2); }

/* Запасной вход на выдаче: свёрнутый блок читается как действие, а не как подпись к полю */
.fallback > summary { margin: var(--space-3) 0; color: var(--accent-text); font-weight: 600; cursor: pointer; }

/* Движение по лицевому счёту: пополнение и списание различаются цветом и знаком — в споре
   о деньгах строку ищут глазами, а не читают подряд */
.ledger-in { color: var(--ok); font-variant-numeric: tabular-nums; }
.ledger-out { color: var(--danger); font-variant-numeric: tabular-nums; }

/* Клиент = код + имя: две приметы рядом, чтобы промах в одной букве кода был виден человеку */
.client-tag { display: inline-flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.client-tag .cli { font-family: var(--mono); color: var(--accent-strong); font-weight: 700; }
.client-tag__name { color: var(--ink); }

/* Кадры ярлыка: сетка миниатюр, разбор спора идёт по всем снимкам посылки */
.shots { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-4); }
.shot { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.shot img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  background: var(--surface-muted); border: 1px solid var(--line); border-radius: var(--r-md);
}
.shot figcaption { font: var(--t-mono); color: var(--muted); }

/* §4.7 Inline-подсказка привязки */
.bindbox {
  display: flex; gap: 12px;
  background: var(--warn-bg);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--warn-bg));
  border-radius: var(--r-sm); padding: 14px 16px; margin: var(--space-4) 0; font-size: 14px;
}
.bindbox code { background: color-mix(in srgb, var(--warn) 14%, transparent); padding: 1px 6px; border-radius: 4px; font-weight: 600; color: var(--warn); }
.bindbox .field { max-width: 200px; }

/* §4.8 Строка-статистика */
.stats { font-size: 14px; color: var(--muted); margin: var(--space-3) 0 8px; }
.stats b { color: var(--ink); font-weight: 800; font-variant-numeric: tabular-nums; }

/* §4.9 Баннер-фидбек (замена .warn/.error) */
.alert--warn, .alert--error, .alert--ok {
  display: flex; align-items: flex-start; gap: 8px;
  border-radius: var(--r-sm); padding: 10px 14px; font-size: 14px; margin: var(--space-3) 0;
}
.alert--warn { background: var(--warn-bg); color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--warn-bg)); }
.alert--error { background: var(--danger-bg); color: var(--danger); border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--danger-bg)); }
.alert--ok { background: var(--ok-bg); color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 35%, var(--ok-bg)); }
.alert--warn a, .alert--error a, .alert--ok a { color: inherit; font-weight: 700; text-decoration: underline; }

.shift-tally {
  background: var(--surface-muted); color: var(--muted);
  border-radius: var(--r-sm); padding: 8px 12px; font-size: 14px; margin: var(--space-3) 0;
}
.shift-tally b { color: var(--ink); font-variant-numeric: tabular-nums; }

.scr-foot { margin-top: var(--space-4); }

/* §4.12 Станция: приборная панель — порядок блоков по рабочему циклу, всё в одном экране.
   Оператор смотрит издалека и работает одной рукой: ключевые числа крупно, цели тапа от 44px. */
.st-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: 40px; }
.st-title { display: flex; align-items: baseline; gap: var(--space-2); margin: 0; font: var(--t-h3); }
.st-sub { margin: 2px 0 0; color: var(--muted); font-size: 14px; }
.st-sub b { color: var(--ink); }
.st-menu { position: relative; }
.st-menu > summary { list-style: none; min-width: 44px; text-align: center; }
.st-menu > summary::-webkit-details-marker { display: none; }
/* Оверлей, а не блок в потоке: раскрытое меню иначе распирает шапку и рвёт заголовок партии. */
.st-menu-body { position: absolute; right: 0; z-index: 20; min-width: 15rem;
  display: flex; flex-direction: column; gap: var(--space-2); align-items: stretch;
  padding: var(--space-2); border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--sh-md); }
/* Кнопка в <form> — вложенный элемент, и без contents ряд кнопок идёт разной ширины. */
.st-menu-body form { display: contents; }

.st-weight { display: flex; align-items: center; gap: var(--space-2); min-height: 56px; cursor: pointer;
  font: 700 32px/1.2 var(--sans); font-variant-numeric: tabular-nums; }
.st-weight .scale-state { font: var(--t-sm); }
.st-weight .scale-label { font: var(--t-sm); color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

.st-hint { margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3); font: var(--t-sm);
  border-radius: var(--r-md); background: var(--accent-soft); }

/* Строка устройств: три точки состояния вместо трёх виджетов — наладка живёт в рабочем месте. */
.devices { display: flex; align-items: center; gap: 6px 12px; flex-wrap: wrap;
  padding: 10px 12px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); cursor: pointer; font-size: 15px; }
.devices .dev { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.devices .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--line-strong); flex: none; }
.devices .dot.ok { background: var(--ok); }
.devices .dot.wait { background: var(--warn); }
.devices .dot.off { background: var(--line-strong); }
.devices .v { font-family: var(--mono); font-size: 14px; font-variant-numeric: tabular-nums; }
.devices .go { margin-left: auto; color: var(--accent-text); font-weight: 700; }
.dev-inline { border: 0; background: none; color: var(--accent-text); font-weight: 700; padding: 0; cursor: pointer; font-size: 14px; }
/* Превью камеры — плавающее окно поверх экрана: оператор перетаскивает его туда, где оно не
   закрывает форму, место запоминается на устройстве (cam-float.mjs). */
.cam-slot { position: fixed; z-index: 35; width: 170px; left: 0; top: 0; }
@media (min-width: 900px) { .cam-slot { width: 260px; } }
.cam-slot .cam-dock { margin-top: 0; box-shadow: var(--sh-overlay); cursor: grab; touch-action: none; }
.cam-slot.is-dragging .cam-dock { cursor: grabbing; }
.cam-slot .cam-dock video { aspect-ratio: 4 / 3; }
.cam-slot .cam-dock.is-collapsed { width: max-content; max-width: 260px; }
/* В узком окне две кнопки в ряд не помещаются — ставим столбиком, на десктопе ряд. */
.cam-slot .cam-tools { flex-direction: column; }
.cam-slot .cam-tools .btn { height: 36px; font-size: 14px; }
@media (min-width: 900px) { .cam-slot .cam-tools { flex-direction: row; } }

/* Цикл одной посылки: трек → вес → полка → «Принять», по строке на шаг. */
.scan-form { display: grid; gap: 10px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 14px; margin-top: var(--space-3); }
.scan-form .row { display: grid; grid-template-columns: 72px minmax(0, 1fr); align-items: center; gap: 6px 10px; min-height: 44px; }
.scan-form .val { display: flex; align-items: center; gap: 6px 10px; flex-wrap: wrap; min-width: 0; }
.scan-form .val .field { flex: 1 1 110px; min-width: 110px; max-width: 150px; }
.scan-form .src { color: var(--muted); font-size: 14px; white-space: nowrap; }
.scan-form .big { font-family: var(--mono); font-size: 24px; font-variant-numeric: tabular-nums; }
.scan-form .wait-box { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; color: var(--muted); font-size: 14px; }
.scan-form .keys { display: none; color: var(--muted); font-size: 13px; margin: 0; }
kbd { font: 12px/1 var(--mono); border: 1px solid var(--line-strong); border-bottom-width: 2px;
  border-radius: 4px; padding: 2px 5px; background: var(--surface-muted); color: var(--muted); }
.btn--primary kbd { border-color: rgb(255 255 255 / .45); background: rgb(255 255 255 / .14); color: #fff; }

.st-feed { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.st-card { background: var(--surface); border: 1px solid var(--line); border-left-width: 4px;
  border-radius: var(--r-md); padding: 10px 12px; display: grid;
  grid-template-columns: minmax(0, 1fr) auto; gap: 2px 10px; font-size: 14px; }
.st-card.is-bound { border-left-color: var(--ok); }
.st-card.is-unbound { border-left-color: var(--warn); }
.st-card.is-error { border-left-color: var(--danger); }
.st-card .trk { font-family: var(--mono); font-size: 15px; overflow-wrap: anywhere; }
.st-card .t { color: var(--muted); font-family: var(--mono); font-size: 13px; text-align: right; }
.st-card .cli { font-weight: 700; }
.st-card.is-unbound .cli { color: var(--warn); }
.st-card .w { grid-column: 1 / -1; color: var(--muted); font-family: var(--mono); }
.st-card .ph, .st-card .ph-msg { color: var(--muted); align-self: center; text-align: right; }
.st-card .ph-msg { color: var(--warn); }
.st-card.is-new { animation: st-flash 2s ease-out; }
.st-empty { color: var(--muted); text-align: center; padding: var(--space-5) var(--space-3);
  border: 1px dashed var(--line); border-radius: var(--r-md); margin: 0; }
@keyframes st-flash { from { background: var(--accent-soft); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .st-card.is-new { animation: none; } }

/* Рабочее место — вторая половина того же экрана: уход на другой маршрут рвал бы связь с весами. */
.st-setup { display: flex; flex-direction: column; gap: 14px; }
.st-setup .st-head { justify-content: flex-start; gap: 12px; }
.st-setup .sec { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 14px; display: grid; gap: 10px; margin: 0; }
.st-setup .radio { display: grid; grid-template-columns: 26px 1fr; gap: 4px 10px; padding: 8px 6px;
  border-radius: var(--r-sm); cursor: pointer; align-items: center; }
.st-setup .radio .n { font-weight: 700; }
.st-setup .radio .d { grid-column: 2; color: var(--muted); font-size: 14px; }
.st-setup .check { display: grid; grid-template-columns: 150px 1fr; gap: 8px; }
.st-setup .check .k { color: var(--muted); }
.st-setup .more summary { cursor: pointer; font-weight: 700; min-height: 40px; display: flex; align-items: center; }
.st-setup .tune { display: grid; grid-template-columns: 150px 1fr; gap: 10px 12px; align-items: center; font-size: 14px; }

/* На широком экране цикл и лента стоят рядом: на посту весь разбор помещается без прокрутки.
   На киоске колонка одна — экран там узкий и вертикальный. */
.st-work { display: grid; gap: var(--space-3); }
@media (min-width: 900px) {
  body:not(.station-body) .scan-form .keys { display: block; }
  body:not(.station-body) .st-work { grid-template-columns: 480px 1fr; gap: 24px; align-items: start; }
}

/* Камера — превью в потоке страницы, форма и лента остаются на виду. 16:9 вместо 3:4:
   бюджет высоты не резиновый, а кадр для OCR снимается с полного сенсора, не с превью. */
.cam-dock {
  margin-top: var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-2); border-radius: var(--r-md);
  background: var(--surface-muted); border: 1px solid var(--line);
}
/* Выдача: превью — прицел для кода с экрана телефона, во весь монитор оно не нужно.
   На телефоне колонка уже 26rem, и ограничение не срабатывает. */
#pin-cam .cam-dock { max-width: 26rem; }
.cam-dock video {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: var(--r-sm); background: #000;
}
/* Прицел: рамка ровно по той доле кадра, которая уходит в декодер. Без неё оператор не знает,
   что наклейку за её пределами читать никто не будет, и держит коробку наугад. */
/* overflow: hidden обрезает затемняющую тень прицела рамкой превью: без него box-shadow в 100vmax
   заливает всю страницу, а не только кадр за пределами области, которая уходит в декодер. */
.cam-view { position: relative; overflow: hidden; }
.cam-roi {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  border: 2px solid var(--accent); border-radius: var(--r-sm);
  box-shadow: 0 0 0 100vmax rgb(0 0 0 / 0.28);
  pointer-events: none;
}
.cam-dock.is-collapsed .cam-roi { display: none; }
.cam-hint { margin: 0; font: var(--t-sm); color: var(--muted); text-align: center; }
.cam-cancel { width: 100%; }
/* Свёрнутое превью: видео остаётся в потоке 1×1 и продолжает декодироваться — детект
   штрихкода читает кадры именно из него, а display:none его бы остановил. */
.cam-dock.is-collapsed video {
  position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
}
.cam-tools { display: flex; gap: var(--space-2); }
.cam-tools .btn { flex: 1; }
.cam-dock.is-collapsed { flex-direction: row; align-items: center; gap: var(--space-2); }
.cam-dock.is-collapsed .cam-hint { flex: 1; text-align: left; }
.cam-dock.is-collapsed .cam-cancel { width: auto; }

/* Съёмка подтверждения перевода — во весь экран: оператор сверяет сумму и статус на экране
   телефона клиента, а инлайн-превью 26rem для этого мелкое. object-fit: contain, а не cover:
   кадр важно видеть целиком, обрезка съела бы верх или низ скриншота перевода. */
#pay-photo-cam .cam-dock {
  position: fixed; inset: 0; z-index: 50;
  margin: 0; border: 0; border-radius: 0;
  padding: var(--space-3);
  background: #000;
  justify-content: center;
}
#pay-photo-cam .cam-view { flex: 1; min-height: 0; display: flex; }
#pay-photo-cam .cam-dock video {
  aspect-ratio: auto;
  width: 100%; height: 100%; max-height: 100%;
  object-fit: contain; border-radius: 0;
}
#pay-photo-cam .cam-hint { color: #fff; }
#pay-photo-cam .cam-cancel { width: min(100%, 24rem); align-self: center; }

/* §4.10a Приём скриншота подтверждения: перетаскивание, вставка, снимок камерой */
.paydrop {
  margin-top: var(--space-3); padding: var(--space-3);
  border: 1px dashed var(--line-strong); border-radius: var(--r-md);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.paydrop.is-over { border-color: var(--accent); background: var(--accent-soft); }
.paydrop__hint { margin: 0; font: var(--t-sm); color: var(--muted); }
.paydrop__got { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.paydrop__got img { width: 6rem; border-radius: var(--r-sm); border: 1px solid var(--line); }
.paydrop__name { font: var(--t-sm); }
.paydrop__warn { margin: 0; font: var(--t-sm); color: var(--warn); }
@media (pointer: coarse) { .paydrop__hint { display: none; } }

/* §4.11 Empty state */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: var(--space-7) var(--space-4); color: var(--muted); text-align: center;
}
.empty .ic { width: 28px; height: 28px; }
.empty p { margin: 0; }
.empty .lbl { margin: 0; }

/* Виджет веса станции (W2 Task 3) */
.scale-panel { margin: .5rem 0; }
/* Шеврон рисует общее правило .app-body details > summary::after — своего здесь нет. */
.scale-panel > summary { gap: .4rem; }
.scale-menu { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: var(--space-2); }
/* Подсказка — не кнопка: в общем ряду она читалась бы как третий вариант подключения. */
.scale-menu .st-hint { flex-basis: 100%; margin: 0; }
/* Обрыв связи с прибором — отказ, а не «обрати внимание»: работать этим устройством нельзя,
   пока связь не вернётся. Внимание (--warn) остаётся за состояниями, где работа продолжается. */
.scale-error { flex-basis: 100%; margin: 0; color: var(--danger); }
.scale-value { font-weight: 600; font-variant-numeric: tabular-nums; }
.scale-state.ok { color: var(--ok); }
.scale-state.wait { color: var(--muted); }
.scale-state.connecting { color: var(--accent); }
.scale-state.error { color: var(--danger); }
/* Точка усиливает статус, но не заменяет его: цвет не может быть единственным носителем смысла. */
.scale-state::before {
  content: ''; display: inline-block; width: 6px; height: 6px; border-radius: var(--r-pill);
  background: currentColor; margin-right: 4px; vertical-align: 1px;
}
.scale-state.connecting::before { animation: state-pulse 1.2s ease-in-out infinite; }
/* Заряд адаптера: корпус приглушён до линии интерфейса, цвет несёт только уровень.
   Красное — за нижней ступенью: адаптер вот-вот перестанет работать. */
.dev-battery { display: inline-flex; align-items: center; color: var(--muted); }
.dev-battery svg { width: 26px; height: 13px; display: block; }
.dev-battery__shell { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }
.dev-battery__tip { stroke: var(--line-strong); stroke-width: 2; stroke-linecap: round; }
.dev-battery__fill { fill: currentColor; }
.dev-battery--ok { color: var(--ok); }
.dev-battery--warn { color: var(--warn); }
.dev-battery--danger { color: var(--danger); }
@keyframes state-pulse { 50% { opacity: .3; } }
.st-weight:has(.scale-state.ok) .scale-value { color: var(--ok); }
/* Без показаний строка веса — служебная: полновесные 32px там читаются как чёрная плашка. */
.st-weight:not(:has(.scale-state.ok)) .scale-value { font-size: 20px; color: var(--muted); }

/* §4.12 Форма логина — центрированная карточка */
.auth-shell { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: var(--space-5); }
.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--sh-md); padding: var(--space-6); width: 100%;
}
/* Логин — узкая карточка по центру; в контенте карточка занимает свою колонку сетки */
.auth-shell .card { max-width: 22rem; }
.card .scr-h1 { margin-bottom: var(--space-5); }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr)); gap: var(--space-4); align-items: start; }

a.card { display: block; color: inherit; text-decoration: none; }
a.card:hover { box-shadow: var(--sh-md); border-color: var(--accent); }
.kpi { margin: var(--space-2) 0 0; font: var(--t-h1); font-variant-numeric: tabular-nums; color: var(--ink); }
.kpi-sub { margin: var(--space-1) 0 0; font: var(--t-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

/* Ход фоновой задачи: серверное состояние, которое дальше двигает опрос */
.task-progress {
  display: flex; flex-direction: column; gap: var(--space-2);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
}
.task-progress__head { display: flex; align-items: center; gap: var(--space-2); margin: 0; flex-wrap: wrap; }
.task-progress__name { overflow-wrap: anywhere; }
.task-progress progress { width: 100%; height: 8px; accent-color: var(--accent-strong); }
.task-progress__nums { margin: 0; font: var(--t-mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.task-progress__warn { color: var(--warn); }

/* Пагинация (_Pager.cshtml) */
.pager { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-3); flex-wrap: wrap; }
.pager__range { color: var(--muted); font-size: 13px; margin-right: auto; }
.pager__pages { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pager__gap { color: var(--muted); padding: 0 2px; }
.pager__size { display: flex; align-items: center; gap: 6px; }
.pager__size .lbl { margin: 0; }
.is-disabled { pointer-events: none; opacity: .4; }

/* Таб-бар раздела (Clients/_Tabs.cshtml) */
.tabs { display: flex; gap: 4px; margin-bottom: var(--space-4); border-bottom: 1px solid var(--line); }
.tab-item {
  font-size: 14px; font-weight: 600; color: var(--muted); opacity: .8;
  padding: 8px 10px; text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab-item:hover { opacity: 1; color: var(--ink); }
.tab-item.is-active { opacity: 1; color: var(--ink); border-bottom-color: var(--accent-strong); }

/* Свёрнутая форма создания клиента */
summary.btn { cursor: pointer; list-style: none; }
summary.btn::-webkit-details-marker { display: none; }
details > .form-grid { margin-top: var(--space-3); }
.create-toggle summary .lbl-open { display: none; }
.create-toggle[open] summary .lbl-closed { display: none; }
.create-toggle[open] summary .lbl-open { display: inline; }
/* Триггер остаётся рядом с заголовком (flex-item в .scr-head); раскрытое содержимое
   переносится на всю ширину строки — flex-basis:100% форсирует перенос на новую строку. */
.scr-head .create-toggle[open] {
  flex-basis: 100%; border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--space-4);
}

/* §9 Телефон. Единственная точка перелома: ниже неё ряд из 5 пунктов перестаёт помещаться,
   и навигация уезжает вниз, под большой палец. Отдельной мобильной версии страниц нет —
   одна разметка, разная раскладка навигации. */
@media (max-width: 719px) {
  .app-header .ah-nav { display: none; }
  .app-body { padding: var(--space-4); }
  .scr-head-actions { flex: 1 1 100%; flex-wrap: wrap; }
  /* Высота панели + системная полоса жеста: без этого последняя строка списка уходит под панель. */
  body { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
  /* Плашка режима подсказок встаёт над нижней панелью, а не под ней */
  .help-mode-bar { bottom: calc(72px + env(safe-area-inset-bottom)); }

  .tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    background: var(--brand-surface);
    padding-bottom: env(safe-area-inset-bottom); /* iPhone: иначе панель под полосой жеста */
    border-top: 1px solid rgba(255, 255, 255, .12);
  }
  .tabbar-item {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    padding: 8px 4px; text-decoration: none; cursor: pointer;
    font-size: 11px; font-weight: 700; color: var(--brand-surface-ink); opacity: .7;
    list-style: none;
  }
  .tabbar-item::-webkit-details-marker { display: none; }
  .tabbar-item .ic { width: 22px; height: 22px; }
  .tabbar-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
  .tabbar-item.is-active { opacity: 1; }
  .tabbar-item.is-active .ic { color: var(--brand-accent, currentColor); }

  .tabbar-more { flex: 1; min-width: 0; display: flex; }
  .tabbar-more > summary { flex: 1; }
  .tabbar-more[open] > summary { opacity: 1; }
  /* Раскрывается вверх от нижнего края — вниз некуда. */
  .tabbar-more-panel {
    position: fixed; left: var(--space-3); right: var(--space-3);
    bottom: calc(64px + env(safe-area-inset-bottom)); z-index: 41;
    padding: 6px; max-height: 60vh; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-md); box-shadow: var(--sh-md);
    display: flex; flex-direction: column;
  }
  .nav-more-item { padding: 12px; } /* палец, не курсор */
}

/* Глазок в поле пароля (вход). Кнопка лежит ВНУТРИ поля, поэтому у input есть отступ справа —
   иначе набранное уезжает под иконку. Область нажатия 44px: пароль набирают с телефона. */
.pw-wrap { position: relative; display: block; }
.pw-wrap .field { width: 100%; padding-right: 44px; }
.pw-eye {
  position: absolute; inset-block: 0; inset-inline-end: 0;
  display: grid; place-items: center;
  width: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
  color: var(--muted);
}
.pw-eye:hover { color: var(--ink); }
.pw-eye svg { width: 20px; height: 20px; }
.pw-eye:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; border-radius: 6px; }

/* Печатаем ровно один лист — тот, которому JS повесил .is-printing перед window.print().
   Чек выдачи виден на экране; лист «на подпись» на экране скрыт (display:none) и разворачивается
   только для печати, поэтому display возвращаем здесь, а не одной видимостью. */
@media print {
  /* Скрываем именно display, а не visibility: невидимый элемент остаётся в потоке и держит
     свою высоту, из-за чего за листом печатались пустые страницы всего экрана. Печатаемый лист
     на это время переезжает прямым ребёнком body (printSheet), поэтому правило его не задевает. */
  body.printing > *:not(.print-sheet.is-printing) { display: none !important; }
  /* На экране body тянется на всю высоту окна, чтобы подпись сборки стояла внизу; на бумаге
     эта высота становится пустым листом после короткого документа. */
  body.printing { min-height: 0; display: block; }
  .print-sheet.is-printing {
    display: block !important;
    width: 100%;
    color: #000; background: #fff;
  }
  .no-print { display: none !important; }

  /* Экономия бумаги: узкие поля страницы и плотная вёрстка листа — мелкий шрифт, тесные строки,
     тонкие линейки вместо рамок и фона таблицы. Действует на оба листа (.print-sheet). */
  @page { margin: 10mm; }
  .print-sheet.is-printing { font-size: 11px; line-height: 1.25; }
  .print-sheet.is-printing h2 { font-size: 13px; margin: 0 0 4px; }
  .print-sheet.is-printing p { margin: 2px 0; }
  .print-sheet.is-printing .feed-wrap { overflow: visible; margin: 4px 0; }
  .print-sheet.is-printing table.feed { border-collapse: collapse; width: 100%; background: none; }
  .print-sheet.is-printing table.feed th,
  .print-sheet.is-printing table.feed td {
    padding: 1px 6px; border: 0; border-bottom: 1px solid #bbb;
    font-size: 11px; line-height: 1.25; background: none;
  }
  .print-sheet.is-printing table.feed thead th { border-bottom: 1px solid #000; text-align: left; }
  .print-sheet.is-printing .receipt-signature { display: block; margin-top: 8px; }

  /* Шапка листа на сборку: слева то, что читают глазами, справа штрихкод в углу.
     Сборщик ищет по трекам, кассир — по номеру, поэтому номер крупнее всего на листе. */
  .print-sheet.is-printing .sheet-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8mm; }
  .print-sheet.is-printing .sheet-kind { font-size: 9px; text-transform: uppercase; letter-spacing: .06em; color: #555; }
  .print-sheet.is-printing .draft-number { font-size: 20px; font-weight: 700; margin: 0 0 2px; }
  .print-sheet.is-printing .sheet-date { color: #555; }
  /* Ширина под X-модуль 0.33 мм: 110 модулей `QD`+3 цифр с тихими зонами ≈ 36 мм, с запасом 45 мм.
     Высота 13 мм — нижняя граница, при которой пистолет берёт код под углом с первого раза.
     Сглаживание при масштабировании красит края штрихов серым и роняет контраст для сканера. */
  .print-sheet.is-printing .draft-barcode { width: 45mm; height: 13mm; margin: 0; image-rendering: crisp-edges; }
  /* Строку посылки нельзя резать между страницами: сборщик потеряет трек. */
  .print-sheet.is-printing table.feed tr { break-inside: avoid; }
}

#preissue-sheet { display: none; } /* только для печати на подпись; пре-выдачный список на экране не дублируем */
.receipt-signature { display: none; } /* пустая строка подписи нужна только на бумаге — экран подписать нечем */

/* Сворачиваемый список (нативный details): заголовок-переключатель как подзаголовок экрана. */
.fold { margin-bottom: var(--space-4); }
.fold__head { cursor: pointer; font: var(--t-h3); margin: var(--space-3) 0; }
.fold__head::marker { color: var(--muted); }

/* Хаб настроек: ссылка и пояснение под ней — список читается взглядом сверху вниз, без таблицы. */
.hub-list { list-style: none; margin: 0 0 var(--space-4); padding: 0; }
.hub-list li { padding: 10px 0; border-bottom: 1px solid var(--line); }
.hub-link { font-weight: 700; color: var(--accent-strong); text-decoration: none; }
.hub-link:hover { text-decoration: underline; }
.hub-hint { display: block; margin-top: 2px; font-size: 13px; color: var(--muted); }

/* §4.13 Панель листов на сборку — рядом с потоком выдачи, на телефоне уезжает вниз */
.issue-live { display: grid; grid-template-columns: minmax(0, 1fr) 18rem; gap: var(--space-5); align-items: start; }
.draft-queue { display: flex; flex-direction: column; gap: var(--space-2); position: sticky; top: var(--space-4); }
.draft-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: var(--space-2);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); }
.draft-row--other { opacity: .6; }
.draft-row form { display: contents; }
.draft-row__age { color: var(--muted); font-size: 13px; }
/* Открытых листов нет — колонка схлопывается, и экран выглядит как до параллельных выдач. */
.draft-queue--empty { display: none; }
.issue-live:has(.draft-queue--empty) { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 719px) { .issue-live { grid-template-columns: minmax(0, 1fr); } .draft-queue { position: static; } }

.draft-number { font: var(--t-h3); margin: 0 0 var(--space-2); }
.draft-barcode { display: block; margin: 0 0 var(--space-2); }

/* Переписка обращения как чат — стороны по краям, как в мессенджере (панель и центр, разметка общая) */
.chat { display: flex; flex-direction: column; gap: 10px; margin: 16px 0; }
.chat-msg { max-width: min(72%, 40rem); padding: 10px 14px; border-radius: 14px 14px 14px 4px; background: var(--surface-muted, rgba(127,127,127,.12)); align-self: flex-start; }
.chat-msg--mine { align-self: flex-end; border-radius: 14px 14px 4px 14px; background: var(--accent-soft, rgba(124,58,237,.18)); }
.chat-meta { font-size: 12px; opacity: .7; margin-bottom: 4px; }
.chat-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-files { margin-top: 6px; font-size: 13px; }
.chat-files a { margin-right: 10px; }
.chat-msg p { margin: 0; }
@media (max-width: 720px) { .chat-msg { max-width: 88%; } }

/* Экран диалогов — рабочее окно на весь просмотр (страница подключает ViewData["Wide"]),
   не отчёт с двумя блоками: сама страница занимает высоту вьюпорта за вычетом шапки панели
   и отступов .app-body, прокручиваются только список и лента внутри, не документ целиком. */
.dialogs-page { display: flex; flex-direction: column; min-height: 0;
  height: calc(100dvh - 52px - var(--space-5) * 2); }
.dialogs-head { flex: none; }
.dialogs-head__row { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; }
.dialogs-head .scr-h1 { margin: 0; }
.dialogs-head form.form-grid { margin: 0; }
.dialogs-layout { flex: 1 1 auto; min-height: 0; display: grid;
  grid-template-columns: 20rem minmax(0, 1fr); gap: var(--space-5); margin-top: var(--space-3); }
.dialogs-list { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-1); }
.dialogs-list__item { display: block; padding: var(--space-2); border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); color: inherit; text-decoration: none; }
.dialogs-list__item:hover { background: var(--surface-muted); }
.dialogs-list__item.is-active { border-color: var(--accent-strong); background: var(--accent-soft); }
.dialogs-list__top { display: flex; justify-content: space-between; gap: var(--space-2); font-size: 12px; color: var(--muted); }
.dialogs-list__peer { font-weight: 700; margin-top: 2px; }
.dialogs-list__preview { color: var(--muted); font-size: 13px; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dialogs-list__item .pill { margin-top: 4px; }
.dialogs-chat { min-height: 0; display: flex; flex-direction: column; }
.dialogs-chat__head { flex: none; }
.dialogs-chat__back { display: none; }
/* Своя прокрутка ленты: длинная переписка открывается на последнем сообщении (якорь
   #last-message из списка), а не пролистывается руками от первого; форма ответа под ней
   закреплена — растёт только середина колонки. */
.dialogs-chat-feed { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
/* Мало сообщений — лента не растягивает их от верха: переписка в мессенджере растёт снизу вверх. */
.dialogs-chat-feed .chat { margin-top: auto; }
.dialogs-chat-feed .chat-msg { max-width: 66%; }
.dialogs-reply { flex: none; }
/* Общий потолок ширины поля форм здесь мешает — ответ должен тянуться на всю колонку. */
.dialogs-reply .form-row { max-width: none; }
/* Шапка панели не резиновая: между мобильной раскладкой (nav скрыт целиком) и широким
   экраном на средних ширинах имя пользователя и более узкие пункты меню перестают наезжать
   друг на друга — само меню высоту не меняет, .dialogs-page считает её как ровно 52px. */
@media (min-width: 720px) and (max-width: 1279px) {
  .ah-user { display: none; }
  .nav-item { padding: 7px 8px; font-size: 13px; }
}
@media (max-width: 719px) {
  /* svh, не dvh: если браузер после первой отрисовки скрывает адресную строку и колонка
     подрастает, лента остаётся на месте — иначе якорь #last-message на телефоне промахивается
     мимо реального низа ленты, посчитанного ещё с открытой адресной строкой. */
  .dialogs-page { height: calc(100svh - 52px - var(--space-4) * 2 - 64px - env(safe-area-inset-bottom)); }
  .dialogs-layout { grid-template-columns: minmax(0, 1fr); margin-top: var(--space-2); }
  .dialogs-layout:not(.dialogs-layout--chat-open) .dialogs-chat { display: none; }
  .dialogs-layout--chat-open .dialogs-list { display: none; }
  .dialogs-chat__back { display: block; }
  /* Заголовок и фильтр относятся к списку — на открытой переписке им нет места. */
  .app-body:has(.dialogs-layout--chat-open) .dialogs-head { display: none; }
  /* Лента важнее пустого поля ответа и трёхстрочной записки бота — сжимаем обе. */
  .dialogs-reply { padding: var(--space-3); }
  .dialogs-reply textarea.field { min-height: calc(var(--ctrl-h) * 1.4); }
  .dialogs-chat__head .alert--warn { padding: 6px 10px; font-size: 13px; }
}

/* §4.14 Мини-карта заявки — Leaflet считает размеры по контейнеру, высоту он сам себе не задаёт */
/* Карта занимает строку целиком: в .form-grid она стоит рядом с полями, и на телефоне
   осталась бы полоской в 200px. Потолок ширины — как у полей формы, чтобы не растягивало. */
.map-block { flex: 1 1 100%; max-width: 30rem; }
.map-pin { height: 15rem; margin-bottom: var(--space-3); border: 1px solid var(--line);
  border-radius: var(--r-md); overflow: hidden; }
.chat-note { font-size: 12px; color: var(--muted); margin-top: 4px; }
