/* =========================================================
   Шрифт — Inter 4, вариативный, из статики приложения.

   Раньше Inter грузился с Google Fonts: каждое открытие страницы уходило
   запросом к серверам Google (с IP пользователя), а без доступа к ним
   планшет оставался на системном шрифте. Теперь файл лежит в
   static/unloading/fonts/ рядом с лицензией (SIL OFL 1.1 — разрешает
   раздавать шрифт вместе с приложением, если лицензия лежит рядом).

   Один файл покрывает все начертания (ось wght) и оптические размеры (ось
   opsz): браузер сам подбирает «текстовый» рисунок для мелкого кегля и
   «дисплейный» для заголовков — то, ради чего в стеке раньше стояла пара
   SF Pro Text / SF Pro Display. SF Pro из стека убран: шрифт Apple по
   лицензии не для веба, а через -apple-system / system-ui системный шрифт
   на устройствах Apple и так доступен законно — он нужен только как
   запасной вариант, если файл не загрузился.

   Имя семейства — InterVariable, а не Inter: так не перепутается с
   локально установленным статическим Inter 3 на чьём-то компьютере.

   Версия файла — 4.001 (Inter 4.1), метка ?v= в адресе. Имя файла
   постоянное, а nginx отдаёт статику с долгим Expires: при обновлении
   шрифта меняйте метку, иначе планшеты останутся на старой копии.
   ========================================================= */

@font-face {
  font-family: "InterVariable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/InterVariable.woff2?v=4.001") format("woff2");
}

/* =========================================================
   Design tokens — adapted from Apple-design-analysis DESIGN.md
   Single accent color, quiet chrome, Inter.
   ========================================================= */

:root {
  /* Colors */
  --color-primary: #0066cc;
  --color-primary-focus: #0071e3;
  --color-primary-on-dark: #2997ff;

  --color-ink: #1d1d1f;
  --color-body: #1d1d1f;
  --color-body-on-dark: #ffffff;
  --color-body-muted: #cccccc;
  --color-ink-muted-80: #333333;
  --color-ink-muted-48: #7a7a7a;

  --color-divider-soft: rgba(0, 0, 0, 0.04);
  --color-hairline: #e0e0e0;

  --color-canvas: #ffffff;
  --color-canvas-parchment: #f5f5f7;
  --color-surface-pearl: #fafafc;
  --color-surface-tile-1: #272729;
  --color-surface-black: #000000;

  --on-primary: #ffffff;
  --on-dark: #ffffff;

  /* Status accents — used only for small status chips, never as a second UI accent.
     Значения подобраны так, чтобы при подмешивании в .chip (16% на фон, ниже)
     текст и точка держали не меньше 4.5:1, а рамка — ровно тот же цвет без
     разбавления (см. .chip ниже) — тоже не меньше 4.5:1. Проверено рендером
     в браузере. Менять цвет статуса — значит пересчитывать контраст заново,
     а не подбирать на глаз. */
  --status-not: #66666a;
  --status-arr: #4340b8;
  --status-unl: #0062cc;
  --status-und: #9b5500;
  --status-dep: #55555a;
  --status-awt: #8a32b8;
  /* «Принимается»: до этой правки совпадал с --status-unl («Разгружается»),
     и два разных этапа с разными ответственными были неразличимы на глаз. */
  --status-rec: #096e8f;
  --status-rcl: #1a7636;
  --status-flag: #c1281e;
  /* Отдельный, ненасыщенный цвет для сплошной заливки с белым текстом
     (.tab-badge) — там нужен контраст в другую сторону (заливка тёмная,
     текст светлый), и тон, подобранный для .chip выше, для такой заливки
     слишком светлый. Не используется в самих чипах. */
  --status-flag-solid: #d12b21;

  /* Бейдж роли «кладовщик» — сплошная заливка с белым текстом, тот же приём,
     что у --status-flag-solid, но отдельный тон: у оператора синий бейдж
     (--color-primary), и два бейджа не должны сливаться на глаз. Контраст к
     белому тексту в обеих темах посчитан явно (см. .role-badge--keeper
     ниже), а не подобран на глаз — 5.47:1 здесь. */
  --color-keeper-badge: #0f766e;

  /* Typography */
  /* Два токена оставлены, хотя семейство теперь одно: на них ссылается весь
     файл, а различие «текст / заголовок» делает ось opsz самого шрифта. */
  --font-display: "InterVariable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-text: "InterVariable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* Radii */
  --radius-xs: 5px;
  --radius-sm: 8px;
  --radius-md: 11px;
  --radius-lg: 18px;
  --radius-pill: 9999px;

  /* Spacing */
  --space-xxs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 17px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-xxl: 48px;
  --space-section: 80px;

  /* Elevation */
  --shadow-product: rgba(0, 0, 0, 0.22) 3px 5px 30px 0;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-text);
  font-size: 17px;
  line-height: 1.47;
  letter-spacing: -0.374px;
  color: var(--color-body);
  background: var(--color-canvas-parchment);
  -webkit-font-smoothing: antialiased;
  /* ss04 — «Disambiguation (no slashed zero)» из таблицы GSUB файла 4.001:
     у l появляется хвостик, у I засечки, чтобы в артикулах не путались I,
     l и 1. Кириллицу набор не трогает (кроме украинских І/Ї), а
     перечёркнутый ноль намеренно не включён — в датах и количествах он
     только шумит. Системный запасной шрифт этой возможности не знает и
     просто игнорирует её.

     Табличные цифры (tnum) глобально НЕ включены: у Inter они делают
     моноширинными и дефис с точкой, и коды вида SH-2026-0042 распадаются
     на «SH - 2026 - 0042» (проверено рендером). Они включены точечно —
     там, где стоит одно число и важно, чтобы оно не прыгало при смене
     значения (количество, счётчики). */
  font-feature-settings: "ss04";
}

/* Поля ввода и кнопки эту настройку сами не наследуют — браузерный стиль
   форм её сбрасывает (проверено в Chromium: у input было "normal"). А
   артикулы и SSCC набирают и сверяют как раз в полях. */
input,
select,
textarea,
button {
  font-feature-settings: inherit;
}

/* Одиночные числа, которые меняются на глазах: цифры одной ширины, чтобы
   значение не «прыгало» и соседние элементы не сдвигались. */
.discrepancy-row__qty,
.tab-badge,
.arrival-photo__count,
.photo-stack__badge {
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0;
}

a {
  color: var(--color-primary);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

img {
  max-width: 100%;
  display: block;
}

/* =========================================================
   Typography scale
   ========================================================= */

.type-tagline {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 600;
  line-height: 1.19;
  letter-spacing: 0.231px;
}

.type-caption {
  font-family: var(--font-text);
  font-size: 14px;
  line-height: 1.43;
  letter-spacing: -0.224px;
}

.type-caption-strong {
  font-family: var(--font-text);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.224px;
}

.type-fine-print {
  font-family: var(--font-text);
  font-size: 12px;
  color: var(--color-ink-muted-48);
  letter-spacing: -0.12px;
}

/* =========================================================
   Layout shell
   ========================================================= */

.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.container {
  max-width: 980px;
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

.container-wide {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

/* .container вложен внутрь .container-wide только на странице поставки —
   как общий родитель для sticky-переключателя вкладок и самих карточек (см.
   комментарий в shipment_detail.html). У вложенного не должно быть СВОЕГО
   бокового отступа: снаружи его уже даёт .container-wide, а раньше оба
   складывались — на телефоне, где ограничение max-width ни у одного из них
   не включается (экран уже 1200px и даже 980px), это удваивало отступ
   (34px вместо 17px на сторону при боковом --space-md) и заметно сужало
   карточки. На десктопе разница не бросалась в глаза — там узкую колонку
   980px и так видно в центре широкой полосы — поэтому баг долго не замечали. */
.container-wide > .container {
  padding-left: 0;
  padding-right: 0;
}

main {
  flex: 1;
}

/* =========================================================
   Global nav
   ========================================================= */

.global-nav {
  background: var(--color-surface-black);
  /* min-height, а не height: `.global-nav__actions` ниже разрешено
     переноситься (flex-wrap: wrap на узких экранах — своя мера против
     обрезанного бейджа роли), а при жёсткой `height` и `align-items: center`
     вторая строка не увеличивает бокс, а центрируется в нём же — половина
     переноса уезжает выше, половина ниже фиксированной высоты, накрывая
     подшапку под шапкой. Проверено рендером в браузере на 320px с длинным
     именем: раньше там наплывало «Выйти» на заголовок поставки. */
  min-height: 44px;
  display: flex;
  align-items: center;
  position: sticky;
  top: 0;
  z-index: 30;
}

/* min-width: 0 у контейнеров шапки — чтобы на узком экране сжималось имя
   пользователя (см. .global-nav__name), а не уезжала строка целиком. */
.global-nav .container-wide {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  width: 100%;
  min-width: 0;
}

.global-nav__brand {
  min-width: 0;
  color: var(--on-dark);
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.12px;
}

.global-nav__brand a {
  color: var(--on-dark);
}

/* Лого-чип «✽ | Dispatxh». Рамка — фиксированный полупрозрачный белый цвет
   в обеих темах, а не currentColor и не смена по теме: шапка сайта (.global-
   nav) всегда чёрная (--color-surface-black), независимо от темы страницы, —
   рамка чёрного цвета в светлой теме (как было раньше) на ней попросту не
   видна. Раньше здесь была ветка на [data-theme="dark"], логика которой
   («чёрная в светлой, белая в тёмной») исходила из того, что фон чипа меняется
   вместе с темой страницы — на деле у чипа один и тот же (всегда тёмный) фон
   в обеих темах, поэтому и рамка должна быть одна на обе темы. */
.brand-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  border-radius: var(--radius-pill);
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  line-height: 1;
  white-space: nowrap;
}

.brand-chip__icon {
  display: inline-flex;
}

.brand-chip__divider {
  opacity: 0.45;
}

.global-nav__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
  min-width: 0;
}

/* Обёртка нужна как якорь для попапа: `.user-menu__dropdown` позиционируется
   абсолютно от неё, а не от всей шапки, поэтому остаётся у ФИО даже когда
   `.global-nav__actions` переносит блоки на узком экране. Без своего
   min-width: 0 она как flex-элемент не сжималась бы ниже содержимого, и
   обрезка имени не работала бы именно на узких экранах. */
.user-menu {
  position: relative;
  min-width: 0;
}

.global-nav__user {
  /* Кнопка, а не span: клик и Enter/Space должны открывать «Выйти» без
     дополнительной разметки под доступность. Сброс браузерных стилей кнопки,
     чтобы выглядела как прежний текст с бейджем. */
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: var(--color-body-muted);
  font-size: 12px;
  letter-spacing: -0.12px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-width: 0;
}

/* Раньше здесь был текстовый треугольник (::before content: "▾") —
   на телефоне и при беглом взгляде он терялся совсем. Теперь это настоящая
   SVG-иконка внутри кнопки (см. base.html, icon_chevron.html), а класс ниже
   только поворачивает её при открытом попапе — так же, как шеврон блока
   «Слои и паллеты» (.units-block__chevron ниже). */
.global-nav__user-chevron {
  display: inline-flex;
  opacity: 0.7;
  transition: transform 0.15s ease;
}

.user-menu.is-open .global-nav__user-chevron {
  transform: rotate(90deg);
}

.user-menu__dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--space-xs);
  min-width: 190px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--color-surface-black);
  border-radius: var(--radius-sm);
  padding: var(--space-xs);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 40;
}

.user-menu__dropdown[hidden] {
  display: none;
}

/* Меню «Скачать ▾» в строке статусов (shipment_detail.html). <details>
   работает без JS; попап — тот же тёмный, что у меню пользователя, и так же
   лежит поверх страницы, ничего не сдвигая. */
.download-menu {
  position: relative;
  /* Справа и после переноса строки на узком экране: попап открывается
     влево от правого края кнопки и иначе уезжал бы за левый край экрана. */
  margin-left: auto;
}

.download-menu__trigger {
  list-style: none;
  cursor: pointer;
  gap: 6px;
  /* touch-action: без задержки двойного тапа на телефоне. */
  touch-action: manipulation;
}

.download-menu__trigger::-webkit-details-marker {
  display: none;
}

.download-menu__chevron {
  display: inline-flex;
  transform: rotate(90deg);
  transition: transform 0.15s ease;
}

.download-menu__chevron svg {
  width: 14px;
  height: 14px;
}

.download-menu[open] .download-menu__chevron {
  transform: rotate(-90deg);
}

.download-menu__list {
  position: absolute;
  top: 100%;
  right: 0;
  margin: var(--space-xs) 0 0;
  padding: var(--space-xs);
  list-style: none;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--color-surface-black);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 40;
}

.download-menu__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 8px 15px;
  border-radius: var(--radius-sm);
  color: #ffffff;
  font-family: var(--font-text);
  font-size: 14px;
  letter-spacing: -0.224px;
  text-decoration: none;
  white-space: nowrap;
}

.download-menu__item:hover,
.download-menu__item:focus-visible {
  background: rgba(255, 255, 255, 0.14);
}

.download-menu__hint {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
}

/* Пломба: «совпадает / не совпадает» (ArrivalDetailsForm.seal_number_match).
   Две кнопки-переключателя на радио — тап-цели крупные, выбор виден сразу. */
.seal-choice {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* Подпись — как у соседних полей (.field label). */
.seal-choice legend {
  padding: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.224px;
  color: var(--color-ink-muted-80);
  margin-bottom: var(--space-xxs);
}

.seal-choice__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* Через .seal-choice: иначе выигрывает `.field label` (display: block).

   Пока ничего не выбрано, кнопки должны читаться как кнопки: плотная рамка,
   белая подложка и тёмный текст. Раньше рамка была почти прозрачной
   (--color-divider-soft), и на белой карточке выбор сливался с фоном. */
.seal-choice .seal-choice__option {
  flex: 1 1 140px;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 8px 14px;
  border: 1.5px solid color-mix(in srgb, var(--color-ink) 30%, transparent);
  border-radius: var(--radius-sm);
  margin: 0;
  background: var(--color-canvas);
  font-weight: 600;
  color: var(--color-ink);
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.seal-choice .seal-choice__option:hover {
  border-color: color-mix(in srgb, var(--color-ink) 50%, transparent);
}

/* Только для клавиатуры: при касании и клике рамка фокуса не нужна. */
.seal-choice .seal-choice__option:has(input:focus-visible) {
  outline: 2px solid var(--color-primary-focus);
  outline-offset: 2px;
}

.seal-choice__option input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--color-ink);
}

/* Выбрано: «всё в порядке» — зелёным, «есть что заметить» — оранжевым. Одна
   идея в трёх соседних полях выглядит одинаково: номер пломбы (совпадает /
   НЕ совпадает), целостность (НЕ НАРУШЕНА / НАРУШЕНА, ОТСУТСТВУЕТ), водитель
   (присутствует / нет). Значения — коды полей формы. */
.seal-choice .seal-choice__option:has(input[value="same"]:checked),
.seal-choice .seal-choice__option:has(input.js-seal-status[value="INT"]:checked),
.seal-choice .seal-choice__option:has(input.driver-status[value="PRS"]:checked) {
  background: color-mix(in srgb, var(--status-rcl) 16%, var(--color-canvas));
  border-color: var(--status-rcl);
  color: var(--status-rcl);
}

.seal-choice .seal-choice__option:has(input[value="same"]:checked) input,
.seal-choice .seal-choice__option:has(input.js-seal-status[value="INT"]:checked) input,
.seal-choice .seal-choice__option:has(input.driver-status[value="PRS"]:checked) input {
  accent-color: var(--status-rcl);
}

.seal-choice .seal-choice__option:has(input[value="different"]:checked),
.seal-choice .seal-choice__option:has(input.js-seal-status:not([value="INT"]):checked),
.seal-choice .seal-choice__option:has(input.driver-status[value="ABS"]:checked) {
  background: color-mix(in srgb, var(--status-und) 18%, var(--color-canvas));
  border-color: var(--status-und);
  color: var(--status-und);
}

.seal-choice .seal-choice__option:has(input[value="different"]:checked) input,
.seal-choice .seal-choice__option:has(input.js-seal-status:not([value="INT"]):checked) input,
.seal-choice .seal-choice__option:has(input.driver-status[value="ABS"]:checked) input {
  accent-color: var(--status-und);
}

/* Поле номера видно, только когда выбрано «Не совпадает». Правило работает
   лишь пока выбор показан: при «ОТСУТСТВУЕТ» блок выбора скрыт, и поле
   остаётся на месте — заблокированным, с подсказкой. */
.arrival-form:has(.js-seal-choice-box:not([hidden])):not(:has(input.js-seal-choice[value="different"]:checked)) .seal-number-field {
  display: none;
}

/* =========================================================
   Список SSCC поставки (форма оператора) и таблицы SSCC на странице
   ========================================================= */
.sscc-list__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

.sscc-list__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-ink-muted-80);
}

/* Таблица прокручивается сама, страница вбок не уезжает. */
.sscc-table-wrap {
  max-width: 100%;
  max-height: 420px;
  overflow: auto;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
}

.sscc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.sscc-table th,
.sscc-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--color-hairline);
  text-align: left;
  vertical-align: top;
}

.sscc-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--color-canvas-parchment);
  font-size: 12px;
  font-weight: 600;
  color: var(--color-ink-muted-80);
  white-space: nowrap;
}

.sscc-table tbody tr:last-child td {
  border-bottom: 0;
}

.sscc-table__num {
  width: 1%;
  color: var(--color-ink-muted-48);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.sscc-table--edit td {
  padding: 3px 4px;
}

.sscc-table--edit .sscc-table__num {
  padding: 12px 8px 0;
}

.sscc-table__input.field-input {
  min-width: 140px;
  min-height: 38px;
  padding: 6px 8px;
  font-size: 14px;
}

.sscc-table__remove {
  width: 32px;
  height: 32px;
  margin-top: 3px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--color-ink-muted-48);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.sscc-table__remove:hover {
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
  color: var(--color-ink);
}

/* Последняя строка — всегда пустая заготовка для вставки: удалять нечего. */
.sscc-table--edit tbody tr:last-child .sscc-table__remove {
  visibility: hidden;
}

.sscc-table__note {
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.35;
}

.sscc-table tr.is-duplicate {
  background: color-mix(in srgb, var(--status-und) 10%, transparent);
}

.sscc-table tr.is-duplicate .sscc-table__note {
  color: var(--status-und);
}

.sscc-table tr.is-error {
  background: color-mix(in srgb, var(--status-flag) 9%, transparent);
}

.sscc-table tr.is-error .sscc-table__note,
.sscc-table__summary.is-error {
  color: var(--status-flag);
}

/* Sub-nav — код поставки и поставщик.

   Раньше сюда же были свалены статусные чипы и кнопка «Скачать…», а сама
   подшапка прилипала при прокрутке вместе с переключателем вкладок под ней.
   На узком экране это съедало слишком много полезной высоты сразу под шапкой
   сайта. Теперь здесь только заголовок; статусы и кнопка переехали в
   отдельный .status-bar ниже, а сама подшапка (и .status-bar) больше не
   прилипают — уезжают вместе с остальным содержимым при прокрутке. Прилипают
   только шапка сайта (.global-nav) и сам переключатель (.tabs-row) — так
   решили сознательно: подшапке и статусам незачем всё время висеть перед
   глазами, а быстро переключаться между Выгрузкой и Приёмкой нужно с любого
   места страницы. */

.sub-nav {
  background: var(--color-canvas-parchment);
  min-height: 52px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--color-divider-soft);
  padding: var(--space-xs) 0;
}

/* justify-content: space-between и перенос строк — не только ради страницы
   поставки (там сейчас в .sub-nav один-единственный заголовок): та же
   .sub-nav используется на дашборде и в формах заведения/правки как строка
   «заголовок + компактные действия» (см. dashboard.html — там СВОЙ
   .sub-nav__actions с кнопкой «Создать поставку»). */
.sub-nav .container-wide {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.sub-nav__title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: 0.1px;
  color: var(--color-ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

.sub-nav__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* Общий блок статусов и кнопки скачивания — один на страницу поставки,
   рисуется до вкладок и одинаков что на «Выгрузке», что на «Приёмке» (на
   «Приёмке» своей карточки «Данные по поставке» нет, а статусы машины
   видеть нужно в любом случае). Раньше это было частью .sub-nav и было
   МОЖНО спутать с содержимым конкретной вкладки. */
.status-bar {
  background: var(--color-canvas);
  border-bottom: 1px solid var(--color-divider-soft);
  padding: var(--space-sm) 0;
}

.status-bar .container-wide {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.status-bar__chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* =========================================================
   Tabs — Выгрузка / Приёмка (сегментированный переключатель)

   Вариант «Б»: сплошная прилипающая полоса на всю ширину, вкладки делят её
   пополам, а не пилюля-по-содержимому по центру (вариант «А»). Полоса — сразу
   под шапкой сайта, поэтому top у неё — просто высота шапки, без сложения с
   высотой подшапки: подшапка и статусы больше не прилипают и не занимают
   место в стеке прилипающих блоков (ср. со старым --sticky-subnav-h).
   ========================================================= */

.tabs-row {
  display: flex;
  gap: 4px;
  background: var(--color-canvas-parchment);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  padding: 4px;
  margin: var(--space-md) 0;
  position: sticky;
  /* app.js меряет реальную высоту .global-nav и кладёт её сюда через
     --sticky-nav-h; 44px остаётся запасным значением до первого измерения и
     на случай отключённого JS. Само закрепление работает, только пока прямой
     родитель этого блока выше своей собственной строки (см. разметку
     shipment_detail.html: tabs-row и .container с карточками — в одной общей
     обёртке .container-wide, иначе прилипать было бы просто некуда). */
  top: var(--sticky-nav-h, 44px);
  z-index: 15;
}

/* Отступ для scrollIntoView(), а не только для якорных ссылок: app.js зовёт
   scrollIntoView у новой карточки единицы и у первой ошибки формы. Без этого
   отступа их можно было бы прокрутить ровно под прилипающие шапку и
   переключатель. --sticky-tabs-h — реальная высота .tabs-row, измеренная
   app.js тем же способом, что и высота шапки: у переключателя стабильная
   высота (те же два слова «Выгрузка»/«Приёмка»), но запасное значение на
   случай отключённого JS оставляем на всякий случай. */
.unit-card,
.field-errors,
.js-form-error {
  scroll-margin-top: calc(
    var(--sticky-nav-h, 44px) + var(--sticky-tabs-h, 60px) + var(--space-md)
  );
}

.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-size: 14px;
  font-weight: 600;
  color: var(--color-ink-muted-48);
  transition: background 0.15s ease, color 0.15s ease;
}

.tab:hover {
  text-decoration: none;
  color: var(--color-ink);
}

.tab.is-active {
  background: var(--color-primary);
  color: #ffffff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.tab-badge {
  /* --status-flag-solid, а не --status-flag: здесь сплошная заливка с белым
     текстом — контраст решает читаемость надписи на заливке, а не заливки
     на карточке, это обратная задача с другим ответом (см. токен в :root). */
  background: var(--status-flag-solid);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
}

/* Раньше — полупрозрачная БЕЛАЯ подложка поверх --color-primary: белый текст
   на светлеющей от неё заливке терял контраст (в тёмной теме — 2.1:1). Здесь
   вместо этого подмешиваем чёрный — заливка темнеет, а не светлеет, синий
   тон остаётся виден, и белый текст читается в обеих темах. */
.tab.is-active .tab-badge {
  background: color-mix(in srgb, black 5%, var(--color-primary));
}

/* =========================================================
   Buttons
   ========================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  font-family: var(--font-text);
  font-size: 17px;
  line-height: 1;
  border: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.08s ease;
}

.btn:active {
  transform: scale(0.95);
}

/* Значок перед подписью кнопки («+ Создать поставку»): не сжимается и стоит
   по центру строки. */
.btn__icon {
  display: inline-flex;
  flex-shrink: 0;
}

.btn-primary {
  background: var(--color-primary);
  color: var(--on-primary);
  border-radius: var(--radius-pill);
  padding: 11px 22px;
}

.btn-primary:hover {
  text-decoration: none;
  background: var(--color-primary-focus);
}

.btn-secondary-pill {
  background: transparent;
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-pill);
  padding: 10px 21px;
}

/* Кнопки на чёрной global-nav панели. Панель всегда чёрная — в любой теме —
   поэтому НЕ используем --color-ink (он инвертируется в тёмной теме и делает
   эту кнопку белой-на-белой). Полупрозрачная белая заливка поверх чёрного
   фона корректно читается в обеих темах. */
.btn-dark-utility {
  background: rgba(255, 255, 255, 0.14);
  color: #ffffff;
  font-size: 14px;
  letter-spacing: -0.224px;
  border-radius: var(--radius-sm);
  padding: 8px 15px;
}

.btn-dark-utility:hover {
  background: rgba(255, 255, 255, 0.22);
}

/* Кнопка с иконкой (SVG) + подпись — используется вместо "+ текстом" и эмодзи */
.btn-icon {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.btn-icon svg {
  flex-shrink: 0;
}

/* "Добавить слой / паллету / расхождение" — светлая пилюля с тонкой рамкой */
.btn-add {
  background: var(--color-canvas);
  color: var(--color-primary);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-pill);
  padding: 9px 18px 9px 14px;
  font-size: 14px;
  font-weight: 600;
}

.btn-add:hover {
  background: var(--color-canvas-parchment);
  text-decoration: none;
}

.btn-add svg {
  color: var(--color-primary);
}

/* Удаление поставки в шапке страницы. Не залитая, а обводочная: действие
   необратимое, и кнопка не должна соперничать по заметности с «Начать
   выгрузку». Заливка появляется только при наведении — тогда уже ясно, куда
   нажимаешь. */
.btn-danger-outline {
  color: var(--status-flag);
  background: transparent;
  /* Радиус и отступы — как у остальных кнопок: .btn их не задаёт, каждый
     вариант выставляет сам, и без этой строки кнопка оставалась квадратной
     среди сплошных «таблеток». */
  border-radius: var(--radius-pill);
  padding: 9px 16px;
  font-weight: 600;
  /* Обводка нарисована тенью, а не border: border добавил бы 2px к высоте, и
     кнопка стояла бы выше соседних «таблеток» в одной строке. */
  border: none;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--status-flag) 45%, transparent);
}

.btn-danger-outline:hover {
  background: color-mix(in srgb, var(--status-flag) 12%, transparent);
  box-shadow: inset 0 0 0 1px var(--status-flag);
}

.btn-block {
  width: 100%;
}

/* =========================================================
   Cards
   ========================================================= */

.card {
  background: var(--color-canvas);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
}

.card + .card {
  margin-top: var(--space-lg);
}

.card-section-title {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: 0.231px;
  margin-bottom: var(--space-md);
}

.info-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  font-size: 14px;
  color: var(--color-ink-muted-80);
}

/* Shipment card in the dashboard grid */

/* Треки сетки не шире доступной ширины (min(…, 100%)): на 320px колонка в
   280px не влезала в контейнер с отступами и распирала страницу. То же у
   .arrival-photos и .photo-grid. */
.shipment-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: var(--space-lg);
}

.shipment-card {
  background: var(--color-canvas);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.shipment-card__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
}

.shipment-card__number {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
}

.shipment-card__meta {
  color: var(--color-ink-muted-48);
  font-size: 14px;
  letter-spacing: -0.224px;
}

.shipment-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: var(--space-xs);
}

/* =========================================================
   Status chips
   ========================================================= */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -0.08px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--color-canvas-parchment);
  /* Рамка — тем же цветом, что текст и точка (currentColor, без разбавления):
     раньше она смешивалась с прозрачностью на 45–48%, и это ограничение
     формулы, а не подбора цвета — при таком небольшом весе итоговый тон
     всегда ближе к фону, чем к цвету, и 3:1 не достижимы ни при каком
     --status-xxx (проверено расчётом на всех девяти статусах). Сплошная
     рамка того же цвета, что и точка, даёт тот же контраст, что и текст —
     он уже подобран под фон ниже. */
  border: 1px solid currentColor;
  color: var(--color-ink);
  /* Раньше — nowrap. Длинный текст («Пломба НЕ совпадает с документами»)
     тогда не влезал в чип на узком экране и вылезал за границу карточки
     (а на 320px — и за границу страницы целиком, горизонтальным скроллом).
     Короткие статусы («Разгружается» и т.п.) на одной строке и с normal —
     переносить им попросту некуда, пока помещаются. Проверено рендером в
     браузере на 320/360/400px: чип переносится на две строки внутри
     карточки, переполнения нет. */
  white-space: normal;
}

/* color-mix даёт каждому статусу собственный тонированный фон на основе его
   же цвета (--status-xxx, заданного через `color`) — без дублирования
   палитры под каждый чип отдельно. Поддержано всеми актуальными браузерами;
   там, где нет — остаётся читаемый фолбэк выше (плоский серый фон, но
   рамка и точка своим цветом — уже заданы в базовом правиле). */
@supports (background: color-mix(in srgb, red 10%, transparent)) {
  .chip {
    background: color-mix(in srgb, currentColor 16%, var(--color-canvas));
  }
}

.chip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.chip--lg {
  font-size: 13px;
  padding: 8px 16px;
}

.chip--lg::before {
  width: 7px;
  height: 7px;
}

.chip-not { color: var(--status-not); }
/* Расхождение выгрузки, аннулированное кладовщиком при приёмке — тот же
   нейтрально-серый токен, что и «Не прибыл» (chip-not): оба состояния
   значат «это больше не актуально», и токен уже проверен на контраст
   и в светлой, и в тёмной теме (см. правило .chip выше). */
.chip-voided { color: var(--status-not); }
.chip-arr { color: var(--status-arr); }
.chip-unl { color: var(--status-unl); }
.chip-und { color: var(--status-und); }
.chip-dep { color: var(--status-dep); }
.chip-awt { color: var(--status-awt); }
.chip-rec { color: var(--status-rec); }
.chip-rcl { color: var(--status-rcl); }

/* Соответствие фактической пломбы документам. Статус вычисляемый, руками не
   меняется, поэтому цвет тут — единственный способ его заметить: зелёный, когда
   номера совпали, красный — когда разошлись. Базовый .chip делает фон и рамку
   подмешиванием currentColor, так что достаточно задать цвет. */
/* --status-not, а не --color-ink-muted-48: последний рассчитан на обычный
   текст на сплошном фоне (используется по всему приложению), а не на
   тонированный фон чипа, и там не дотягивал до 4.5:1. «Пломба неизвестна» —
   тоже нейтральное, «никакое» состояние, поэтому берём готовый нейтральный
   статус, а не заводим для одного чипа ещё один токен. */
.chip-seal-unknown { color: var(--status-not); }
.chip-seal-ok { color: var(--status-rcl); }
.chip-seal-mismatch { color: var(--status-flag); }
/* «Пломба нарушена» — тот же красный: повод разбираться, как и расхождение. */
.chip-seal-broken { color: var(--status-flag); }

/* Строка действий формы: сохранение слева, разрушительное действие отжато
   вправо. Расстояние между ними не украшение — оно снижает шанс промахнуться
   мимо «Сохранить» и попасть в «Удалить» пальцем на планшете. */
.form-actions-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.form-actions-row .btn-danger-outline {
  margin-left: auto;
}

/* Заголовок карточки и кнопка правки в одной строке */
.card-head-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
}

/* Всё о пломбе одной строкой-плашкой: номер по документам, фактический,
   целостность, соответствие. Подложка отделяет блок от общей строки данных
   — это те сведения, которые смотрят первыми при приёмке машины. */
.seal-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-lg);
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-ink) 4%, transparent);
  font-size: 14px;
  color: var(--color-ink-muted-80);
}

.seal-summary__title {
  font-weight: 600;
  color: var(--color-ink);
}

@media (max-width: 640px) {
  .seal-summary {
    gap: var(--space-xs) var(--space-md);
    padding: var(--space-sm);
  }
}

.chip-discrepancy-flag {
  color: var(--status-flag);
  background: rgba(214, 69, 69, 0.08);
}

/* =========================================================
   Forms
   ========================================================= */

.field {
  margin-bottom: var(--space-md);
}

.field label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.224px;
  color: var(--color-ink-muted-80);
  margin-bottom: var(--space-xxs);
}

.field-input,
.field-select,
.field-textarea {
  width: 100%;
  font-family: var(--font-text);
  font-size: 17px;
  color: var(--color-ink);
  background: var(--color-canvas);
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
}

.field-input:focus,
.field-select:focus,
.field-textarea:focus {
  outline: 2px solid var(--color-primary-focus);
  outline-offset: 1px;
}

.form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 var(--space-md);
}

@media (max-width: 640px) {
  .form-grid-2 {
    grid-template-columns: 1fr;
  }
}

.js-pallete-field,
.js-bulk-field {
  display: none;
}

/* =========================================================
   Photo gallery / multi-file preview
   ========================================================= */

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
  gap: var(--space-md);
  margin-top: var(--space-xs);
}

.photo-tile {
  position: relative;
  border-radius: var(--radius-sm);
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background: var(--color-canvas-parchment);
  box-shadow: var(--shadow-product);
}

.photo-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-empty {
  color: var(--color-ink-muted-48);
  font-size: 14px;
  padding: var(--space-lg) 0;
}

/* Обёртка нужна, чтобы кнопка удаления была DOM-соседом .photo-tile
   (а не вложенной в него формой) — вложенные <form> в HTML запрещены. */
.photo-tile-wrapper {
  position: relative;
}

.photo-delete-form {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  margin: 0;
}

/* Кадр, добавленный уже на приёмке через «Дополнить данные» — отличаем от
   исходных снимков выгрузки тем же нейтральным тоном, что и .chip-voided /
   .chip-stage, чтобы в галерее не заводить третий цвет без надобности. */
.photo-tile-wrapper__stage-badge {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 2;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: -0.08px;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.photo-delete-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  padding: 0;
}

.photo-delete-btn:hover {
  background: var(--status-flag);
}

.photo-delete-btn svg {
  width: 13px;
  height: 13px;
}

/* Плитка "добавить фото" — часть той же сетки, что и фото; клик по ней
   открывает системный выбор файла, загрузка стартует сразу по выбору
   (см. .js-ajax-upload-form в app.js) — без отдельной кнопки "Загрузить". */
.photo-tile--add {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  border: 1.5px dashed var(--color-hairline);
  background: transparent;
  color: var(--color-primary);
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  padding: var(--space-xs);
  transition: border-color 0.15s ease, background 0.15s ease;
}

.photo-tile--add:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 6%, transparent);
}

.photo-tile--add svg {
  width: 22px;
  height: 22px;
}

.visually-hidden-input,
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.file-preview-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

.file-preview-grid img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-hairline);
}

/* =========================================================
   Discrepancy list
   ========================================================= */

.discrepancy-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-xs);
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--color-divider-soft);
}

.discrepancy-row:last-child {
  border-bottom: none;
}

/* Аннулированное расхождение — тонкая рамка слева тем же нейтральным тоном,
   что и чип «Аннулировано кладовщиком» (.chip-voided). Не opacity: запись
   всё ещё должна читаться полностью, приглушать нужно не текст, а сам факт
   «это в стороне от активных расхождений». */
.discrepancy-row--voided {
  border-left: 3px solid var(--status-not);
  padding-left: var(--space-sm);
}

.discrepancy-row__supplement-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

/* display: none по умолчанию, а не [hidden] — универсальный тогл
   data-toggle-form (см. app.js) переключает именно inline style.display,
   и раскрывает в "grid": те же формы, что и остальные тогл-формы страницы
   (форма расхождения, форма правки поставки), поэтому колонка здесь одна —
   просто устойчивый вертикальный стек полей без флекс-обвязки отдельно. */
.discrepancy-row__supplement-form,
.discrepancy-row__annul-form {
  display: none;
  margin-top: var(--space-xs);
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--color-canvas-parchment);
  gap: var(--space-xs);
}

.discrepancy-row__annul-form .field-textarea {
  margin: 0;
}

.discrepancy-row__main {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
}

.discrepancy-row__qty {
  font-family: var(--font-display);
  font-weight: 600;
  flex-shrink: 0;
}

/* =========================================================
   Tiles
   ========================================================= */

.tile {
  padding: var(--space-xxl) 0;
}

.tile-light {
  background: var(--color-canvas);
}

.tile-parchment {
  background: var(--color-canvas-parchment);
}

/* =========================================================
   Auth / login card
   ========================================================= */

.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-canvas-parchment);
  padding: var(--space-lg);
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--color-canvas);
  border-radius: var(--radius-lg);
  padding: var(--space-xxl) var(--space-xl);
  box-shadow: var(--shadow-product);
}

.auth-card h1 {
  font-size: 28px;
  text-align: center;
  margin-bottom: var(--space-xs);
}

.auth-card__subtitle {
  text-align: center;
  color: var(--color-ink-muted-48);
  font-size: 14px;
  margin-bottom: var(--space-xl);
}

/* =========================================================
   Messages / alerts
   ========================================================= */

/* Слой уведомлений. Фиксированный, а не строка в потоке страницы: раньше
   сообщение вставлялось в начало <main>, и при ошибке загрузки фото в нижней
   части длинной вкладки оно оказывалось за экраном — человек видел, что
   «ничего не произошло». Теперь оно всегда под шапкой, поверх содержимого.
   pointer-events: none у слоя и auto у самих сообщений — пустой слой не
   перехватывает клики по странице. */
.alerts {
  position: fixed;
  top: calc(var(--sticky-nav-h, 44px) + var(--space-sm));
  left: 50%;
  transform: translateX(-50%);
  width: min(560px, calc(100vw - 2 * var(--space-md)));
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  pointer-events: none;
}

.alerts:empty {
  display: none;
}

.alert {
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
  font-size: 14px;
  margin-bottom: var(--space-xs);
}

.alerts .alert {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: 0;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

.alert__text {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Крестик у уведомления: ошибки висят, пока их не закроют (см. flash в
   app.js), поэтому закрыть их должно быть чем. */
.alert__close {
  flex: none;
  appearance: none;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 20px;
  line-height: 1;
  padding: 0 var(--space-xs);
  cursor: pointer;
  opacity: 0.7;
}

.alert__close:hover,
.alert__close:focus-visible {
  opacity: 1;
}

.alert-success {
  background: color-mix(in srgb, var(--status-rcl) 16%, var(--color-canvas));
  color: var(--status-rcl);
  border-left: 4px solid var(--status-rcl);
  font-weight: 600;
}

.alert-error {
  background: color-mix(in srgb, var(--status-flag) 16%, var(--color-canvas));
  color: var(--status-flag);
  border-left: 4px solid var(--status-flag);
  font-weight: 600;
}

.alert-info {
  background: color-mix(in srgb, var(--color-primary) 16%, var(--color-canvas));
  color: var(--color-primary);
  border-left: 4px solid var(--color-primary);
  font-weight: 600;
}

/* =========================================================
   Empty state
   ========================================================= */

.empty-state {
  text-align: center;
  padding: var(--space-section) var(--space-lg);
  color: var(--color-ink-muted-48);
}

/* =========================================================
   Responsive
   ========================================================= */

@media (max-width: 640px) {
  .tile {
    padding: var(--space-xl) 0;
  }
}

/* =========================================================
   Роль оператора — бейдж рядом с именем в шапке
   ========================================================= */

.role-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  color: #fff;
}

/* Отдельный тон для кладовщика — та же форма бейджа, что у оператора выше,
   контраст к белому тексту посчитан отдельно и для тёмной темы (см. override
   в html[data-theme="dark"] ниже, рядом с --color-primary для тёмной темы). */
.role-badge--keeper {
  background: var(--color-keeper-badge);
}

/* =========================================================
   Слои / паллеты — сворачиваемый блок и карточки юнитов
   ========================================================= */

.units-block > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.units-block > summary::-webkit-details-marker {
  display: none;
}

/* Раньше — текстовый треугольник (::before, "▸"): на телефоне и при беглом
   взгляде почти не читался. Настоящая SVG-иконка (см. icon_chevron.html)
   даёт тот же поворот при раскрытии, но заметно крупнее и чётче. */
.units-block__chevron {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-ink-muted-48);
  transition: transform 0.15s ease;
}

.units-block[open] > summary .units-block__chevron {
  transform: rotate(90deg);
}

.unit-card {
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin-top: var(--space-md);
  background: var(--color-canvas-parchment);
}

.unit-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.unit-card__title-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px var(--space-xs);
}

/* Чип типа единицы в шапке карточки: паллета и слой — разными цветами. Токены
   статусов, а не новые цвета: их контраст уже проверен в обеих темах (см.
   .chip). Точка-маркер статуса здесь лишняя — это не статус. */
.unit-type-chip::before {
  display: none;
}

.unit-type-chip--pallet { color: var(--status-unl); }
.unit-type-chip--layer { color: var(--status-awt); }

.unit-card__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
}

.unit-card__delete {
  background: transparent;
  border: none;
  color: var(--color-ink-muted-48);
  cursor: pointer;
  padding: 5px;
  border-radius: var(--radius-xs);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.unit-card__delete:hover {
  color: var(--status-flag);
  background: rgba(214, 69, 69, 0.1);
}

.unit-card__delete svg {
  width: 15px;
  height: 15px;
}

/* Поле и кнопка переносятся, поле сжимается. min-width: 0 обязателен — у
   flex-элемента с input он по умолчанию равен ширине содержимого, и строка
   не сжималась вовсе (страница уезжала вбок на 320–360px). */
.unit-sscc-form {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  min-width: 0;
}

.field-input--inline {
  width: auto;
  padding: 6px 12px;
  font-size: 14px;
}

/* SSCC единицы: номера «чипами» с пометками, под ними поле с подсказками. */
.unit-sscc {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: var(--space-xs);
}

.unit-sscc__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.unit-sscc__title {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-ink-muted-80);
}

.unit-sscc__list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.unit-sscc__item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  min-height: 36px;
  padding: 3px 3px 3px 10px;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-canvas);
  font-size: 13px;
}

/* Номер и пометки — колонкой слева, крестик — справа по центру. */
.unit-sscc__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.3;
}

.unit-sscc__number {
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Номер есть и у другой единицы или его нет в списке — на это надо обратить
   внимание: оранжевая рамка и пометка. */
.unit-sscc__item.is-shared,
.unit-sscc__item.is-extra {
  border-color: color-mix(in srgb, var(--status-und) 60%, transparent);
  background: color-mix(in srgb, var(--status-und) 10%, var(--color-canvas));
}

.unit-sscc__flag {
  font-size: 12px;
  color: var(--status-und);
}

.unit-sscc__remove-form {
  margin: 0;
}

.unit-sscc__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--color-ink-muted-48);
  cursor: pointer;
}

.unit-sscc__remove:hover {
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
  color: var(--color-ink);
}

.unit-sscc__remove svg {
  width: 14px;
  height: 14px;
}

/* Подсказки SSCC: список под полем, поверх содержимого ниже. */
.sscc-combo {
  position: relative;
}

/* Кнопка «ABC» / «123» в полях с цифровой клавиатурой (SSCC, артикул,
   номер пломбы, номер ворот) — переключает цифровую и буквенную
   клавиатуру (app.js, initDigitKeyboard). Встроена в правый край поля
   вплотную, на всю его высоту, с разделителем слева: раньше она стояла с
   отступом 4px и своей рамкой и выглядела отдельной кнопкой внутри поля.
   Поле — блочное: строчный input оставлял под собой зазор по базовой
   линии, обёртка выходила выше поля, и кнопка съезжала вниз. */
.digit-kbd {
  position: relative;
  display: block;
}

.digit-kbd .digit-kbd__input {
  display: block;
  padding-right: 64px;
}

.digit-kbd__toggle {
  position: absolute;
  /* 1px — рамка поля: кнопка ложится внутрь неё, вплотную к краю */
  top: 1px;
  right: 1px;
  bottom: 1px;
  min-width: 52px;
  padding: 0 10px;
  border: 0;
  border-left: 1px solid var(--color-hairline);
  border-radius: 0 calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px) 0;
  background: var(--color-canvas-parchment);
  color: var(--color-ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  cursor: pointer;
  touch-action: manipulation;
}

/* Поле заблокировано (номер пломбы при «ОТСУТСТВУЕТ») — переключать нечего. */
.digit-kbd__input:disabled ~ .digit-kbd__toggle {
  display: none;
}

.camera .digit-kbd__toggle {
  border-left-color: rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}

.sscc-combo__list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 30;
  max-height: 280px;
  margin: 0;
  padding: 4px;
  overflow-y: auto;
  list-style: none;
  background: var(--color-canvas);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}

.sscc-combo__list[hidden] {
  display: none;
}

.sscc-combo__option {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  min-height: 40px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 14px;
}

.sscc-combo__option.is-active,
.sscc-combo__option:hover {
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.sscc-combo__option.is-disabled {
  cursor: default;
  opacity: 0.55;
}

.sscc-combo__number {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.sscc-combo__meta {
  font-size: 12px;
  color: var(--color-ink-muted-48);
}

.sscc-combo__flag {
  flex-basis: 100%;
  font-size: 12px;
  color: var(--status-und);
}

/* Нераспределённые номера SSCC. Блок в своей обёртке (#sscc-unallocated),
   поэтому отступ `.card + .card` на него не действует — задан здесь. */
.sscc-unallocated {
  margin-top: var(--space-lg);
}

.sscc-unallocated[open] > summary .units-block__chevron {
  transform: rotate(90deg);
}

.sscc-unallocated > summary {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  cursor: pointer;
  list-style: none;
}

.sscc-unallocated > summary::-webkit-details-marker {
  display: none;
}

.sscc-unallocated .sscc-table-wrap {
  margin-top: var(--space-sm);
}

.sscc-unallocated__empty,
.sscc-unallocated__extra,
.sscc-unallocated__more {
  margin-top: var(--space-sm);
}

.sscc-table__number {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* «Выгружено мест» над кнопкой «Завершить выгрузку»: подпись, поле и
   «Сохранить» в строку, ошибка — отдельной строкой под ними. */
.unloaded-places-form {
  display: flex;
  flex-wrap: wrap;
  gap: 6px var(--space-xs);
  align-items: center;
  margin-bottom: var(--space-sm);
}

.unloaded-places-form__label {
  font-size: 14px;
  font-weight: 600;
}

.unloaded-places-form .field-input--inline {
  flex: 1 1 64px;
  max-width: 140px;
  min-width: 0;
}

.unloaded-places-form .field-input[aria-invalid="true"] {
  border-color: var(--status-flag);
  box-shadow: 0 0 0 1px var(--status-flag);
}

.unloaded-places-form__error {
  flex-basis: 100%;
  margin: 0;
}

/* =========================================================
   Модалка подтверждения удаления — в стиле iOS Alert
   ========================================================= */

.confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
}

/* Вопрос из кода (askConfirm) — поверх лайтбокса (200) и камеры (300):
   удаление неотправленного фото спрашивают прямо из просмотра. */
.confirm-modal.confirm-modal--top {
  z-index: 400;
}

.confirm-modal__cancel[hidden] {
  display: none;
}

.confirm-modal[hidden] {
  display: none;
}

.confirm-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(18px) saturate(115%);
  -webkit-backdrop-filter: blur(18px) saturate(115%);
}

.confirm-modal__dialog {
  position: relative;
  width: 100%;
  max-width: 300px;
  background: var(--color-canvas);
  border-radius: 26px;
  overflow: hidden;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
  text-align: center;
  animation: confirm-modal-in 0.2s cubic-bezier(0.32, 0.72, 0, 1);
}

@keyframes confirm-modal-in {
  from { transform: scale(0.92); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.confirm-modal__body {
  padding: 24px 22px 18px;
}

.confirm-modal__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  margin-bottom: 7px;
  letter-spacing: -0.2px;
}

.confirm-modal__message {
  font-size: 13.5px;
  color: var(--color-ink-muted-48);
  line-height: 1.45;
}

/* iOS 26: кнопки — отдельные пилюли в столбик, деструктив сверху */
.confirm-modal__actions {
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  padding: 0 14px 14px;
}

.confirm-modal__actions button {
  width: 100%;
  border: none;
  padding: 13px 0;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  font-family: var(--font-text);
  border-radius: var(--radius-pill);
}

.confirm-modal__cancel {
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-ink) 9%, transparent);
}

/* --status-flag-solid, а не --status-flag: тот подобран для ТЕКСТА на
   тонированном фоне чипа (см. коммент у токена) и на сплошной заливке
   выглядит тускло — в тёмной теме это ещё и просто розовый (#ff7a72), а не
   красный. -solid — уже готовый под сплошную заливку с белым текстом тон
   (используется в .tab-badge), по обеим темам ближе к системному red danger
   и держит те же ≥4.5:1, что и обычный --status-flag. */
.confirm-modal__confirm {
  color: #fff;
  background: var(--status-flag-solid);
}

/* Тона кнопки подтверждения (задаются из data-confirm-tone на форме) */
.confirm-modal__confirm.is-danger {
  background: var(--status-flag-solid);
}

.confirm-modal__confirm.is-blue {
  background: var(--status-unl);
}

.confirm-modal__confirm.is-green {
  background: var(--status-rcl);
}

/* =========================================================
   Тёмная тема
   ========================================================= */

html[data-theme="dark"] {
  /* Раньше здесь была системная палитра iOS без изменений — она хорошо
     смотрится на чистом чёрном, но чип красит не холст, а тонированный
     подмес самого этого цвета (16%/22%, см. .chip ниже), и семь статусов
     из девяти не дотягивали до 4.5:1 текста к фону чипа — контраст мерился
     рендером в браузере, а не на глаз. Плюс рамка была полупрозрачной
     (currentColor на 45%), а это на любом цвете физически не может дать
     3:1 к такому фону: 45% веса в сторону цвета от фона, который сам того
     же цвета, — слишком маленький шаг. Ниже — те же тона чуть светлее (без
     смены оттенка, только светлота), и рамка ниже сделана непрозрачной. */
  --status-not: #a5a5a9;
  --status-arr: #9c9aff;
  --status-unl: #51a8ff;
  --status-und: #ff9f0a;
  /* Чуть светлее --status-not: иначе после подгонки под контраст оба серых
     («Не прибыл» и «Уехал») сходились в один и тот же тон. */
  --status-dep: #adadb1;
  --status-awt: #d086f5;
  /* «Принимается»: до этой правки совпадал с --status-unl («Разгружается»). */
  --status-rec: #32ade6;
  --status-rcl: #32d74b;
  --status-flag: #ff7a72;
  /* Для .tab-badge: сплошная заливка с белым текстом требует контраста в
     обратную сторону — заливка должна быть темнее, а не светлее, чем
     --status-flag выше (тот подобран для текста НА тонированном фоне). */
  --status-flag-solid: #ec0d00;

  --color-primary: #2997ff;
  --color-primary-focus: #0a84ff;
  --color-primary-on-dark: #64b5ff;

  /* Темнее, чем в светлой теме — как и у --status-flag-solid выше: сплошная
     заливка с белым текстом на тёмном фоне странице держит контраст лучше на
     более насыщенном/тёмном тоне, а не на светлом. 3.74:1 — тот же порядок,
     что у операторского --color-primary в этой же теме (3.02:1): бейдж роли
     в тёмной теме везде на мелком жирном тексте, не на длинных абзацах. */
  --color-keeper-badge: #0d9488;

  --color-ink: #f5f5f7;
  --color-body: #f5f5f7;
  --color-ink-muted-80: #d1d1d6;
  --color-ink-muted-48: #98989d;

  --color-divider-soft: rgba(255, 255, 255, 0.06);
  --color-hairline: #3a3a3c;

  --color-canvas: #1c1c1e;
  --color-canvas-parchment: #131314;
  --color-surface-pearl: #232325;

  --shadow-product: rgba(0, 0, 0, 0.55) 3px 5px 30px 0;
}

html[data-theme="dark"] .sub-nav {
  background: var(--color-canvas-parchment);
}

html[data-theme="dark"] .status-bar {
  background: var(--color-canvas);
  border-color: rgba(255, 255, 255, 0.1);
}

html[data-theme="dark"] .tabs-row {
  background: var(--color-surface-pearl);
  border-color: rgba(255, 255, 255, 0.1);
}

html[data-theme="dark"] .unit-card {
  background: var(--color-surface-pearl);
}

html[data-theme="dark"] .chip-discrepancy-flag {
  background: rgba(214, 69, 69, 0.18);
}

html[data-theme="dark"] .field-input,
html[data-theme="dark"] .field-select,
html[data-theme="dark"] .field-textarea {
  background: var(--color-surface-pearl);
  border-color: rgba(255, 255, 255, 0.12);
  color: var(--color-ink);
}

html[data-theme="dark"] .confirm-modal__dialog {
  background: var(--color-surface-pearl);
}

html[data-theme="dark"] .confirm-modal__backdrop {
  background: rgba(0, 0, 0, 0.6);
}

/* =========================================================
   Кнопки: мягкая (btn-soft) и состояние "скрыть форму" (btn-add.is-open)
   ========================================================= */

/* п.9: современная мягкая кнопка вместо btn-pearl */
.btn-soft {
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  color: var(--color-primary);
  border: none;
  border-radius: var(--radius-pill);
  padding: 9px 16px;
  font-size: 14px;
  font-weight: 600;
}

.btn-soft:hover {
  background: color-mix(in srgb, var(--color-primary) 20%, transparent);
  text-decoration: none;
}

.btn-sm {
  padding: 6px 13px;
  font-size: 13px;
}

/* п.13: в открытом состоянии кнопка меняет цвет на нейтрально-серый,
   а иконка-плюс поворачивается на 45° и визуально становится крестиком */
.btn-add.is-open {
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
  color: var(--color-ink-muted-48);
  border-color: transparent;
}

.btn-add.is-open svg {
  color: var(--color-ink-muted-48);
}

.btn-add__icon {
  display: inline-flex;
  transition: transform 0.18s ease;
}

.btn-add.is-open .btn-add__icon {
  transform: rotate(45deg);
}

/* =========================================================
   п.6: блок кнопок добавления слоя/паллеты — всегда снизу списка
   ========================================================= */

.units-actions {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  align-items: center;
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-divider-soft);
}

.units-actions__hint {
  flex-basis: 100%;
  margin: 0;
  color: var(--color-ink-muted-48);
}

.units-actions .btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* =========================================================
   п.5: кольцевой прогресс загрузки поверх миниатюры
   ========================================================= */

.photo-tile--uploading {
  position: relative;
  border-radius: var(--radius-sm);
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background: var(--color-canvas-parchment);
}

.photo-tile--uploading img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.55);
}

.upload-ring {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.upload-ring svg {
  width: 44px;
  height: 44px;
  transform: rotate(-90deg);
}

.upload-ring__track {
  fill: none;
  stroke: rgba(255, 255, 255, 0.28);
  stroke-width: 3.5;
}

.upload-ring__bar {
  fill: none;
  stroke: #fff;
  stroke-width: 3.5;
  stroke-linecap: round;
  /* r=19 → длина окружности 2πr ≈ 119.38 */
  stroke-dasharray: 119.38;
  stroke-dashoffset: 119.38;
  transition: stroke-dashoffset 0.15s linear;
}

.photo-tile--uploading.is-error .upload-ring__bar {
  stroke: var(--status-flag);
}

/* =========================================================
   п.11: лайтбокс предпросмотра фото
   ========================================================= */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  /* Браузеру достаётся только щипок (увеличить фото); одиночный палец не
     прокручивает страницу, а обрабатывается свайпом в app.js. Когда страница
     уже увеличена (.is-zoomed, ставит app.js по visualViewport.scale),
     палец должен двигать увеличенную картинку — там touch-action: auto. */
  touch-action: pinch-zoom;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-lg);
}

.lightbox[hidden] {
  display: none;
}

/* Страница под лайтбоксом (и под confirm-модалкой) заморожена: body
   фиксируется, а app.js выставляет ему top = минус прежняя прокрутка (см.
   lockPageScroll). overscroll-behavior гасит «резиновый» отскок и pull-to-
   refresh в мобильных браузерах, пока слой открыт. */
html.is-scroll-locked {
  overscroll-behavior: none;
}

html.is-scroll-locked body {
  position: fixed;
  left: 0;
  right: 0;
  overflow: hidden;
}

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* Обёртка вокруг картинки и спиннера: пока экранная версия грузится, у
   <img> нет src (и атрибут hidden — см. app.js), и без своего минимального
   размера тут было бы нечему центрировать спиннер относительно. */
.lightbox__body {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  min-height: 120px;
  max-width: 100%;
  max-height: 78vh;
  /* Кадр едет за пальцем при свайпе (transform ставит app.js). */
  will-change: transform;
}

.lightbox__img {
  position: relative;
  max-width: 100%;
  max-height: 78vh;
  border-radius: var(--radius-md);
  object-fit: contain;
  animation: lightbox-in 0.18s cubic-bezier(0.32, 0.72, 0, 1);
}

@keyframes lightbox-in {
  from { transform: scale(0.94); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* Спиннер загрузки экранной версии. Раньше на этом месте на миг оставалось
   предыдущее открытое фото или мгновенно подставлялось превью с плитки —
   теперь тут явно видно, что грузится именно новая, более крупная версия, а
   не что она «уже открылась». */
.lightbox__spinner {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: #fff;
  animation: lightbox-spin 0.7s linear infinite;
}

@keyframes lightbox-spin {
  to { transform: rotate(360deg); }
}

.lightbox.is-zoomed {
  touch-action: auto;
}

/* Счётчик «3 / 7» — на всех устройствах: при свайпе иначе непонятно, сколько
   осталось. По центру сверху, на одной линии с кнопкой закрытия. */
.lightbox__counter {
  position: absolute;
  top: 23px;
  left: 50%;
  z-index: 2;
  transform: translateX(-50%);
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

/* Кнопки ‹ › — только там, где есть мышь и место: на телефоне листают
   свайпом, а кнопки закрывали бы часть фото на узком экране. z-index явный:
   без него они оказались бы в одной группе со .lightbox__backdrop (см.
   комментарий у .lightbox__original ниже). */
.lightbox__nav {
  display: none;
  position: absolute;
  top: 50%;
  z-index: 2;
  width: 44px;
  height: 44px;
  margin-top: -22px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease, opacity 0.15s ease;
}

.lightbox__nav--prev {
  left: 18px;
}

.lightbox__nav--next {
  right: 18px;
}

/* Шеврон в partial нарисован вправо — для «назад» отражаем. */
.lightbox__nav--prev svg {
  transform: scaleX(-1);
}

.lightbox__nav:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.3);
}

.lightbox__nav:disabled {
  opacity: 0.3;
  cursor: default;
}

@media (min-width: 641px) and (hover: hover) {
  .lightbox__nav {
    display: flex;
  }

  /* Поля под кнопки: фото не должно заходить под них на узком окне. */
  .lightbox {
    padding-left: 76px;
    padding-right: 76px;
  }
}

/* z-index обязателен, а не «на всякий случай»: .lightbox — flex-контейнер, и
   flex-элементы стекуются по z-index (или порядку в DOM при z-index:auto) в
   одной группе, а не по правилу «position:static рисуется под position:—».
   Без явного z-index на этом элементе `opacity: 0.85` в покое сама по себе
   создаёт стековый контекст (что уравнивало его с .lightbox__backdrop), а
   `opacity: 1` при наведении его снимает — ссылка на миг уходила под
   затемнение, курсор терял :hover, opacity откатывалась на 0.85, ссылка
   всплывала обратно — и так по кругу: наведение выглядело как мерцание, а
   клик мог попасть в фон вместо ссылки. Явный z-index убирает зависимость
   стекового порядка от opacity вообще. */
.lightbox__error {
  position: relative;
  z-index: 1;
  max-width: min(420px, 86vw);
  padding: var(--space-md);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  text-align: center;
  font-size: 15px;
}

.lightbox__error[hidden] {
  display: none;
}

.lightbox__original {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.16);
  transition: background 0.15s ease;
}

.lightbox__original:hover {
  background: rgba(255, 255, 255, 0.26);
  text-decoration: none;
}

/* Кнопки неотправленного фото — тот же вид, что «Открыть оригинал». */
.lightbox__actions {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
}

.lightbox__actions[hidden],
.lightbox__action[hidden] {
  display: none;
}

.lightbox__action {
  min-height: 40px;
  padding: 9px 18px;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}

.lightbox__action:hover {
  background: rgba(255, 255, 255, 0.26);
}

.lightbox__action--danger {
  background: rgba(198, 40, 40, 0.55);
}

.lightbox__action--danger:hover {
  background: rgba(198, 40, 40, 0.75);
}

.lightbox__close {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 2;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.lightbox__close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Плитка-кнопка предпросмотра: сбрасываем стили кнопки */
button.photo-tile {
  border: none;
  padding: 0;
  cursor: zoom-in;
  display: block;
  width: 100%;
  font: inherit;
}

/* Чип привязки расхождения к слою/паллете (п.14) */
.chip-unit {
  color: var(--color-primary);
  font-size: 11px;
  padding: 3px 9px;
}

/* Этап, на котором нашли расхождение. Нужен только там, где строка показана не
   на своей вкладке: расхождение приёмки, привязанное к паллете, попадает в
   карточку на вкладке «Выгрузка». */
.chip-stage {
  color: var(--status-awt);
  font-size: 11px;
  padding: 3px 9px;
}

/* =========================================================
   Тёмная тема — дополнения
   ========================================================= */

/* 22%/--color-primary текстом (#2997ff) давали 4.05:1 текста к фону и 1.39:1
   фона к холсту — на «Скачать» и «Изменить данные» рендером в
   браузере подтверждён именно этот разрыв, обе кнопки читались плохо. Текст
   светлее (#64b5ff — тот же тон, что уже используется в файле для другого
   места, где нужен акцент читаемый именно НА тёмном) даёт 5.0:1, фон немного
   плотнее (28%) — 1.55:1 к холсту, граница пилюли всё равно видна. */
html[data-theme="dark"] .btn-soft {
  background: color-mix(in srgb, var(--color-primary) 28%, transparent);
  color: #64b5ff;
}

/* Без переопределения :hover ниже базовый .btn-soft:hover (20%, см. выше)
   оказался бы бледнее нового базового состояния (28%) — наведение делало бы
   кнопку светлее вместо плотнее. 32% держит те же ≥4.5:1 (4.65:1). */
html[data-theme="dark"] .btn-soft:hover {
  background: color-mix(in srgb, var(--color-primary) 32%, transparent);
}

/* --color-primary в тёмной теме светлее, чем в светлой, поэтому и чёрного
   для тех же 4.5:1 под белым текстом нужно больше. */
html[data-theme="dark"] .tab.is-active .tab-badge {
  background: color-mix(in srgb, black 20%, var(--color-primary));
}

html[data-theme="dark"] .confirm-modal__cancel {
  background: rgba(255, 255, 255, 0.12);
  color: var(--color-ink);
}

html[data-theme="dark"] .btn-add.is-open {
  background: rgba(255, 255, 255, 0.1);
}

/* Статусы в тёмной теме — фон плотнее, чтобы чип не сливался с карточкой.
   Рамка отдельно не переопределяется: она везде currentColor (см. .chip). */
@supports (background: color-mix(in srgb, red 10%, transparent)) {
  html[data-theme="dark"] .chip {
    background: color-mix(in srgb, currentColor 22%, var(--color-canvas));
  }
}

/* =========================================================
   Повторное уведомление: вместо дубля коротко пульсируем уже
   висящее сообщение, чтобы было заметно, что событие повторилось
   ========================================================= */

@keyframes alert-pulse {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.025); }
  100% { transform: scale(1); }
}

.alert--pulse {
  animation: alert-pulse 0.32s cubic-bezier(0.32, 0.72, 0, 1);
}

@media (prefers-reduced-motion: reduce) {
  .alert--pulse {
    animation: none;
  }
}

/* =========================================================
   Стилизованный выбор файла (.file-drop)
   Нативный input[type=file] скрыт, кликабельная область — label
   ========================================================= */

.file-drop {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 12px 14px;
  border: 1.5px dashed var(--color-hairline);
  border-radius: var(--radius-md);
  background: var(--color-canvas);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
  min-height: 66px;
}

/* Идёт загрузка: повторный выбор файла в это время не принимается (см.
   form.dataset.busy в app.js), и по виду контрола это должно быть понятно. */
.file-drop.is-uploading {
  opacity: 0.65;
  cursor: progress;
  pointer-events: none;
}

.file-drop:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 5%, transparent);
}

/* Клавиатурная доступность: подсвечиваем обёртку при фокусе скрытого input */
.file-drop:focus-within {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-primary-focus);
  outline-offset: 2px;
}

.file-drop__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  color: var(--color-primary);
  flex-shrink: 0;
}

.file-drop__icon svg {
  width: 19px;
  height: 19px;
}

.file-drop__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.file-drop__label {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-ink);
}

.file-drop__hint {
  font-size: 12px;
  color: var(--color-ink-muted-48);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-drop__preview {
  flex-shrink: 0;
}

.file-drop__preview img {
  width: 42px;
  height: 42px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-hairline);
}

.file-drop.is-filled {
  border-style: solid;
  border-color: color-mix(in srgb, var(--status-rcl) 45%, transparent);
  background: color-mix(in srgb, var(--status-rcl) 6%, transparent);
}

.file-drop.is-filled .file-drop__icon {
  background: color-mix(in srgb, var(--status-rcl) 15%, transparent);
  color: var(--status-rcl);
}

/* Django рисует для ClearableFileInput ссылку «Currently/Изменить/Очистить» —
   внутри нашего контрола она не нужна */
.file-drop a,
.file-drop br {
  display: none;
}

html[data-theme="dark"] .file-drop {
  background: var(--color-surface-pearl);
}

/* =========================================================
   Заблокированная кнопка сохранения (живая валидация формы)
   ========================================================= */

.btn[disabled],
.btn.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
  filter: grayscale(0.4);
}

.btn[disabled]:active,
.btn.is-disabled:active {
  transform: none;
}

/* =========================================================
   Мультизагрузка: контрол без превью справа (миниатюры — ниже)
   ========================================================= */

.file-drop--multi {
  min-height: 58px;
}

.file-drop--multi + .file-preview-grid:not(:empty) {
  margin-top: var(--space-xs);
}

/* =========================================================
   Пустой слой/паллета — без фотофиксации
   ========================================================= */

.chip-warn {
  color: var(--status-und);
}

.unit-card--empty {
  border-color: color-mix(in srgb, var(--status-und) 45%, transparent);
  background: color-mix(in srgb, var(--status-und) 6%, var(--color-canvas-parchment));
}

.unit-card__empty-badge {
  margin-left: var(--space-xs);
}

html[data-theme="dark"] .unit-card--empty {
  background: color-mix(in srgb, var(--status-und) 12%, var(--color-surface-pearl));
}

/* =========================================================
   Ряды фильтров дашборда
   Внутри ряда — ИЛИ, между рядами — И (Ctrl+клик = добавить к выбору)
   ========================================================= */

.filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg) 0 var(--space-md);
}

.filter-row {
  display: flex;
  gap: var(--space-xs);
  flex-wrap: wrap;
  align-items: center;
  padding: 0;
}

.filter-row__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.2px;
  text-transform: uppercase;
  color: var(--color-ink-muted-48);
  min-width: 112px;
}

.filter-row--reset {
  margin-top: var(--space-xxs);
  padding-left: 112px;
  gap: var(--space-sm);
}

.filter-chip {
  background: var(--color-canvas);
  color: var(--color-ink);
  font-family: var(--font-text);
  font-size: 14px;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-hairline);
  cursor: pointer;
  user-select: none;
}

.filter-chip--today {
  font-weight: 700;
}

.filter-chip.is-active {
  background: var(--color-primary);
  border: 2px solid var(--color-primary);
  color: #fff;
}

@media (max-width: 720px) {
  .filter-row__label {
    min-width: 100%;
    margin-bottom: 2px;
  }
  .filter-row--reset {
    padding-left: 0;
  }
}

/* =========================================================
   Пагинация
   ========================================================= */

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  margin-top: var(--space-xl);
}

.pagination__btn {
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  background: var(--color-canvas);
  border: 1px solid var(--color-hairline);
  color: var(--color-primary);
  font-size: 14px;
  font-weight: 600;
}

.pagination__btn:hover {
  text-decoration: none;
  background: color-mix(in srgb, var(--color-primary) 8%, var(--color-canvas));
}

.pagination__btn.is-disabled {
  color: var(--color-ink-muted-48);
  opacity: 0.5;
  cursor: not-allowed;
}

.pagination__status {
  font-size: 14px;
  color: var(--color-ink-muted-48);
}

/* =========================================================
   Фото прибытия: сетка карточек с автозагрузкой
   ========================================================= */

.arrival-photos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.arrival-photo__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 66px;
  border: 1.5px dashed var(--color-hairline);
  border-radius: var(--radius-md);
  color: var(--color-ink-muted-48);
}

.arrival-form {
  padding-top: var(--space-xs);
}

/* =========================================================
   Ошибки полей формы

   Раньше ошибки валидации уходили только в общий блок сообщений наверху —
   было видно, что форма не сохранилась, но не видно, какое поле чинить.
   ========================================================= */

.field-errors {
  list-style: none;
  margin: var(--space-xxs) 0 0;
  padding: 0;
  font-size: 13px;
  line-height: 1.35;
  color: var(--status-flag);
}

.field-errors li + li {
  margin-top: 2px;
}

.field-errors + .type-fine-print {
  margin-top: var(--space-xxs);
}

/* =========================================================
   Фото, сохранённые после неудачной отправки формы
   ========================================================= */

.pending-photos-note {
  margin: 0 0 var(--space-md);
  padding: var(--space-xs) var(--space-sm);
  font-size: 13px;
  line-height: 1.4;
  border-radius: var(--radius-sm);
  color: var(--status-rcl);
  background: color-mix(in srgb, var(--status-rcl) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--status-rcl) 35%, transparent);
}

/* =========================================================
   Чекбокс подтверждения (закрытие приёмки)

   Настоящее поле формы, а не скрытый input: без JS модалка не срабатывает,
   и приёмку иначе нельзя было закрыть вообще.
   ========================================================= */

.confirm-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  margin-bottom: var(--space-sm);
  font-size: 15px;
  line-height: 1.4;
  color: var(--color-ink-muted-80);
  cursor: pointer;
}

.confirm-check input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  accent-color: var(--color-primary);
}

/* Чекбокс в обычном поле формы (например, «Пломбы нет в документах»).
   Тап-цель того же размера, что у чекбокса подтверждения. */
.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  font-size: 15px;
  line-height: 1.4;
  color: var(--color-ink);
  cursor: pointer;
  padding: 6px 0;
}

.checkbox-row input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  accent-color: var(--color-primary);
}

/* Целостность пломбы, когда форма уже недоступна и статус показан чипом:
   «НЕ НАРУШЕНА» — зелёным, остальное — оранжевым, как у кнопок выбора. */
.chip-seal-status-intact { color: var(--status-rcl); }
.chip-seal-status-alert { color: var(--status-und); }

/* =========================================================
   Компактная зона выбора файла (под фото прибытия)
   ========================================================= */

.file-drop--slim {
  min-height: 0;
  padding: var(--space-xxs) var(--space-xs);
  font-size: 13px;
}

.file-drop--slim svg {
  width: 16px;
  height: 16px;
}

/* =========================================================
   Атрибут hidden должен побеждать компонентные display

   Правило браузера `[hidden] { display: none }` имеет нулевую
   специфичность, поэтому любой компонент с собственным display его
   перебивает: `.chip` объявлен как inline-flex, и бейдж «без фото»
   с атрибутом hidden продолжал бы показываться. В файле уже были точечные
   заплатки (.confirm-modal[hidden], .lightbox[hidden]) — здесь общее
   правило вместо них.
   ========================================================= */

[hidden] {
  display: none !important;
}

/* =========================================================
   Чек-лист «чего не хватает, чтобы начать выгрузку»

   Фото прибытия, пломбу и отметку о водителе после вскрытия борта уже не
   собрать, поэтому кнопка блокируется, а рядом перечислено недостающее.
   ========================================================= */

.gate-checklist {
  margin-bottom: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  color: var(--status-flag);
  background: color-mix(in srgb, var(--status-flag) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--status-flag) 30%, transparent);
}

.gate-checklist__title {
  margin: 0 0 var(--space-xxs);
  font-size: 14px;
  font-weight: 600;
}

.gate-checklist__list {
  margin: 0;
  padding-left: 1.2em;
  font-size: 14px;
  line-height: 1.5;
}

/* =========================================================
   Заблокированное поле номера пломбы
   ========================================================= */

.field.is-locked label {
  opacity: 0.5;
}

.field-input:disabled,
.field-select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--color-canvas-parchment);
}

/* =========================================================
   Стопка фото прибытия

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

.arrival-photo__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-xs);
  margin-bottom: var(--space-xxs);
}

.arrival-photo__label {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-ink-muted-80);
}

.arrival-photo__count {
  font-size: 12px;
  color: var(--color-ink-muted-48);
  white-space: nowrap;
}

.photo-stack {
  position: relative;
  display: block;
  width: 104px;
  height: 104px;
  /* Запас снизу под разъехавшиеся слои и счётчик. Сверху запас не нужен:
     слои уходят вниз-вправо и на подпись ракурса не наезжают. */
  margin: 0 0 26px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* Бейдж-счётчик внутри — position:absolute с z-index:20 (см. ниже), а у
     .photo-stack самого по себе своего z-index нет: position:relative БЕЗ
     z-index не заводит новый контекст наложения, и бейдж сравнивался по
     z-index не со своими соседями внутри стопки, а сразу с прилипающим
     переключателем вкладок (.tabs-row, z-index: 15) в общем контексте
     страницы — и как 20 > 15, бейдж оказывался поверх .tabs-row при
     прокрутке, хотя по смыслу должен быть виден только внутри своей
     карточки. isolation: isolate заводит границу контекста наложения прямо
     здесь: z-index:20 бейджа остаётся сравниваться только с тем, что внутри
     самой стопки, наружу больше не «пробивает». */
  isolation: isolate;
}

.photo-stack__layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-canvas);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.18);
  /* Нижние слои уходят ВНИЗ-ВПРАВО и слегка поворачиваются.
     Раньше вектор был вверх-влево, и на наведении сдвиг (16px) перекрывал
     отступ до подписи (8px) — стопка наползала на название ракурса. */
  transform:
    translate(calc(var(--layer) * 5px), calc(var(--layer) * 5px))
    rotate(calc(var(--layer) * 3deg));
  z-index: calc(10 - var(--layer));
  transition: transform 0.18s ease;
}

.photo-stack__layer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Раскрытая стопка «разъезжается» заметнее. */
.photo-stack.is-open .photo-stack__layer {
  transform:
    translate(calc(var(--layer) * 8px), calc(var(--layer) * 8px))
    rotate(calc(var(--layer) * 5deg));
}

/* Наведение — только там, где есть настоящий курсор, и слабее раскрытия.
   Раньше наведение разъезжало стопку так же, как раскрытие, а на телефоне
   :hover после касания «залипает» на элементе: второе нажатие закрывало
   кадры и снимало is-open, но стопка оставалась разъехавшейся. С мышью
   после закрытия стопка теперь тоже заметно съезжается обратно. */
@media (hover: hover) and (pointer: fine) {
  .photo-stack:not(.is-open):hover .photo-stack__layer {
    transform:
      translate(calc(var(--layer) * 6.5px), calc(var(--layer) * 6.5px))
      rotate(calc(var(--layer) * 4deg));
  }
}

.photo-stack__badge {
  position: absolute;
  right: -6px;
  bottom: -6px;
  z-index: 20;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-canvas);
  font-size: 12px;
  font-weight: 600;
  line-height: 24px;
  text-align: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

/* Один снимок ракурса: та же плитка, что у стопки, но без нижних слоёв —
   открывается сразу в лайтбоксе. Обёртка шириной с плитку, чтобы крестик
   удаления встал в её угол, а не в угол всей колонки. Нижний отступ плитки
   (.photo-stack) оставлен прежним: «Добавить ещё» под одиночным кадром и под
   стопкой в соседней колонке стоят на одной линии. */
.photo-stack-single {
  width: 104px;
}

.arrival-photo__expanded {
  margin-top: var(--space-sm);
}

.photo-grid--compact {
  grid-template-columns: repeat(auto-fill, minmax(min(84px, 100%), 1fr));
  gap: var(--space-xs);
}

/* Ракурс прибытия — компактная карточка. Подпись сверху на всю ширину, под
   ней в один ряд: превью (стопка или одиночный кадр) и кнопки добавления.
   Нет фото — кнопки на всю ширину. Раскрытая стопка и плитки снимаемых
   камерой кадров (.js-camera-holder) — отдельными рядами ниже. */
.arrival-photo {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  /* 16px: нижние слои стопки уходят вправо на 10px с поворотом и иначе
     касаются кнопки. */
  column-gap: 16px;
  row-gap: var(--space-xs);
  align-items: start;
  min-width: 0;
  padding: 10px 12px;
  /* Внутри белой карточки «Данные о прибытии» — серая подложка с рамкой:
     так три ракурса читаются отдельными блоками. */
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: var(--color-canvas-parchment);
}

.arrival-photo > .arrival-photo__head {
  grid-column: 1 / -1;
  margin-bottom: 0;
}

.arrival-photo > .photo-stack,
.arrival-photo > .photo-stack-single {
  grid-column: 1;
  grid-row: 2;
}

.arrival-photo > .arrival-photo__add {
  grid-column: 2;
  grid-row: 2;
}

.arrival-photo > .arrival-photo__add--alone,
.arrival-photo > .arrival-photo__empty,
.arrival-photo > .js-camera-holder {
  grid-column: 1 / -1;
}

.arrival-photo > .arrival-photo__expanded {
  grid-column: 1 / -1;
  grid-row: 3;
  margin-top: 0;
}

/* Превью в карточке — 64px вместо 104px; запас снизу — только под сдвиг
   нижних слоёв и счётчик. */
.arrival-photo .photo-stack {
  width: 64px;
  height: 64px;
  margin: 0 0 12px;
}

.arrival-photo .photo-stack-single {
  width: 64px;
}

.arrival-photo .photo-stack__badge {
  min-width: 22px;
  height: 22px;
  line-height: 22px;
  font-size: 11px;
}

.arrival-photo .photo-stack-single .photo-delete-btn {
  width: 24px;
  height: 24px;
}

/* Кнопки в один ряд: «Сделать фото» растягивается, галерея — квадрат 44px
   (тап-цель не меньше 44px, см. tests/js «тап-цели»). */
.arrival-photo__add {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-width: 0;
}

.arrival-photo__add > .camera-open {
  flex: 1 1 auto;
  min-width: 0;
  gap: 6px;
  padding: 8px 12px;
  /* 13px: в три колонки на компьютере карточка ~300px, и с 14-15px подпись
     обрезалась многоточием. */
  font-size: 13px;
  white-space: nowrap;
}

.arrival-photo__add > .camera-open svg {
  width: 18px;
  height: 18px;
}

.arrival-photo__add > .camera-open span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.arrival-photo__gallery {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 2px 10px 2px 3px;
  border-radius: var(--radius-pill);
}

.arrival-photo__gallery .file-drop__icon {
  width: 38px;
  height: 38px;
  border-radius: 50%;
}

.arrival-photo__gallery .file-drop__icon svg {
  width: 20px;
  height: 20px;
}

/* С камерой галерея — запасной путь: квадратная кнопка с иконкой, подпись
   остаётся для скринридера. Без камеры подпись видна — это главная кнопка. */
.has-camera .arrival-photo__gallery {
  width: 44px;
  height: 44px;
  padding: 0;
  justify-content: center;
}

.has-camera .arrival-photo__gallery .file-drop__body {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

html:not(.has-camera) .arrival-photo__gallery {
  flex: 1 1 auto;
}

/* Загрузка из галереи при квадратной кнопке: подпись «Загрузка…» скрыта
   (она для скринридера), и полупрозрачная кнопка выглядела просто
   неактивной. Вместо иконки — крутящееся кольцо. */
.has-camera .arrival-photo__gallery.is-uploading {
  opacity: 1;
}

.has-camera .arrival-photo__gallery.is-uploading .file-drop__icon svg {
  visibility: hidden;
}

.has-camera .arrival-photo__gallery.is-uploading .file-drop__icon {
  position: relative;
}

.has-camera .arrival-photo__gallery.is-uploading .file-drop__icon::after {
  content: "";
  position: absolute;
  inset: 9px;
  border: 2px solid var(--color-primary);
  border-right-color: transparent;
  border-radius: 50%;
  animation: lightbox-spin 0.8s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .has-camera .arrival-photo__gallery.is-uploading .file-drop__icon::after {
    animation: none;
    border-right-color: var(--color-primary);
    opacity: 0.6;
  }
}

html[data-theme="dark"] .photo-stack__layer {
  border-color: var(--color-surface-pearl);
}

html[data-theme="dark"] .photo-stack__badge {
  background: var(--color-canvas);
  color: var(--color-ink);
}

/* =========================================================
   Узкие экраны

   Проверено рендером в браузере: до этих правок страница выгрузки уезжала
   по горизонтали на 30px при ширине 360 и на 70px при 320. Виноваты были
   не сетки — они и раньше сворачивались в одну колонку, — а три строки,
   которые не переносились: SSCC в шапке паллеты, действия в шапке сайта и
   правая часть строки расхождения. Ниже лечатся причины, а не симптом:
   `overflow-x: hidden` только спрятал бы уехавший блок.
   ========================================================= */

/* Треки сеток (.shipment-grid, .arrival-photos, .photo-grid) — через
   min(…, 100%) прямо в базовых правилах; переносы и min-width: 0 у
   .unit-sscc-form и элементов шапки — тоже там, с объяснением. */

.unit-sscc-form .field-input--inline {
  flex: 1 1 130px;
  min-width: 0;
}

/* Поле SSCC в карточке растягивается внутри своей обёртки-комбобокса. */
.unit-sscc-form .sscc-combo {
  flex: 1 1 160px;
  min-width: 0;
}

.unit-sscc-form .sscc-combo .field-input--inline {
  width: 100%;
}

/* Шапка сайта: имя пользователя сжимается с многоточием, а не выталкивает
   бейдж роли или переключатель темы за край (min-width: 0 у контейнеров —
   в их базовых правилах). Сжимается имя, бейдж роли остаётся целым: общий
   overflow на контейнере обрезал и бейдж. */
.global-nav__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.global-nav__user .role-badge {
  flex: 0 0 auto;
}

/* «Склад · Выгрузка и приёмка» на 320–360px не помещается рядом с именем и
   кнопками — ссылка внутри `min-width: 0`-элемента переносилась на три
   строки и вылезала за фиксированную высоту шапки (44px) поверх подшапки
   под ней, а не сжималась. Та же беда, что с `.global-nav__name` ниже, но
   для заголовка её раньше не лечили.

   :not(.brand-chip) — это правило старше самого чипа: тогда в этом месте был
   обычный текстовый заголовок переменной длины. `display: block` здесь имеет
   более высокую специфичность (класс+тег), чем `.brand-chip { display:
   inline-flex }` (просто класс), и побеждал независимо от порядка в файле —
   flex-раскладка чипа отключалась целиком, gap между «✽», «|» и «Dispatxh»
   переставал работать, и на 100% зуме они визуально слипались. Сегодня в
   этом месте всегда чип с фиксированным коротким текстом, а не длинный
   заголовок, поэтому исключение ничего не отнимает у исходной задачи —
   просто не даёт ей задевать то, для чего она не писалась. */
.global-nav__brand a:not(.brand-chip) {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Статусы и кнопка «Скачать…» на узком экране — в столбик, а не в строку:
   два крупных чипа и текст кнопки рядом с заголовком не помещаются и раньше
   переносились некрасиво вперемешку. Сама подшапка (.sub-nav) больше не
   несёт статусов и кнопки — там остался только заголовок, ему свой перенос
   строк не нужен, overflow-wrap на .sub-nav__title (см. базовое правило
   выше) и так не даёт длинному коду/поставщику вылезти за край. */
@media (max-width: 640px) {
  .status-bar .container-wide {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
  }

  .status-bar__chips {
    width: 100%;
  }
}

/* Строка расхождения: количество и кнопки переносятся под описание.

   Перенос держится на базовой ширине описания (flex-basis 14rem). Раньше
   колонка описания была `flex:1` inline-стилем — это базовая ширина 0, и
   перенос не срабатывал никогда: на 375px правая часть занимала 222px, а
   описанию оставалось 42px — по слову на строку, длинные слова наезжали на
   «кол-во». Теперь, если рядом с кнопками описанию тесно, кнопки уходят
   под него отдельной строкой (flex-wrap — в базовом .discrepancy-row__main). */
.discrepancy-row__body {
  flex: 1 1 14rem;
  min-width: 0;
  /* Артикулы вида ART-1234567-СТУЛ-ОФИСНЫЙ-ЧЁРНЫЙ — одно «слово»: без
     этого оно вылезало за колонку. */
  overflow-wrap: anywhere;
}

.discrepancy-row__side {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
  min-width: 0;
}

/* Чипы и пункты метаданных — отдельными кусками с общим зазором. Пункт не
   рвётся внутри («03.10.2026 01:19» целиком), разделитель «·» висит в конце
   пункта, а не отдельным текстом между ними — поэтому строку он не начинает
   и сам по себе на строке не остаётся. */
.discrepancy-row__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  margin-top: 2px;
  color: var(--color-ink-muted-48);
}

.discrepancy-row__facts {
  display: inline;
}

.discrepancy-row__fact {
  white-space: nowrap;
}

.discrepancy-row__fact:not(:last-child)::after {
  content: " · ";
}

.discrepancy-row__note {
  margin-top: 4px;
}

.discrepancy-photos {
  max-width: 320px;
}

/* «Добавить фото» под строкой расхождения: кнопка камеры и выбор файла —
   в одну строку, переносятся на узком экране. */
.disc-photo-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

/* Корзины нет: к единице привязано расхождение приёмки (см.
   UnloadingUnit.has_receiving_discrepancies). */
.unit-card__locked-note {
  color: var(--color-ink-muted-48);
}

@media (max-width: 640px) {
  .discrepancy-row__main {
    gap: var(--space-xs);
  }

  /* На телефоне правая часть — всегда своя строка под описанием:
     количество слева в одну строку («кол-во 12»), кнопки справа. */
  .discrepancy-row__side {
    flex: 1 1 100%;
  }

  .discrepancy-row__qty {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-right: auto;
  }

  /* Фото расхождения и единицы — миниатюрами по три-четыре в ряд. Сетка
     с колонкой от 140px в карточке слоя (281px на 375px) давала одну
     колонку: каждый кадр во всю ширину, три фото — три экрана прокрутки. */
  .discrepancy-photos,
  .unit-card .js-unit-photo-grid,
  .unit-card .js-camera-placeholder-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(84px, 100%), 1fr));
    gap: var(--space-xs);
    max-width: none;
  }

  /* В миниатюре 84–90px бейдж «приёмка» в верхнем левом углу сходился с
     крестиком удаления (34px) в верхнем правом — уводим бейдж вниз. */
  .discrepancy-photos .photo-tile-wrapper__stage-badge {
    top: auto;
    bottom: 6px;
  }
}

/* =========================================================
   Тап-цели на телефоне и на любом «грубом» указателе

   Кнопка-корзина была 23×25px — для склада, где работают в перчатках, это
   мало. Поднимаем до 44px там, где ввод пальцем, и не трогаем мышь на
   десктопе: две ветки условия нужны, потому что узкое окно на десктопе
   тоже стоит считать мобильным.
   ========================================================= */

@media (max-width: 640px), (pointer: coarse) {
  .btn,
  .btn-add {
    min-height: 44px;
  }

  .unit-card__delete {
    min-width: 44px;
    min-height: 44px;
  }

  /* Крестик сидит в углу миниатюры (в компактной сетке она 84px), поэтому
     до 44px не растягиваем — иначе он закроет сам снимок. */
  .photo-delete-btn {
    width: 34px;
    height: 34px;
  }

  .field-input,
  .field-select,
  .field-textarea {
    /* iOS масштабирует страницу, если шрифт поля меньше 16px */
    font-size: 16px;
  }
}

/* =========================================================
   Попап пользователя: строка «Тёмная тема» с переключателем-тумблером

   Раньше кнопка смены темы стояла отдельно в шапке — теперь она строка
   попапа, там же, где «Выйти» (см. base.html). Обе строки растягиваются на
   всю ширину попапа и по высоте совпадают с прежней кнопкой «Выйти».
   ========================================================= */
.user-menu__dropdown form,
.user-menu__row {
  width: 100%;
}

.user-menu__dropdown .btn-dark-utility {
  width: 100%;
  justify-content: flex-start;
}

.user-menu__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 15px;
  color: #ffffff;
  font-family: var(--font-text);
  font-size: 14px;
  letter-spacing: -0.224px;
  cursor: pointer;
}

.user-menu__row:hover {
  background: rgba(255, 255, 255, 0.14);
}

.user-menu__divider {
  height: 1px;
  margin: 4px 6px;
  background: rgba(255, 255, 255, 0.14);
}

.toggle-switch {
  position: relative;
  flex-shrink: 0;
  width: 34px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.24);
  transition: background 0.15s ease;
}

.toggle-switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #ffffff;
  transition: transform 0.15s ease;
}

.user-menu__row.is-on .toggle-switch {
  background: var(--color-primary-on-dark);
}

.user-menu__row.is-on .toggle-switch__thumb {
  transform: translateX(14px);
}

/* Мобильные экраны: сокращаем боковые отступы (снаружи и внутри карточек),
   чтобы контенту оставалось больше места — раньше по бокам оставалось много
   свободного места, а в паре с багом двойного вложения .container внутри
   .container-wide (см. правило выше) карточки на странице поставки были ещё
   уже. Брейкпоинт расширен с 420 до 640px: сама разница отступов не зависит
   от точной ширины экрана, а на 421–640px раньше отступ оставался прежним
   (24px), хотя экран уже мобильный. */
@media (max-width: 640px) {
  .container,
  .container-wide {
    padding: 0 var(--space-sm);
  }

  .card {
    padding: 16px;
  }
}

@media (max-width: 420px) {
  .global-nav__name {
    /* Имя целиком не нужно: важнее, чтобы бейдж роли и стрелка-подсказка
       рядом с ним не обрезались */
    max-width: 28vw;
  }
}


/* =========================================================
   Приходные документы поставки

   Строка «номер · название · объём · ×» в форме оператора и чипы документов
   в блоке «Данные по поставке». На узком экране строка складывается в два
   этажа (номер и ×, ниже название и объём): в одну линию в 320–390px не
   помещается ни одно из трёх полей.
   ========================================================= */

.receipts {
  grid-column: 1 / -1;
  display: grid;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}

.receipts__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.receipts__title {
  font-weight: 600;
  font-size: 15px;
}

.receipts__list {
  display: grid;
  gap: var(--space-sm);
}

.receipts .btn-add {
  justify-self: start;
}

/* Приёмка закрыта: документы списком, без полей. */
.receipts__readonly {
  margin: 0;
  padding-left: 1.2em;
  display: grid;
  gap: 4px;
  overflow-wrap: anywhere;
}

.receipt-row {
  display: grid;
  grid-template-columns: 1fr 110px;
  grid-template-areas:
    "num  act"
    "name vol"
    "note note"
    "errs errs";
  gap: 8px;
  align-items: start;
}

.receipt-row__num { grid-area: num; }
.receipt-row__name { grid-area: name; }
.receipt-row__vol { grid-area: vol; }
.receipt-row__act { grid-area: act; justify-self: end; }
.receipt-row__note { grid-area: note; margin: -2px 0 0; }
.receipt-row__errors { grid-area: errs; }

.receipt-row .field-input {
  margin: 0;
}

.receipt-row__vol .field-input {
  text-align: right;
}

@media (min-width: 641px) {
  .receipt-row {
    grid-template-columns: minmax(140px, 180px) 1fr 110px 44px;
    grid-template-areas:
      "num  name vol act"
      "note note note note"
      "errs errs errs errs";
  }
}

.receipt-row__del {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-ink) 7%, transparent);
  color: var(--color-ink-muted-48);
  font: 400 22px/1 var(--font-text);
  cursor: pointer;
}

.receipt-row__del:hover {
  background: color-mix(in srgb, var(--status-flag) 14%, transparent);
  color: var(--status-flag);
}

.receipt-row__del:focus-within,
.receipt-row__del:focus-visible {
  outline: 2px solid var(--color-primary-focus);
  outline-offset: 1px;
}

.receipt-row__del.is-locked {
  opacity: 0.35;
  cursor: not-allowed;
}

.receipt-row__del.is-locked:hover {
  background: color-mix(in srgb, var(--color-ink) 7%, transparent);
  color: var(--color-ink-muted-48);
}

/* Строка помечена на удаление: поля гаснут, кнопка становится красной. Само
   удаление произойдёт при сохранении, и до него его можно отменить. */
.receipt-row.is-removed .field-input {
  text-decoration: line-through;
  opacity: 0.45;
}

.receipt-row.is-removed .receipt-row__del {
  background: var(--status-flag-solid);
  color: #fff;
}

.receipt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.receipt-chip {
  font: 600 13px var(--font-text);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
  color: var(--color-primary);
  overflow-wrap: anywhere;
}

.receipt-chip small {
  font-weight: 400;
  color: var(--color-ink-muted-80);
}

/* =========================================================
   Распределение расхождения по приходным документам

   Блок «документ — количество» в формах расхождения и в «Указать приход».
   Счётчик «N из M» зелёный, когда сумма равна количеству, и оранжевый, пока
   нет (считает app.js; окончательно решает сервер).
   ========================================================= */

.alloc {
  display: grid;
  gap: 10px;
  border: 1px solid var(--color-hairline);
  border-radius: 14px;
  padding: 12px 14px;
  background: var(--color-surface-pearl);
}

.alloc__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-xs);
  flex-wrap: wrap;
}

.alloc__title {
  font-weight: 600;
  font-size: 15px;
}

.alloc__sum {
  font: 600 13px var(--font-text);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
}

.alloc__sum:empty {
  display: none;
}

.alloc__sum.is-warn {
  background: color-mix(in srgb, var(--status-und) 16%, var(--color-canvas));
  color: var(--status-und);
}

.alloc__sum.is-ok {
  background: color-mix(in srgb, var(--status-rcl) 16%, var(--color-canvas));
  color: var(--status-rcl);
}

.alloc__row {
  display: grid;
  grid-template-columns: 1fr 96px;
  grid-template-areas:
    "label qty"
    "rest  qty";
  gap: 2px 10px;
  align-items: center;
}

.alloc__label {
  grid-area: label;
  display: grid;
  gap: 1px;
  margin: 0;
  min-width: 0;
}

.alloc__code {
  font-weight: 600;
  font-size: 15px;
  overflow-wrap: anywhere;
}

.alloc__name {
  font-size: 13px;
  color: var(--color-ink-muted-48);
  overflow-wrap: anywhere;
}

.alloc__row .alloc__qty {
  grid-area: qty;
  margin: 0;
  text-align: right;
}

.alloc__rest {
  grid-area: rest;
  justify-self: start;
  border: none;
  background: none;
  padding: 2px 0;
  color: var(--color-primary);
  font: 600 13px var(--font-text);
  cursor: pointer;
}

.alloc__rest:hover {
  text-decoration: underline;
}

@media (max-width: 640px), (pointer: coarse) {
  .alloc__rest {
    min-height: 32px;
  }
}

/* Единственный документ: поле только для чтения, чтобы было видно, к чему
   относится расхождение, но не было ощущения, что здесь есть выбор. */
.locked-field {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-ink) 5%, transparent);
  font-size: 15px;
  overflow-wrap: anywhere;
}

.locked-field svg {
  flex: none;
  color: var(--color-ink-muted-48);
}

/* Строка «Приход: …» под описанием расхождения. */
.alloc-line {
  margin-top: 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}

.chip-need {
  background: color-mix(in srgb, var(--status-und) 16%, var(--color-canvas));
  color: var(--status-und);
  border-color: var(--status-und);
}

.receiving-gate .gate-checklist {
  margin-bottom: var(--space-sm);
}

/* =========================================================
   Камера приложения (partials/camera.html, js/camera.js)

   Кнопка «Сделать фото» скрыта, пока camera.js не убедился, что камера в
   браузере есть (класс has-camera на <html>). Без него остаётся обычный
   выбор файла — тот же, что был до камеры.
   ========================================================= */

.camera-open {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 18px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.camera-open svg {
  width: 20px;
  height: 20px;
  flex: none;
}

.has-camera .camera-open {
  display: inline-flex;
}

/* В форме расхождения кнопка стоит над полем выбора фото */
.has-camera .js-multiphoto-form .camera-open:not(.camera-open--tile) {
  margin-bottom: var(--space-xs);
}

/* Плитка в сетке фото слоя/паллеты — рядом с плиткой «Добавить фото» */
.camera-open--tile {
  flex-direction: column;
  gap: 6px;
  aspect-ratio: 1 / 1;
  min-height: 0;
  padding: var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: 11px;
  text-align: center;
}

/* --- Полноэкранный видоискатель ------------------------------------ */

.camera {
  position: fixed;
  inset: 0;
  z-index: 300; /* выше лайтбокса (200) */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  background: #000;
  color: #fff;
  /* Свайпы и щипки внутри камеры не должны двигать страницу под ней */
  touch-action: none;
  overscroll-behavior: contain;
}

html.is-camera-open,
html.is-camera-open body {
  overflow: hidden;
}
/* Android-приложение: видоискатель — камера телефона, и приложение рисует
   его ПОД страницей (camera.js, «Камера Android-приложения»). Поэтому камера
   прозрачная, а всё, кроме неё, — невидимо, но остаётся на месте: после
   закрытия камеры страница та же, с той же прокруткой. */
html.is-camera-open.is-native-camera,
html.is-camera-open.is-native-camera body {
  background: transparent !important;
}

html.is-camera-open.is-native-camera body > :not(.camera) {
  visibility: hidden;
}

.is-native-camera .camera {
  background: transparent;
}

.is-native-camera .camera__video {
  visibility: hidden;
}


/* contain, а не cover: видно ровно то, что попадёт в кадр. При cover края
   кадра обрезались бы в видоискателе, но оставались на снимке. */
.camera__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  /* Цифровой зум — transform: scale() из camera.js, от центра: ровно ту
     область, что обрежет снимок. */
  transform-origin: 50% 50%;
}

.camera__flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.camera__flash.is-on {
  animation: camera-flash 0.22s ease-out;
}

@keyframes camera-flash {
  from { opacity: 0.75; }
  to { opacity: 0; }
}

.camera__top,
.camera__bottom {
  position: relative;
  z-index: 1;
}

.camera__top {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: max(12px, env(safe-area-inset-top)) 12px 12px;
  background: linear-gradient(rgba(0, 0, 0, 0.6), transparent);
}

.camera__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  font-size: 16px;
  text-align: center;
}

.camera__icon-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  cursor: pointer;
}

.camera__icon-btn svg {
  width: 22px;
  height: 22px;
}

/* Пустое место справа от заголовка, пока фонарика нет: заголовок остаётся
   по центру экрана, а не съезжает вправо. */
.camera__top::after {
  content: "";
  flex: none;
  width: 48px;
}

.camera__top:has(#camera-torch:not([hidden]))::after {
  display: none;
}

.camera__icon-btn[aria-pressed="true"] {
  background: #ffd23f;
  color: #000;
}

.camera__status {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  width: min(360px, calc(100% - 32px));
  transform: translate(-50%, -50%);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  background: rgba(30, 30, 30, 0.94);
  text-align: center;
}

.camera__status-text {
  margin: 0 0 var(--space-sm);
  font-size: 15px;
  line-height: 1.45;
}

.camera__status-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
}

.camera__pill {
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: var(--radius-pill);
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  /* Подпись не переносится («Заполнить · 3» в одну строку) */
  white-space: nowrap;
}

.camera__pill--light {
  border-color: #fff;
  background: #fff;
  color: #000;
}

.camera__pill:disabled {
  opacity: 0.4;
  cursor: default;
}

.camera__bottom {
  padding: 12px 16px max(16px, env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
}

.camera__strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 6px 2px 12px;
  scrollbar-width: none;
}

.camera__shot {
  position: relative;
  flex: none;
  width: 56px;
  height: 56px;
}

.camera__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
  border: 2px solid #fff;
}

.camera__shot-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: #000;
  color: #fff;
  font-size: 16px;
  line-height: 24px;
  cursor: pointer;
}

/* Три колонки: галерея слева, спуск строго по центру, «Готово» справа */
.camera__controls {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-sm);
}

/* Колонки заданы явно: кнопка галереи прячется, как только снят первый
   кадр, и без явной колонки спуск съезжал бы на её место — влево. */
.camera__gallery,
.camera__left {
  grid-column: 1;
  grid-row: 1;
  justify-self: start;
}

/* .camera__shutter — вторая колонка (см. ниже, вместе с размером). */

.camera__done,
.camera__right {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
}

/* Спуск — крупный (76px): на складе снимают в перчатках */
.camera__shutter {
  grid-column: 2;
  grid-row: 1;
  width: 76px;
  height: 76px;
  padding: 0;
  border: 4px solid #fff;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  position: relative;
}

.camera__shutter::after {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.12s ease;
}

.camera__shutter:active::after,
.camera__shutter.is-busy::after {
  transform: scale(0.86);
}

.camera__shutter:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Без задержки двойного тапа: спуск срабатывает по касанию (camera.js). */
.camera__shutter,
.camera__zoom-btn {
  touch-action: manipulation;
}

/* Кольцо вокруг спуска, пока камера снимает кадр. */
.camera__shutter.is-busy::before {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  border: 3px solid transparent;
  border-top-color: #fff;
  animation: camera-ring 0.7s linear infinite;
}

@keyframes camera-ring {
  to { transform: rotate(360deg); }
}

/* «Держите…» — над спуском, пока кадр не снят. */
.camera__hold {
  position: absolute;
  left: 50%;
  bottom: 210px;
  z-index: 3;
  transform: translateX(-50%);
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  background: rgba(30, 30, 30, 0.94);
  font-size: 15px;
  font-weight: 600;
  pointer-events: none;
}

/* Зум: кнопки 1× / 2× / 4× над лентой кадров. Кнопка текущей ступени
   показывает точное увеличение («2,5×»). */
.camera__zoom {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-bottom: 6px;
}

.camera__zoom[hidden],
.camera__zoom-btn[hidden] {
  display: none;
}

.camera__zoom-btn {
  min-width: 44px;
  height: 44px;
  padding: 0 10px;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.camera__zoom-btn.is-active {
  background: rgba(255, 255, 255, 0.9);
  color: #000;
}

/* Диагностика (?camdebug=1): поверх видоискателя под шапкой. */
.camera__debug {
  position: absolute;
  top: calc(max(12px, env(safe-area-inset-top)) + 56px);
  left: 8px;
  /* Под кнопками камеры (z-index тот же, панель раньше в разметке) и не шире
     нужного: спуск и «Готово» должны оставаться доступными. */
  z-index: 1;
  max-width: min(520px, calc(100% - 16px));
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 8px;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.6);
}

.camera__debug[hidden] {
  display: none;
}

.camera__debug-text {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Короткие сообщения («кадр не получился») — над кнопками */
.camera[data-toast]::after {
  content: attr(data-toast);
  position: absolute;
  left: 50%;
  bottom: 220px;
  z-index: 3;
  max-width: calc(100% - 32px);
  transform: translateX(-50%);
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: rgba(30, 30, 30, 0.94);
  font-size: 14px;
  text-align: center;
}

/* Телефон боком: панель с кнопками — справа столбиком, кадр — на всю
   высоту. Зафиксировать ориентацию из браузера нельзя. */
@media (orientation: landscape) and (max-height: 520px) {
  .camera__bottom {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    gap: 12px;
    padding: 12px max(12px, env(safe-area-inset-right)) 12px 12px;
    background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.7));
  }

  .camera__controls {
    grid-template-columns: auto;
    grid-template-rows: 1fr auto 1fr;
    height: 100%;
  }

  /* Сверху «Готово», в центре спуск, снизу галерея — как в системной
     камере, повёрнутой боком. */
  .camera__gallery,
  .camera__left,
  .camera__shutter,
  .camera__done,
  .camera__right {
    grid-column: 1;
    justify-self: center;
  }

  .camera__done, .camera__right { grid-row: 1; align-self: start; }
  .camera__shutter { grid-row: 2; }
  .camera__gallery, .camera__left { grid-row: 3; align-self: end; }

  .camera__strip {
    flex-direction: column;
    max-height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    padding: 6px;
  }

  .camera__top {
    padding-right: 140px;
  }

  .camera[data-toast]::after {
    bottom: 24px;
  }

  .camera__zoom {
    flex-direction: column;
    margin-bottom: 0;
  }

  .camera__hold {
    bottom: 24px;
  }

  .camera__debug {
    max-width: calc(100% - 300px);
    max-height: calc(100% - 80px);
    overflow-y: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .camera__flash.is-on { animation: none; }
  .camera__shutter.is-busy::before { animation: none; }
}

/* --- Камера: режим «Слои и паллеты» ---------------------------------- */

/* Под шапкой: какую единицу снимаем и кнопки «+ Слой / + Паллета» */
.camera__session {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  margin: 0 12px;
  padding: 8px 8px 8px 14px;
  border-radius: var(--radius-md);
  background: rgba(0, 0, 0, 0.55);
}

.camera__unit {
  flex: 1 1 160px;
  min-width: 0;
  font-weight: 600;
  font-size: 15px;
}

.camera__unit-actions {
  display: flex;
  gap: 8px;
}

/* SSCC текущей единицы: строка во всю ширину плашки, под выбором типа. */
.camera__sscc {
  flex: 1 1 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.camera__sscc[hidden] {
  display: none;
}

.camera__sscc-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.camera__sscc-list:empty {
  display: none;
}

.camera__sscc-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 2px 2px 10px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: var(--radius-pill);
  font-size: 13px;
  overflow-wrap: anywhere;
}

.camera__sscc-chip.is-pending,
.camera__sscc-chip.is-queued {
  border-style: dashed;
  color: rgba(255, 255, 255, 0.8);
}

.camera__sscc-chip.is-error {
  border-color: #ff8a65;
  color: #ffab91;
}

.camera__sscc-remove,
.camera__sscc-retry {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: #fff;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.camera__sscc-row {
  display: flex;
  gap: 8px;
  min-width: 0;
}

.camera__sscc-combo {
  flex: 1 1 auto;
  min-width: 0;
}

.camera__sscc-input {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  /* 16px: iOS иначе увеличивает страницу при фокусе */
  font: inherit;
  font-size: 16px;
}

.camera__sscc-input::placeholder {
  color: rgba(255, 255, 255, 0.6);
}

/* Подсказки в камере — тёмные, как остальной интерфейс съёмки. */
.camera .sscc-combo__list {
  max-height: 40vh;
  background: rgba(30, 30, 30, 0.97);
  border-color: rgba(255, 255, 255, 0.2);
  color: #fff;
}

.camera .sscc-combo__option.is-active,
.camera .sscc-combo__option:hover {
  background: rgba(255, 255, 255, 0.14);
}

.camera .sscc-combo__meta {
  color: rgba(255, 255, 255, 0.65);
}

.camera .sscc-combo__flag {
  color: #ffb74d;
}

/* Тип выбран, а единица ещё не заведена (ждёт первого кадра) */
.camera__pill[aria-pressed="true"] {
  border-color: #fff;
  background: #fff;
  color: #000;
}

/* «Расхождение» — заметная, но не сливается со спуском */
.camera__pill--alert {
  border-color: #ff8a65;
  background: rgba(255, 112, 67, 0.25);
}

/* Режим съёмки расхождения — полоса под шапкой, чтобы было видно, что кадры
   идут уже не к слою, а к расхождению */
.camera__mode {
  position: relative;
  z-index: 1;
  margin: 0 12px;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  background: #c62828;
  font-weight: 600;
  font-size: 15px;
  text-align: center;
}

.camera__mode-title,
.camera__mode-hint {
  display: block;
}

.camera__mode-hint {
  margin-top: 2px;
  font-weight: 400;
  font-size: 14px;
}

.camera__mode-hint[hidden] {
  display: none;
}

/* Статус загрузки кадра единицы на ленте */
.camera__shot.is-queued img,
.camera__shot.is-uploading img {
  opacity: 0.55;
}

.camera__shot.is-uploading::after {
  content: "";
  position: absolute;
  inset: 18px;
  border: 3px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: camera-spin 0.8s linear infinite;
}

.camera__shot.is-done::after {
  content: "✓";
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #2e7d32;
  color: #fff;
  font-size: 13px;
  line-height: 22px;
  text-align: center;
}

.camera__shot.is-error img {
  border-color: #ff5252;
}

.camera__shot-retry {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: rgba(198, 40, 40, 0.55);
  color: #fff;
  font-size: 22px;
  cursor: pointer;
}

@keyframes camera-spin {
  to { transform: rotate(360deg); }
}

/* Шторка с формой расхождения — снизу, поверх видоискателя. Цвета — токены
   страницы: форма та же, что в карточке, и должна читаться так же в обеих
   темах. */
.camera__sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  max-height: 88%;
  border-radius: 18px 18px 0 0;
  background: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.35);
}

.camera__sheet-head {
  padding: 16px 16px 8px;
}

.camera__sheet-title {
  font-weight: 600;
  font-size: 17px;
}

.camera__sheet-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 16px;
  overscroll-behavior: contain;
  touch-action: pan-y;
}

.camera-sheet__form {
  display: grid;
  gap: var(--space-sm);
}

.camera-sheet__form .field-select,
.camera-sheet__form .field-input,
.camera-sheet__form .field-textarea {
  width: 100%;
  /* iOS масштабирует страницу, если шрифт поля меньше 16px */
  font-size: 16px;
}

.camera-sheet__photos.is-warning {
  color: var(--status-flag);
}

.camera__sheet-actions {
  display: flex;
  gap: var(--space-xs);
  justify-content: flex-end;
  padding: 12px 16px max(16px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--color-divider-soft);
}

.camera__sheet-actions .btn {
  min-height: 44px;
}

@media (prefers-reduced-motion: reduce) {
  .camera__shot.is-uploading::after { animation: none; }
}

/* Телефон боком: панель единицы и баннер расхождения — под шапкой слева, а
   не в общем потоке. Иначе они уезжали вниз и ложились под кнопку
   «Расхождение» в правой колонке. Здесь, после базовых правил режима, —
   чтобы их position: relative не перебивал этот. */
@media (orientation: landscape) and (max-height: 520px) {
  .camera__session,
  .camera__mode {
    position: absolute;
    top: 72px;
    left: max(12px, env(safe-area-inset-left));
    right: 160px;
    margin: 0;
  }
}

/* Кадры камеры в форме расхождения (camera.js, .js-staged-photos) — над
   миниатюрами выбранных из галереи, с тем же отступом. */
.camera-staged {
  margin-top: var(--space-xs);
}

/* Кадр камеры на странице, пока он грузится (camera.js, «Миниатюры на
   странице»): та же плитка с кольцом, что у загрузки из галереи. Ждущий
   очереди — кольцо пустое и снимок темнее; упавший — красное кольцо, нажатие
   открывает просмотр с «Повторить» / «Сохранить» / «Удалить», крестик
   убирает кадр. */
.camera-page-tile.is-queued img {
  filter: brightness(0.4);
}

.camera-page-tile__retry {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  background: rgba(198, 40, 40, 0.45);
  color: #fff;
  font-size: 26px;
  cursor: pointer;
}

.camera-page-tile__retry[hidden],
.camera-page-tile .photo-delete-form[hidden] {
  display: none;
}

/* Временная лента кадров ракурса прибытия — над кнопками загрузки. */
.camera-pending {
  margin: var(--space-xs) 0;
}

/* Карточка единицы, которую сервер ещё заводит: снимок уже есть, номера ещё
   нет. Пунктир — не настоящая карточка, действий на ней нет. */
.unit-card--placeholder {
  border-style: dashed;
}

/* =========================================================
   Журнал изменений доказательной части (partials/evidence_log.html)
   ========================================================= */

.evidence-log {
  margin-top: var(--space-lg);
}

.evidence-log__count {
  margin-left: auto;
  color: var(--color-ink-muted-48);
  font-weight: 400;
}

.evidence-log__list {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-sm);
}

.evidence-log__item {
  padding: var(--space-sm) 0 0;
  border-top: 1px solid var(--color-hairline);
}

.evidence-log__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px var(--space-sm);
}

.evidence-log__kind {
  font-weight: 600;
}

.evidence-log__subject,
.evidence-log__meta,
.evidence-log__empty {
  color: var(--color-ink-muted-48);
}

.evidence-log__changes {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.evidence-log__label {
  color: var(--color-ink-muted-80);
}

.evidence-log__old {
  text-decoration: line-through;
  color: var(--color-ink-muted-48);
  overflow-wrap: anywhere;
}

.evidence-log__new {
  overflow-wrap: anywhere;
}

.evidence-log__note {
  margin: 4px 0 0;
  color: var(--color-ink-muted-80);
  overflow-wrap: anywhere;
}

.evidence-log__empty-state {
  margin-top: var(--space-md);
  color: var(--color-ink-muted-48);
}

/* =========================================================
   «Неотправленные фото» — кадры из хранилища браузера, которым на странице
   некуда встать (camera.js, addOrphan): миниатюра, «Куда приложить»,
   «Приложить» и «Удалить».
   ========================================================= */
.orphan-photos {
  border: 1.5px solid var(--status-und);
}

.orphan-photos__list {
  display: grid;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.orphan-photos__item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.orphan-photos__save-all {
  margin-bottom: var(--space-xs);
}

.orphan-photos__save-all[hidden] {
  display: none;
}

.orphan-photos__open {
  flex: none;
  padding: 0;
  border: none;
  background: none;
  line-height: 0;
  cursor: zoom-in;
  border-radius: var(--radius-sm);
}

.orphan-photos__thumb {
  flex: none;
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

.orphan-photos__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  min-width: 0;
}

.orphan-photos__select {
  flex: 1 1 180px;
  min-width: 0;
}

/* «Незавершённое»: новая единица или расхождение — рамкой, с миниатюрами,
   полем (номер / единица) и кнопками. Расхождения новой единицы — внутри
   её рамки, с отступом. */
.orphan-photos__groups {
  display: grid;
  gap: var(--space-sm);
}

.orphan-photos__group {
  display: grid;
  gap: var(--space-xs);
  padding: var(--space-sm);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-sm);
  min-width: 0;
}

.orphan-photos__nested:empty {
  display: none;
}

.orphan-photos__nested {
  display: grid;
  gap: var(--space-xs);
  padding-left: var(--space-sm);
  border-left: 3px solid var(--status-und);
}

.orphan-photos__head {
  margin: 0;
}

.orphan-photos__thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.orphan-photos__thumbs:empty {
  display: none;
}

.orphan-photos__create {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-xs);
}

.orphan-photos__create[hidden],
.orphan-photos__loose-title[hidden] {
  display: none;
}

.orphan-photos__number-label {
  display: grid;
  gap: 4px;
  flex: 1 1 180px;
  min-width: 0;
  font-size: 14px;
}

.orphan-photos__number {
  max-width: 140px;
}

.orphan-photos__hint {
  flex-basis: 100%;
  margin: 0;
}

.orphan-photos__hint:empty {
  display: none;
}

.orphan-photos__hint.is-error {
  color: var(--status-flag);
}

.orphan-photos__loose-title {
  margin: var(--space-sm) 0 0;
  font-size: 15px;
}
