/* ФотоСклад — оболочка кладовщика.
   Всё крупное намеренно: телефон в одной руке, перчатки, блики от ворот. */

/* ── Токены ──────────────────────────────────────────────────────────
   Шкалы объявлены здесь целиком и специально в одном месте. До этого в
   файле жили 16 размеров шрифта и 18 значений отступов: каждый новый
   кусок вёрстки добавлял свой, и «мелкая подпись» на соседних экранах
   различалась на пиксель. Ступеней стало семь и пять — менять размер
   теперь значит переехать на соседнюю ступень, а не выдумать ещё одну. */

:root {
  --bg: #f4f5f7;
  --card: #ffffff;
  --ink: #16181d;
  /* Подписи. Прежний #5b6470 давал 6.0:1 на карточке и 5.5:1 на фоне —
     ниже заявленных в docs/UI.md 7:1. Этот даёт 7.9 и 7.3. Правится одним
     токеном: на нём висят .meta, .grouphead, .winnote, .homes, .adhint. */
  --muted: #4a525c;
  --line: #dfe3e8;
  /* Цветные заливки затемнены до 7:1 и на своих бледных плашках, и под
     белым текстом. Было 4.5–5.3:1 — плашка состояния читалась хуже, чем
     обычная серая подпись рядом с ней. */
  --accent: #084f96;
  --ok: #165b31;
  --warn: #7a4400;
  --stop: #98201a;

  /* Что кладётся НА заливку. Белым по светло-синему в тёмной теме выходило
     2.85:1 — подпись кнопки съёмки была почти не видна. Поэтому у каждой
     заливки свой «верхний» цвет: в светлой теме заливка тёмная и текст
     белый, в тёмной заливка светлая и текст цвета фона приложения. */
  --on-accent: #ffffff;
  --on-ok: #ffffff;
  --on-warn: #ffffff;
  --on-stop: #ffffff;

  /* Пятый смысловой цвет — сборка. Четырёх не хватило: у каждого раздела
     теперь свой цвет, а зелёный, синий, янтарный и красный уже заняты
     приёмкой, отгрузкой, перемещением и разбором. Замеры те же 7:1 —
     9.21 белым по заливке, 7.79 на своей бледной плашке. */
  --pick: #513593;
  --on-pick: #ffffff;
  --pick-bg: #eeeaf8;

  /* Бледные плашки под цветной текст: делают статус видимым боковым зрением. */
  --ok-bg: #e6f4ec;
  --warn-bg: #fbf0e0;
  --stop-bg: #fbe9e7;
  --accent-bg: #e6f0fb;

  /* Шкала кегля. Семь ступеней, каждая со своим смыслом; ничего мельче
     13 px в приложении нет — это правило экрана из docs/UI.md. */
  --fs-note: 13px;    /* уточнение к уже узнанному: время, срок, счётчик */
  --fs-cap: 14px;     /* подпись: пояснение под полем, легенда галочек */
  --fs-body: 15px;    /* второй план строки: контрагент, состояние, мелкая кнопка */
  --fs-lead: 17px;    /* основной текст и подписи кнопок */
  --fs-head: 20px;    /* заголовок экрана, номер документа в списке */
  --fs-title: 24px;   /* номер документа на съёмке, заголовок нижнего листа */
  --fs-num: 30px;     /* число, которое читают с метра */
  /* Вне шкалы намеренно: цифра на грани коробки и код привязки — это не
     текст, а графика, и размер им задаёт рисунок, а не иерархия. */

  /* Шкала отступов. Доводки в 1–3 px оставлены как были: они выравнивают
     знак внутри строки, а не разносят блоки друг от друга. */
  --s1: 4px;            /* волосяной зазор внутри строки */
  --s2: 8px;            /* базовый зазор между целями */
  --s3: 12px;           /* внутренний отступ карточки и панели */
  --s4: 16px;           /* отступ крупного блока */
  --s5: 24px;           /* воздух вокруг заголовка экрана */

  /* Карточка лежит НА фоне, а не нарисована на нём линией. Рамка остаётся —
     на дешёвом экране под бликами от ворот она пропадает последней, — но
     тень отделяет карточку физически. Намеренно на грани видимости: больше
     превратит список в стопку визиток. */
  --lift: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 1px rgba(16, 24, 40, .04);

  /* Три слоя, а не один. Карточка лежит на фоне (--lift), панель висит над
     содержимым и обязана отделяться, когда список под неё уезжает (--raise),
     лист лежит поверх всего (--over). Раньше у нижней панели была только
     волосяная линия, и прокрученный под неё список с ней сливался, а у листа
     над затемнением не было вообще ничего — его край держался на скруглении.

     Тень панели направлена ВВЕРХ (отрицательный сдвиг): свет в приложении
     падает сверху, и панель снизу экрана отбрасывает тень на содержимое, а
     не под себя, за край экрана, где её никто не увидит. */
  --raise: 0 -2px 10px rgba(16, 24, 40, .07);
  --over: 0 -8px 30px rgba(16, 24, 40, .18);

  /* Цвет раздела. У приёмки, отгрузки, перемещения и сборки своя полоса
     сверху и свой оттенок фона списка: кладовщик открывает один и тот же
     экран четырьмя разными дорогами, и до чтения заголовка отличить их
     было нечем. Цвет здесь — второй признак, а не единственный: заголовок
     «Отгрузка / покупателям» никуда не делся.

     Заливки полос держим отдельными токенами, а не берём --ok и --accent:
     в тёмной теме смысловые цвета светлые, и полоса во всю ширину светила
     бы в глаза в цеху без окон. Поэтому --bar-* в каждой теме свои, а
     разделы ниже от темы не зависят вовсе.

     Замеры: белым по любой из светлых заливок 7.9–10.0, светлым по тёмным
     11.2–13.2. Оттенки фона подобраны так, чтобы подпись --muted на них
     держала 7:1 (7.01–7.15 в светлой, 8.4–8.6 в тёмной). */
  --bar-accent: #084f96;
  --bar-ok: #165b31;
  --bar-warn: #7a4400;
  --bar-pick: #513593;
  --bar-stop: #98201a;
  --bar-mut: #3c434c;

  --tint-accent: #eef2f8;
  --tint-ok: #eef5f1;
  --tint-warn: #f7f3ea;
  --tint-pick: #f2f0f8;
  --tint-stop: #fbf3f2;
  --tint-mut: #f1f2f4;

  /* Значения по умолчанию: главный экран носит цвет самого приложения. */
  --brandbar: var(--bar-accent);
  --on-brandbar: #ffffff;
  --tint: var(--bg);

  --tap: 56px;          /* минимальная цель нажатия */
  --gap: var(--s2);     /* зазор между целями, чтобы перчатка не мазала */
  /* Левый край текста. В .scroll уже есть --gap, у карточки 1 px рамки и
     12 px своего отступа — заголовку группы, чтобы встать с текстом
     карточки в одну вертикаль, нужно ровно столько. Замеры до правки на
     одном экране давали 14, 21 и 23 px у соседних блоков. */
  --edge-in: 13px;
  /* Шкала скругления. До этого рядом с одним токеном жили десять одиночных
     значений — 2, 3, 4, 8, 9, 10, 12, 18, 22, 28 px, — и соседние элементы
     скруглялись по-разному без всякой причины. Ступеней пять, и у каждой свой
     смысл: чем крупнее поверхность, тем мягче угол. Кругляш считается от
     высоты и поэтому в шкалу не входит.

     Радиус ступенью выше внутри ступени ниже выглядит вдавленным: миниатюра
     в карточке берёт --r-chip, карточка --r-card, и вложенность читается. */
  --r-mark: 4px;       /* волосяная деталь: грань коробки, метка */
  --r-chip: 10px;      /* миниатюра в строке, мелкая кнопка, плашка */
  --r-ctl: 12px;       /* элемент управления: поле, кнопка, плитка */
  --r-card: 14px;      /* карточка и панель */
  --r-big: 20px;       /* крупная поверхность: нижний лист, большое окно */
  --r-pill: 999px;     /* кругляш: чип, бейдж, подсказка */

  /* Прежнее имя карточного радиуса: на нём 24 правила, и переименование ради
     переименования — это диффы, в которых потом не найти настоящую правку. */
  --radius: var(--r-card);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --bar-h: 76px;        /* высота нижней панели действий: 56 цель + 10 + 10 */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #15171a;
    --card: #1e2126;
    --ink: #f2f4f7;
    /* 6.4:1 на карточке было мало; этот даёт 7.7. */
    --muted: #aab4c2;
    --line: #2c3138;
    /* Светлее прежнего #4c9aff. Тот был хорош сам по себе, но всё, что им
       написано, не дотягивало: 5.7:1 на карточке, 5.4:1 на своей бледной
       плашке (вкладка, чипс, метка «из 1С»). Этот держит 7.5 и 7.1, а
       тёмный текст поверх него — 8.3 вместо 6.3. */
    --accent: #79b3ff;
    /* Чуть светлее прежних: на своих плашках выходило 5.7–6.8:1,
       у красного хуже всех, а он-то и сообщает о просрочке. Стало 7:1. */
    --ok: #61cb8e;
    --warn: #e1a85f;
    --stop: #f59089;

    /* Заливки здесь светлые, поэтому текст на них — цвета фона приложения.
       Белым по ним выходило 2.2–2.9:1. */
    --on-accent: #15171a;
    --on-ok: #15171a;
    --on-warn: #15171a;
    --on-stop: #15171a;

    /* На тёмном фоне мягкая тень не читается вовсе — нужна плотная. */
    --lift: 0 1px 2px rgba(0, 0, 0, .45), 0 1px 1px rgba(0, 0, 0, .3);
    /* В темноте тень не видна: чёрное на чёрном ничего не отделяет. Поэтому
       слои здесь держатся на светлой кромке сверху — так же, как это делают
       сами тёмные интерфейсы телефонов, — а тень лишь добавляет глубины. */
    --raise: 0 -1px 0 rgba(255, 255, 255, .07), 0 -2px 12px rgba(0, 0, 0, .5);
    --over: 0 -1px 0 rgba(255, 255, 255, .09), 0 -8px 34px rgba(0, 0, 0, .66);
    /* Не смысловые цвета: они здесь светлые, и полоса во всю ширину слепила
       бы ночью. Те же пять оттенков, но тёмные — цвет раздела узнаётся, а
       экран остаётся тёмным. */
    --bar-accent: #1b3252;
    --bar-ok: #17392a;
    --bar-warn: #3a2c14;
    --bar-pick: #2b2350;
    --bar-stop: #3a1c1a;
    --bar-mut: #24272c;

    --tint-accent: #14171d;
    --tint-ok: #141a16;
    --tint-warn: #1a1815;
    --tint-pick: #17151c;
    --tint-stop: #1c1617;
    --tint-mut: #17181a;

    --brandbar: var(--bar-accent);
    --on-brandbar: #eaf1fb;

    /* Сборка: 7.72 на карточке, 7.66 на своей плашке, 8.59 тёмным поверх. */
    --pick: #bda8f2;
    --on-pick: #15171a;
    --pick-bg: #241d3a;

    --ok-bg: #16301f;
    --warn-bg: #322614;
    --stop-bg: #35191a;
    --accent-bg: #16263a;
  }
}

* { box-sizing: border-box; }

/* Фон корневого элемента растекается по всему полотну — в том числе на полосу
   у нижнего края экрана, которая в этом режиме iOS лежит ВНЕ страницы (замер
   17.09: экран 932, страница 873, разница 59 — высота строки с часами). Изнутри
   страницы её не закрасить ничем, что лежит в потоке: хвост нижней панели её не
   достал. А фон полотна — достаёт. Поэтому он цвета панели, а не фона списка. */
html { background: var(--card); }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  /* Системный шрифт: ничего не грузим из сети, приложение обязано жить офлайн. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: var(--fs-lead);
  line-height: 1.35;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
}

body {
  display: flex;
  flex-direction: column;
  /* Место под чёлкой и островом отдано полосе .notch: приложение рисует себя
     до края экрана, но пишет — ниже. Отступ один на всё, потому что верхним
     на экране бывает не только шапка: на входе её нет, а плашка «нет сети»
     лежит выше всех экранов сразу. */
  padding-top: env(safe-area-inset-top, 0px);
}

button, input, select, textarea { font: inherit; color: inherit; }

button {
  touch-action: manipulation;     /* убирает 300 мс задержку, но зум страницы не запрещает */
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  border: 0;
  background: none;
}

:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ── Цифры и буквы ───────────────────────────────────────────────────
   Моноширинные цифры там, где числа стоят столбиком или меняются на месте.
   Пропорциональная единица вдвое уже восьмёрки: счётчик очереди, прыгающий
   с 9 на 10, сдвигал подпись рядом, а числа в соседних строках списка не
   вставали в столбец. Правило общее, а не по месту: до этого tabular-nums
   стояло в пяти правилах из многих, где цифры есть.

   Шрифт берём системный, и на айфоне это San Francisco: у него цифры в
   таком начертании и так одинаковой ширины, но на андроиде и в вебвью это
   не так, и полагаться на удачу незачем. */
.num, .homenum, .homes, .meta, .qty, .badge, .note, .cnt,
.itable .it-n, .itable .it-qty, .q-sum, .daynum, .pos, .when, .time {
  font-variant-numeric: tabular-nums;
}

/* Трекинг. Крупное число набрано слишком широко: на 30 px межбуквенный
   просвет системного шрифта рассчитан на текст, а не на цифру, которую
   читают с метра. Мелкая подпись наоборот слипается. Величины намеренно
   крошечные — это настройка, которую замечают, только если её убрать. */
/* Только у счётчика. Номер документа (.num) набран с ПОЛОЖИТЕЛЬНЫМ
   трекингом, и это верно: «СПНФ-000973» — не число, а код, и буквы с
   цифрами в нём должны стоять свободнее, иначе ноль липнет к девятке. */
.homenum { letter-spacing: -.015em; }
h1, h2, .shot-title, .screen-title { letter-spacing: -.01em; line-height: 1.25; }
.meta, .note, .cap, .adhint, .winnote { letter-spacing: .005em; }

/* ── Отклик на нажатие ───────────────────────────────────────────────
   Одно правило вместо двенадцати. Раньше каждая нажимаемая вещь заводила
   своё `:active { background: … }`, и палец получал ответ только цветом —
   на ярком складе под бликами его часто не видно вовсе.

   Здесь ответ двойной: цвет и лёгкая просадка. 0.98 — это 1–2 px на кнопке
   обычного размера: не «эффект», а ощущение, что кнопку продавили. Возврат
   120 мс, то есть быстрее, чем палец успевает уйти с экрана: экран остаётся
   таким же быстрым, просто перестаёт быть деревянным.

   transform на position:fixed создаёт систему координат для вложенного
   fixed — поэтому нижняя панель и шапка в список не входят, они только
   основание для кнопок внутри себя. */
.press,
.card, .homebtn, .key, .placebtn, .itemslink, .sesscm, .boxarrow,
.rel-row, .qitem[data-go], .chip, .hint, .btn {
  transition: transform .12s ease, background-color .12s ease, box-shadow .12s ease;
}
.press:active,
.card:active, .homebtn:active, .key:active, .placebtn:active, .itemslink:active,
.sesscm:active, .boxarrow:active, .rel-row:active, .qitem[data-go]:active,
.chip:active, .hint:active, .btn:active {
  transform: scale(.98);
}
/* Крупной поверхности просадка идёт меньше: карточка во всю ширину при 0.98
   отъезжает от края на 4 px, и это читается как сдвиг, а не как нажатие. */
.card:active, .qitem[data-go]:active, .rel-row:active, .itemslink:active {
  transform: scale(.995);
}

.hidden { display: none !important; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

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

.screen {
  display: none;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.screen.active { display: flex; }
/* Точка отсчёта для всего, что позиционируется внутри экрана входа.
   Подтверждению смены она больше не нужна: оно встало по центру экрана. */
#s-login { position: relative; }

.top {
  flex: 0 0 auto;
  background: var(--card);
  border-bottom: 1px solid var(--line);
}

.scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--gap) var(--gap) calc(var(--bar-h) + var(--safe-b) + 16px);
}

/* Нижняя панель — всё частое живёт здесь, до неё дотягивается большой палец. */
/* Айфон, режим «страница под чёлкой»: полоса «домой» лежит за нижним краем
   страницы, и отступ под неё — потерянные 34 точки, на которые панель
   поднималась зря. Признак ставит app.js, см. markCutBottom. */
.cutbottom .bottom { padding-bottom: 10px; }

.bottom {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  gap: var(--gap);
  align-items: center;
  padding: 10px var(--gap) calc(10px + var(--safe-b));
  background: var(--card);
  /* Линия остаётся: на дешёвом экране под бликами от ворот она держится
     дольше тени. Тень поверх неё отделяет панель от уезжающего под неё
     списка — одной линии для этого мало. */
  border-top: 1px solid var(--line);
  box-shadow: var(--raise);
  z-index: 20;
}

/* ── Шапка задач ────────────────────────────────────────────────────── */

/* Шапка стала ниже на восемь точек: кнопка кабинета уехала вниз, и держать
   ради неё прежний воздух больше не за чем. Высоту теперь задаёт либо кнопка
   «назад» (она цель нажатия и меньше не станет), либо сам заголовок. */
.headrow {
  display: flex;
  align-items: stretch;
  gap: var(--gap);
  padding: var(--s1) var(--gap);
}

.tabs { display: flex; gap: var(--gap); flex: 1 1 auto; }

.tab {
  flex: 1 1 0;
  min-height: var(--tap);
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-lead);
  font-weight: 600;
  border: 2px solid transparent;
}
.tab[aria-selected="true"] {
  background: var(--accent-bg);
  color: var(--accent);
  border-color: var(--accent);
}

/* ── Поиск ──────────────────────────────────────────────────────────── */

.searchrow { display: flex; gap: var(--gap); padding: 0 var(--gap) var(--s2); }

.searchbox {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 0 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.searchbox svg { flex: 0 0 auto; color: var(--muted); }
.searchbox input {
  flex: 1 1 auto;
  min-width: 0;
  height: calc(var(--tap) - 4px);
  border: 0;
  background: none;
  font-size: var(--fs-lead);
}
.searchbox input:focus { outline: none; }

.iconbtn {
  flex: 0 0 auto;
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
}
.iconbtn.plain { border-color: transparent; background: none; }

.chips { display: flex; gap: var(--gap); padding: 0 var(--gap) var(--s2); overflow-x: auto; }
/* Кнопка, из-под которой чипсы разворачиваются. Число включённых фильтров —
   маленькой плашкой в углу: свёрнутая строка не должна уметь врать о том,
   что список отобран. */
.filtbtn { position: relative; flex: 0 0 auto; }
.filtbtn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.filt-n {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--r-chip);
  background: var(--accent);
  color: #fff;
  font-size: var(--fs-note);
  font-weight: 700;
  line-height: 20px;
  text-align: center;
}
.chip {
  flex: 0 0 auto;
  min-height: 44px;               /* чипсы — не основной путь, допускаем 44 */
  padding: 0 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--muted);
  font-size: var(--fs-body);
  font-weight: 600;
}
.chip[aria-pressed="true"] {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--accent);
}

/* Честность про офлайн: список может быть несвежим. */
.stale {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 var(--gap) var(--gap);
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-body);
  font-weight: 600;
}

.banner {
  margin: 0 var(--gap) var(--gap);
  padding: 12px;
  border-radius: var(--radius);
  background: var(--stop-bg);
  color: var(--stop);
  font-size: var(--fs-body);
  font-weight: 600;
}

/* ── Главный экран ──────────────────────────────────────────────────
   Четыре кнопки во весь экран, каждая примерно четверть высоты. Цель
   нажатия здесь заведомо больше 56 px: в это тычут в перчатке, не глядя. */

.hometitle { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
.hometitle b { font-size: var(--fs-head); font-weight: 700; }
.hometitle span { font-size: var(--fs-cap); color: var(--muted); margin-top: 2px; }

/* ── Цвет раздела ────────────────────────────────────────────────────
   Какой раздел открыт, объявляет сам экран атрибутом data-view. Всё
   остальное — производные двух переменных, поэтому новый раздел добавляется
   одной строчкой, а не правкой десятка правил.

   Значения от темы не зависят: тёмные заливки живут в --bar-*, а какая
   тема сейчас, уже решено там. */
[data-view="receipt"] { --brandbar: var(--bar-ok); --tint: var(--tint-ok); }
[data-view="shipment"] { --brandbar: var(--bar-accent); --tint: var(--tint-accent); }
[data-view="transfer"] { --brandbar: var(--bar-warn); --tint: var(--tint-warn); }
[data-view="assembly"] { --brandbar: var(--bar-pick); --tint: var(--tint-pick); }
[data-view="day"] { --brandbar: var(--bar-mut); --tint: var(--tint-mut); }
[data-view="sort"] { --brandbar: var(--bar-stop); --tint: var(--tint-stop); }

/* Полоса раздела. Заливаем всю верхнюю панель, а не одну строку заголовка:
   она и есть шапка экрана, и вырезанный из неё поиск оставлял бы белую
   полоску между цветом и списком. Заодно цвет заходит под вырез телефона —
   у .top для этого свой отступ по safe-area. */
.brandtop { background: var(--brandbar); border-bottom-color: transparent; }
.brandtop .hometitle b,
.brandtop .viewtitle b,
.brandtop .shot-title .num { color: var(--on-brandbar); }
/* Подписи на заливке: тот же цвет вполсилы. Отдельного токена не заводим —
   он разошёлся бы с основным при первой же правке палитры. */
.brandtop .hometitle span,
.brandtop .viewtitle span,
.brandtop .shot-title .party { color: var(--on-brandbar); opacity: .78; }
/* Кнопки на заливке. Подложка полупрозрачным серым, а не белым и не чёрным:
   одно правило работает и на светлой заливке, и на тёмной. */
.brandtop .iconbtn.plain { color: var(--on-brandbar); }
.brandtop .iconbtn:not(.plain),
.brandtop .chip {
  color: var(--on-brandbar);
  background: rgba(127, 127, 127, .22);
  border-color: rgba(127, 127, 127, .45);
}
/* Включённый фильтр на заливке: белым по полупрозрачному, иначе акцентный
   синий чипса тонул в синей же полосе отгрузки. */
.brandtop .chip[aria-pressed="true"] {
  background: rgba(255, 255, 255, .92);
  border-color: transparent;
  color: var(--ink);
}
.brandtop .filtbtn[aria-expanded="true"] { border-color: var(--on-brandbar); }
/* Поле поиска остаётся белым: это место для ввода, и оно обязано читаться
   как углубление в полосе, а не как её часть. */
.brandtop .searchbox { background: var(--card); border-color: transparent; }
/* Тумблер «Мои» на экране за день: выбранное состояние — плашка поверх
   полосы, невыбранное — её обводка. */
.brandtop .mineonly[aria-pressed="true"] {
  background: rgba(255, 255, 255, .92);
  border-color: transparent;
  color: var(--ink);
}

/* Оттенок фона списка. Полоса сверху говорит о разделе громко, фон —
   вполголоса, и вместе они держат узнавание, пока полоса ушла за край при
   прокрутке. */
.tintbg { background: var(--tint); }

/* ── Полоса под чёлкой ───────────────────────────────────────────────
   Приложение занимает весь экран, включая место под чёлкой и островом
   (см. black-translucent в index.html), и заливать это место должно само.

   Полоса отдельным слоем, а не отступом у шапки, по двум причинам. Шапка
   есть не на каждом экране — на входе её нет вовсе. И над экранами живёт
   плашка «нет сети»: она бледная, а часы в этом режиме iOS всегда белые,
   и белое по бледно-янтарному не читается никак.

   Цвет — раздела. Он тёмный в обеих темах, и белые часы поверх него держат
   свои 7:1 с запасом. Раздел объявляет тело страницы: полоса лежит вне
   экранов и через вложенность их значения не достанет.

   Выше всего, включая тост: под чёлкой не должно оказываться ничего, кроме
   часов. Нажатия пропускает насквозь — это заливка, а не кнопка. */
/* Живёт дальше, хотя в нынешнем режиме высота её нулевая: поля безопасной
   зоны сверху там нет. Оставлена намеренно — она сама включается там, где
   поле появляется (другой режим, поворот экрана, другая система), и красится
   цветом раздела, а не остаётся серой дырой. */
.notch {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: env(safe-area-inset-top, 0px);
  background: var(--brandbar);
  z-index: 70;
  pointer-events: none;
}


/* Заголовок однорежимного списка задач — на месте бывших вкладок */
/* Заголовок раздела одной строкой: «Отгрузка покупателям». Двумя строками
   он читался как название и подпись к нему, хотя это одна фраза. Кегль у
   обеих частей один — так они стоят на общей линии, а разделяет их только
   насыщенность. Не влезло — хвост обрезается многоточием, а не переносится:
   высоту строки держит кнопка «назад», и вторая строка текста ей не нужна. */
.viewtitle {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .3em;
  font-size: var(--fs-head);
  white-space: nowrap;
}
.viewtitle b { flex: 0 0 auto; font-weight: 700; }
/* Кнопка «назад» перед заголовком: у неё вокруг стрелки по 15 точек пустого
   поля, и общий зазор строки поверх этого поля был лишним воздухом. Убираем
   его и прижимаем кнопку на 4 точки к краю — цель нажатия та же, 56 точек, а
   «Перемещение между складами» на 375 точках встаёт целиком: ему не хватало
   ровно 11. */
.headrow > .iconbtn.plain:first-child { margin-left: -4px; margin-right: calc(-1 * var(--gap)); }
.viewtitle span {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 400;
  color: var(--muted);
}

.homegrid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  padding: var(--gap) var(--gap) calc(var(--bar-h) + var(--safe-b) + 16px);
}

.homebtn {
  flex: 1 1 0;
  min-height: 92px;            /* на низком экране кнопки не схлопываются */
  display: flex;
  align-items: center;
  /* Отступы по шкале, и это не только порядок ради порядка: прежние 14 и 16
     отбирали у названия кнопки те самые пять пикселей, из-за которых «Фото
     за день» ломалось на две строки. Круг под знаком шире прежней иконки на
     десять точек, и «Фото за день» сломалось снова — поэтому зазор здесь
     ступенью ниже соседних: у круга свои поля вокруг знака, и 12 точек рядом
     с ним были бы двойным отступом. Замер: названию нужно 154 точки,
     остаётся 159. */
  gap: var(--s2);
  width: 100%;
  text-align: left;
  padding: var(--s3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--lift);
}
.homebtn:active { background: var(--accent-bg); }
/* Знак раздела в круге своей заливки. Пять белых карточек одного веса не
   отличались друг от друга ничем, кроме слова, — а попадают в них пальцем
   в перчатке и не глядя. Заливки те же четыре, что уже носят плашки
   состояния: новых цветов в приложении не появляется. */
.homedisc {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
}
.homedisc .homeic { width: 24px; height: 24px; color: inherit; }
/* Круг повторяет цвет раздела: тот же признак человек увидит через секунду
   в полосе открытого экрана, и связь «кнопка — экран» держится цветом, а не
   памятью о том, куда нажал. */
.tone-ok { background: var(--ok-bg); color: var(--ok); }
.tone-warn { background: var(--warn-bg); color: var(--warn); }
.tone-accent { background: var(--accent-bg); color: var(--accent); }
.tone-pick { background: var(--pick-bg); color: var(--pick); }
.tone-stop { background: var(--stop-bg); color: var(--stop); }
.tone-mut { background: var(--bg); color: var(--muted); }
.homeic { flex: 0 0 auto; color: var(--accent); }
.homeb { flex: 1 1 auto; min-width: 0; }
.homet { display: block; font-size: var(--fs-title); font-weight: 700; }
.homes { display: block; font-size: var(--fs-body); color: var(--muted); margin-top: 2px; }
.homenum {
  flex: 0 0 auto;
  font-size: var(--fs-num);
  font-weight: 800;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* Число в цвет своего раздела — тот же признак, что и круг слева, с другого
   края строки. */
.homenum.n-ok { color: var(--ok); }
.homenum.n-warn { color: var(--warn); }
.homenum.n-accent { color: var(--accent); }
.homenum.n-pick { color: var(--pick); }
.homenum.n-stop { color: var(--stop); }
/* Ноль не дело. Тем же весом, что семёрка, он обещал работу, которой нет. */
.homenum.zero { color: var(--muted); font-weight: 600; }
/* Дата вместо числа: словами и мельче — это подпись, а не счёт. Ступень
   ниже соседей: иначе подпись отбирает ширину у названия кнопки, и
   «Фото за день» ломается на две строки. */
.homenum.day { font-size: var(--fs-body); font-weight: 700; color: var(--muted); text-align: right; max-width: 40%; }

/* ── Фото за день ───────────────────────────────────────────────────── */

.daybar { display: flex; align-items: center; gap: var(--gap); padding: 0 var(--gap) var(--s2); }
.daybar input[type="date"] {
  flex: 1 1 auto;
  min-width: 0;
  height: var(--tap);
  padding: 0 12px;
  font-size: var(--fs-lead);
  font-weight: 700;
  text-align: center;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* Тумблер «мои снимки» живёт в шапке экрана. Не растёт и не переносится:
   заголовок рядом короткий, но ширину всё равно отдаём ему. */
.mineonly { flex: 0 0 auto; white-space: nowrap; min-height: 40px; padding: 0 14px; }

/* ── Заголовки групп ─────────────────────────────────────────────────
   Рубрика над пачкой строк. Раньше то же самое было объявлено пять раз в
   разных углах файла — .grouphead, .itemcount, .galcap и два варианта по
   цвету, — и «то же самое» медленно расходилось: 13 px против 14, свой
   набор отступов у каждого. База одна, модификаторы остались только там,
   где отличается смысл: цвет рубрики говорит, чем эта пачка особенная.
   Левый край общий с текстом карточек — см. --edge-in. */
.grouphead,
.itemcount,
.galcap {
  padding: var(--s3) var(--edge-in) var(--s1);
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Смысловые модификаторы: почему эта пачка не как остальные. */
.grouphead.overdue { color: var(--stop); }
.grouphead.warn { color: var(--warn); }
/* Найденное в 1С мимо списка: не «дело на сегодня», а ответ на набранный номер. */
.grouphead.found { color: var(--accent); }
/* Заголовок группы снимков — это номер документа и имя контрагента.
   Капслок рубрики здесь читался бы как крик и мешал узнавать имя. */
.grouphead.doc {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-lead);
  color: var(--ink);
}
.grouphead .ghn { color: var(--muted); font-weight: 700; }

/* Строка заказа. Крупный шрифт здесь не помогает: заказов на экране десятки,
   и человек ищет не «читаемую букву», а нужный номер среди соседних. Чем
   больше строк видно разом, тем меньше прокрутки в перчатке. Цель нажатия
   правило не нарушает: нажимается вся карточка целиком, а она выше 56 px. */
.card {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  /* 8 px, а не 6: между целями нажатия меньше не бывает, иначе перчатка
     мажет и открывается соседний заказ. Замер до правки давал 5–7 px. */
  margin-bottom: var(--gap);
  min-height: var(--tap);         /* вся карточка — одна цель нажатия */
  /* Полоса слева есть у КАЖДОЙ карточки, а красили её раньше только у
     просроченной. Одинаковые белые прямоугольники сверху донизу читать
     нечем: глаз ищет, за что зацепиться, и цепляется за текст, то есть
     читает подряд. Крашеный столбик слева отвечает «что горит» до чтения.
     Цвет здесь второй признак, не единственный: плашка с текстом состояния
     осталась на месте. Полоса рисуется внутрь — текст не съезжает. */
  --rail: var(--line);
  box-shadow: inset 6px 0 0 var(--rail), var(--lift);
  /* Цифры в столбик: в системном шрифте единица уже восьмёрки, и номера
     соседних заказов слегка пляшут при одинаковом числе знаков. */
  font-variant-numeric: tabular-nums;
}
/* Смысл состояния приходит из 1С цветом; его перевод в наши токены уже
   делает stateTone() — здесь только раскраска. */
.card.rail-ok { --rail: var(--ok); }
.card.rail-warn { --rail: var(--warn); }
.card.rail-stop { --rail: var(--stop); }
.card .num { font-size: var(--fs-head); }
/* Слово операции рядом с номером. Плашкой в 13 px при номере в 20 его
   читали, только уткнувшись носом, — а именно оно отличает две строки
   одного перемещения друг от друга. Кегль основной, плашка бледная:
   слово ищут боковым зрением, а читают уже глядя. */
.card .sideword {
  flex: 0 0 auto;
  padding: 2px var(--s2);
  border-radius: var(--r-pill);
  background: var(--accent-bg);
  color: var(--ink);
  font-size: var(--fs-lead);
  font-weight: 700;
  white-space: nowrap;
}
/* Знаки состояния держатся вместе и прижаты к правому краю. Раньше между
   номером и ними стоял растяжитель, и при переносе плашка состояния уезжала
   на вторую строку отдельно от числа снимков: две соседние карточки
   разъезжались по-разному, и глазу не за что было зацепиться. */
.card-marks {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

/* Две стороны перемещения стоят в списке соседними строками с одинаковым
   номером и одинаковым маршрутом. Слово их уже различает; подложка добавляет
   различие боковым зрением — до того, как строку начнут читать. Цвет здесь
   не единственный признак, а второй: слово никуда не делось. */
.card .sideword.in { background: var(--ok-bg); }
.card .sideword.out { background: var(--warn-bg); }

.card .party { font-size: var(--fs-body); margin-top: 2px; }
/* Мета-строка обрезается одной строкой. Комментарий из 1С бывает длиной в
   предложение, и на самом мелком кегле карточки он раскидывал её на две
   строки — ради текста, который читают, уже открыв заказ. */
.card .meta {
  font-size: var(--fs-note);
  margin-top: 2px;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Срок стоит рядом с номером, а не первым словом мелкой строки внизу: по нему
   список отсортирован, и именно его ищут глазами. Просроченный — цветом,
   тем же, что и полоса слева у карточки. */
.card .due {
  flex: 0 0 auto;
  /* Ступенью ниже соседей по строке: срок ищут рядом с номером, а не читают
     вместо него, и трёх точек, которые он отдаёт, хватает, чтобы состояние
     осталось в одной строке с номером. */
  font-size: var(--fs-cap);
  font-weight: 700;
  color: var(--muted);
}
.card.overdue .due { color: var(--stop); }
.card .pill { font-size: var(--fs-note); padding: 3px 9px; }
.card .photos { font-size: var(--fs-note); }
.card:active { background: var(--accent-bg); }
/* Срок главнее состояния: заказ, готовый к отгрузке неделю назад, — это
   прежде всего просроченный заказ. */
.card.overdue { --rail: var(--stop); }

/* Верх карточки переносится: у перемещения тут и сторона дела, и состояние,
   и счётчик кадров — на 390 px в одну строку они не помещаются, а обрезать
   состояние значит соврать про заказ. */
/* 5 px, а не 8: строка «номер + срок + состояние + счётчик» помещается на
   375 точках впритык, и каждая правка шрифта её опрокидывает. Сначала это
   сделал переезд срока наверх (не хватило двух точек), потом табличные цифры
   (ещё три: в таблице единица шириной с восьмёрку). Здесь это не цели нажатия
   — нажимается карточка целиком, — так что расстояние между ними вопрос
   воздуха, а не промаха пальцем. Замер после правки: строке нужно 331 точка
   из 333, и запаса тут нет. Перенос никуда не делся и остаётся страховкой: номер подлиннее или
   состояние в три слова уедут на вторую строку, и это лучше, чем обрезать
   состояние и соврать про заказ. */
.card-top { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; row-gap: 5px; }
.card-top .photos { flex: 0 0 auto; }
.num { font-size: var(--fs-title); font-weight: 700; letter-spacing: .01em; }
.spacer { flex: 1 1 auto; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-size: var(--fs-cap);
  font-weight: 700;
  white-space: nowrap;
}
.pill.ok { background: var(--ok-bg); color: var(--ok); }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.pill.stop { background: var(--stop-bg); color: var(--stop); }
.pill.mut { background: var(--bg); color: var(--muted); }

.photos { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-body); font-weight: 700; color: var(--muted); }
.photos.has { color: var(--accent); }

/* Галочки статуса выгрузки — как в мессенджерах: одна «ушло», две «на месте».
   Цвет свой, не карточкин: число снимков и его судьба — разные вещи, и слить
   их в один синий значит спрятать вторую. */
.tick { display: inline-flex; align-items: center; margin-left: 2px; }
.tick.queued { color: var(--muted); }
.tick.sent { color: var(--ok); }
/* Черновик галочкой не обозначить — её нет. Слово честнее пустого места:
   кладовщик должен понять, что кадры ещё в телефоне, а не на диске. */
.tick.draft {
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .01em;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--warn-bg);
  color: var(--warn);
}

/* Подпись к галочкам и строка про черновики в очереди. Мелко, но не мельче
   14 px — это правило экрана. */
.ticknote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s1) var(--s2);
  padding: var(--s2) var(--edge-in);
  font-size: var(--fs-cap);
  line-height: 1.4;
  color: var(--muted);
}
.ticknote .tick { margin: 0; }
/* Каждая пара «знак — что значит» держится вместе: разорванная переносом
   подпись читается как два разных куска текста. */
.tickone { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
/* Пояснения показываются поштучно, по факту знаков на экране. Точку рисуем
   только тогда, когда перед этим пояснением стоит видимое: `+` не пропускает
   скрытых соседей и оставлял бы точку в начале строки. */
.tickone:not([hidden]) ~ .tickone:not([hidden])::before {
  content: '·'; margin-right: 4px; color: var(--muted);
}

/* Внутри карточки это span-ы (кнопка не может содержать блочные ссылки),
   поэтому строки раскладываем вручную. */
.party { display: block; font-size: var(--fs-lead); margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.meta { display: block; font-size: var(--fs-cap); color: var(--muted); margin-top: 3px; }

.empty { padding: 40px var(--s4); text-align: center; color: var(--muted); font-size: var(--fs-lead); }
/* Знак над строкой в пустом месте. Пустой раздел кладовщик видит каждое утро:
   до первого кадра пусты все. Цвет линии, то есть почти не виден, — он не
   кричит, а сообщает, что экран не сломался, а просто пуст. Ставим только там,
   где место пусто по делу; «Спрашиваю сервер…» — не пустое место, а ожидание,
   и знака не получает. */
.eglyph { display: flex; justify-content: center; margin-bottom: var(--s3); color: var(--line); }
.eglyph svg { display: block; }

/* Подписи под списком: каким окном он отобран и что с поиском в 1С.
   Мелко, но не мельче 14 px — это правило экрана. */
.winnote {
  padding: var(--s2) var(--edge-in) var(--s1);
  font-size: var(--fs-cap);
  line-height: 1.4;
  color: var(--muted);
}
.winnote:empty { display: none; }
.winnote.nowifi { color: var(--warn); font-weight: 700; }

/* Грань найденной карточки синяя — тем же цветом, что и её заголовок группы.
   Внутренняя тень, а не рамка: рамка сдвигала бы текст этой карточки на
   6 px вправо относительно соседних, и левый край списка ломался. */
/* Найденное в 1С мимо списка главнее всего: это прямой ответ на набранный
   номер, и спутать его с соседними строками нельзя. */
.card.found { --rail: var(--accent); }

/* ── Кнопки ─────────────────────────────────────────────────────────── */

/* Иконка внутри flex-кнопки сжимается вместе с текстом и превращается
   в точку — фиксируем размер явно. */
.btn svg, .queuebtn svg, .stale svg, .iconbtn svg { flex: 0 0 auto; }

.btn {
  min-height: var(--tap);
  padding: 0 var(--s4);
  border-radius: var(--radius);
  font-size: var(--fs-lead);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn.wide { flex: 1 1 auto; }
/* Отключённая кнопка. Прозрачность в .45 давала 1.99:1 — подпись не
   читалась вовсе, и человек не понимал, что там вообще написано и почему
   не нажимается. Поэтому не прозрачность, а свой спокойный вид: заливка
   цвета разделителя и приглушённый текст, 6.1:1. Синяя главная кнопка в
   отключённом виде тоже становится спокойной, а не бледно-синей — иначе
   она продолжает звать себя нажать. */
.btn[disabled],
.btn.primary[disabled] {
  opacity: 1;
  background: var(--line);
  border-color: var(--line);
  color: var(--muted);
}

/* «Снять без заказа» всегда на экране и всегда в одну строку: спрятать её
   в меню значит получить несделанный снимок вместо снимка без привязки.
   Класс, а не id: в разметке у кнопки только класс, и правило по #free-shot
   не работало никогда — подпись переносилась на две-три строки и раздувала
   нижнюю панель до 92 px при заложенных 76. */
.free-shot { white-space: nowrap; padding: 0 var(--s3); font-size: var(--fs-lead); }

.queuebtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  min-height: var(--tap);
  min-width: 0;
  overflow: hidden;
  padding: 0 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  font-size: var(--fs-body);
  font-weight: 700;
}
.queuebtn.pending { background: var(--warn-bg); border-color: var(--warn); color: var(--warn); }
.bottom { gap: 8px; }
.bottom .queuebtn { flex: 0 0 auto; }

/* Панель из трёх кнопок. Сетка, а не флекс с растяжителями: середина обязана
   быть серединой ЭКРАНА, иначе главная кнопка ездит вслед за счётчиком
   очереди — то двузначным, то трёхзначным, то пустым. Колонки по краям
   ровно в цель нажатия, середина забирает остальное. */
.bottom.trio {
  display: grid;
  grid-template-columns: var(--tap) minmax(0, 1fr) var(--tap);
  align-items: center;
}
.bottom.trio .free-shot { justify-self: center; min-width: 0; overflow: hidden; }

/* Квадратная кнопка нижней панели: цель нажатия целиком, подпись внутри. */
.sqbtn {
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 0;
  overflow: hidden;
}
/* Число очереди под стрелкой. Табличное и в одну строку: при сотне кадров
   оно обязано ужаться само, а не растолкать квадрат. */
.sqbtn .qbtn-text {
  font-size: var(--fs-note);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.sqbtn .qbtn-text:empty { display: none; }
/* Инициалы вместо имени: полное имя ждёт первой строкой внутри листа. */
.whobtn .who-ini { font-size: var(--fs-lead); font-weight: 700; letter-spacing: .02em; }
.queuebtn.clean { color: var(--muted); }
/* Очередь пуста, но в телефоне лежат несданные черновики. Это не беда и не
   покой: работа сделана, но не отдана, и по «Готово» её ещё ждут. Поэтому
   свой вид — заметный, но не тревожный красный. */
.queuebtn.holding { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }

/* ── Вход ─────────────────────────────────────────────────────────────
   Экран .login и список .person убраны из разметки ещё при переходе на
   ввод кода: в index.html и app.js их больше нет, правила удалены. */

.pin { text-align: center; padding: 8px var(--gap); }

/* Картинка на входе. Высота задана в единицах экрана, а не в точках: на
   маленьком телефоне она должна уступить место клавиатуре, а не выдавить её
   за край. Ниже 620 px убирается совсем — кнопки важнее. */
.hero { margin: 10px auto 2px; max-width: 300px; }
.hero svg { display: block; width: 100%; height: auto; }
/* В светлой теме это тень, в тёмной — подсвеченный пол: иначе коробка висит
   в пустоте. Один и тот же приём, что и под коробкой на экране съёмки. */
.hero-shadow { fill: #000; opacity: .1; }
@media (prefers-color-scheme: dark) { .hero-shadow { fill: #fff; opacity: .05; } }
.hero-box { filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .12)); }
.hero-seam, .hero-tape { fill: none; stroke-linecap: round; }
.hero-seam { stroke: #8a6a45; stroke-width: 1.4; opacity: .5; }
.hero-tape { stroke: #f0dfc4; stroke-width: 7; opacity: .75; }
/* Рамки — акцентом: это единственная цветная деталь на экране до того, как
   человек начнёт набирать код, и она же связывает вход с экраном съёмки. */
.hero-frame path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .85;
}

@media (max-height: 760px) { .hero { max-width: 230px; margin-top: 4px; } }
@media (max-height: 620px) { .hero { display: none; } }
.pindots { display: flex; gap: var(--s3); justify-content: center; margin: var(--s4) 0 var(--s5); }
.pindot { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--muted); }
.pindot.on { background: var(--accent); border-color: var(--accent); }

.pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); max-width: 340px; margin: 0 auto; }
.key {
  min-height: 68px;
  border-radius: var(--radius);
  background: var(--card);
  border: 1px solid var(--line);
  font-size: var(--fs-title);
  font-weight: 600;
}
.key:active { background: var(--accent-bg); }

/* Экран кода: заголовок и подпись живут прямо в .pin, отдельного .login больше нет */
.pin h1 { font-size: var(--fs-title); margin: 14px 0 6px; }
.pin .sub { color: var(--muted); font-size: var(--fs-lead); margin: 0 0 4px; }

/* Отказ виден и без цвета: текст плюс встряска. На складе бывают выгоревшие
   экраны, и красным одним полагаться нельзя. */
.pinerr { min-height: 22px; color: var(--stop); font-size: var(--fs-lead); font-weight: 600; }
.pinerr.shake { animation: shake .28s ease; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-7px); }
  75% { transform: translateX(7px); }
}

.demohint {
  margin: var(--s4) auto 0;
  max-width: 340px;
  padding: 10px var(--s3);
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-cap);
  line-height: 1.4;
}

/* Подтверждение смены: перекрывает клавиатуру, чтобы имя нельзя было не заметить */
.hello {
  /* fixed, а не absolute: подтверждение должно встать по центру ЭКРАНА. При
     absolute точкой отсчёта был экран входа, и стоило над ним появиться чему-то
     ещё — плашке обновления, полосе демо-режима, отступу под вырез, — как имя
     уезжало вниз на высоту этой полосы. Человек смотрит на имя полсекунды и
     решает, он ли это; смещённое оно читается как чужой экран. */
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--bg);
  color: var(--ok);
  z-index: 5;
}
/* display:flex у .hello перебивает браузерное [hidden] — гасим явно,
   иначе подтверждение висит поверх клавиатуры с пустым именем */
.hello[hidden] { display: none; }
.hello-name { font-size: var(--fs-num); font-weight: 800; color: var(--ink); }
.hello-sub { color: var(--muted); font-size: var(--fs-lead); }

/* ── Бумаги на съёмке ───────────────────────────────────────────────── */

/* Переключатель «Доверенность | Прочие» над сеткой. Две кнопки во всю ширину:
   выбор из двух, и прятать половину в меню незачем. */
.docswitch {
  display: flex;
  gap: var(--gap);
  padding: 0 var(--gap) var(--s2);
}
.docswitch[hidden], .docswitch.hidden { display: none; }
.docswitch .chip { flex: 1 1 0; min-height: var(--tap); }

/* ── Сборка ─────────────────────────────────────────────────────────── */

/* Две кнопки сборки отдельной строкой над панелью камеры. Во всю ширину и
   каждая в свой край: «собран полностью» закрывает задание, и промах по нему
   стоит дороже, чем лишнее нажатие. */
/* Строка живёт внутри нижней панели и занимает её целиком, встав над затвором.
   order:-1 ставит её первой независимо от места в разметке, а нижний отступ
   отделяет от камеры: иначе «собран полностью» оказывается вплотную к затвору. */
.bottom.shoot { flex-wrap: wrap; }
.asmrow {
  flex: 1 0 100%;
  order: -1;
  display: flex;
  gap: var(--gap);
  margin-bottom: var(--s2);
}
.asmrow[hidden] { display: none; }
/* Половина ширины на кнопку — это 128 точек на телефоне 375. «Собран
   полностью» в одну строку туда не влезает, поэтому перенос разрешён, а кегль
   взят подписной. Обрезать текст многоточием здесь нельзя: «Собран…» и
   «Собран…» неразличимы, а разница между ними — закрытое задание. */
.asmrow .btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--tap);
  padding: 6px 8px;
  font-size: var(--fs-cap);
  line-height: 1.2;
  white-space: normal;
}

/* Раскрытая поставка внутри строки связанного заказа. Отступ слева показывает
   вложенность: это состав не нашего заказа, а той машины, из которой он
   собирается, и спутать их нельзя. */
.relbody {
  margin: var(--s2) 0 var(--s1) var(--s3);
  padding-left: var(--s2);
  border-left: 2px solid var(--line);
}
.relbody .itemcount { margin-top: 0; }
.relbody .relgo { margin-top: var(--s2); }
.rel-chev { flex: 0 0 auto; color: var(--muted); transition: transform .15s; }
.rel-chev.open { transform: rotate(180deg); }

/* ── Съёмка ─────────────────────────────────────────────────────────── */

.shot-top { padding: 10px var(--gap); display: flex; align-items: center; gap: 10px; }
/* Кнопка «чужие» не переносится и не растёт: на 375 точках она в две строки
   разваливала и себя, и заголовок рядом. Ширину отдаём заголовку — он длиннее
   и важнее. */
#sort-others { flex: 0 0 auto; white-space: nowrap; padding: 0 12px; font-size: var(--fs-cap); }
.shot-title { flex: 1 1 auto; min-width: 0; }
.shot-title .num { display: block; }
.shot-title .party { margin-top: 2px; }

/* Строка «номер + слово операции». Плашка вида работы стояла соседом
   заголовка и отжимала номер: тексту нужно было 147 px, оставалось 123, и
   номер уезжал под плашку. А номер — единственное место, где кладовщик
   сверяет, тот ли документ он снимает.
   Теперь они в одной строке с переносом: пока помещаются рядом — стоят
   рядом, не помещаются (узкий экран, длинный номер) — слово уходит на
   свою строку, а номер получает всю ширину и не режется. */
.numrow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--s2);
  row-gap: 2px;
}
.numrow .num {
  flex: 0 1 auto;
  min-width: 0;
  /* Совсем длинный номер лучше перенести, чем показать половину. */
  overflow-wrap: anywhere;
}

/* Слово операции: «отправить» / «принять» / «приёмка» / «отгрузка».
   Два перемещения одного документа различаются только им, и ошибка здесь
   означает, что кадры отгрузки уедут в приёмку. Поэтому основной кегль, а
   не 13 px прежней плашки, и своя бледная подложка — слово должно
   находиться боковым зрением раньше, чем его начнут читать. */
.kindword {
  flex: 0 0 auto;
  padding: 2px var(--s2);
  border-radius: var(--r-pill);
  background: var(--accent-bg);
  color: var(--ink);
  font-size: var(--fs-lead);
  font-weight: 700;
  white-space: nowrap;
}

/* Строка под заголовком съёмки: состав слева, «Доп. место» справа. Обе цели в
   одной строке и в 44 точки высоты — каждая точка здесь отнимается у коробки. */
/* Полоса прокручивается вбок — как страховка, а не как замысел. Кнопки должны
   помещаться, и сейчас помещаются; но стоит подписи стать длиннее на слово, и
   последняя уезжала за край экрана молча, без всякой возможности до неё
   добраться. Так это уже случилось с «Документами». */
.toprow {
  display: flex;
  align-items: stretch;
  border-top: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.toprow::-webkit-scrollbar { display: none; }
.toprow .itemslink { border-top: 0; }
/* Кнопка нового места. Не главное действие экрана и потому спокойная, но и не
   спрятанная: без неё вторую паллету снять некуда. */
.placebtn {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--gap);
  border-left: 1px solid var(--line);
  color: var(--accent);
  font-size: var(--fs-body);
  font-weight: 600;
  white-space: nowrap;
}
.placebtn:active { background: var(--accent-bg); }
@media (max-height: 700px) { .placebtn { font-size: var(--fs-note); } }

/* Строка состава. Состав перестал быть обязательной остановкой по дороге к
   камере: карточка ведёт прямо на съёмку, а «что внутри» открывается отсюда.
   Второстепенная цель — она не спорит с коробкой и затвором, но остаётся
   настоящей кнопкой: свериться с документом иногда нужно, и делать это в
   перчатке. Высота 44, а не 56: путь неосновной, и каждая точка высоты здесь
   отнимается у коробки. */
.itemslink {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--gap);
  border-top: 1px solid var(--line);
  background: none;
  color: var(--accent);
  font-size: var(--fs-body);
  font-weight: 600;
  text-align: left;
}
.itemslink:active { background: var(--accent-bg); }
.itemslink-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.itemslink-c { flex: 0 0 auto; transform: scaleX(-1); opacity: .8; }
/* Низкий экран: коробке нужнее. Подпись сжимается до одного слова со
   стрелкой — кнопка остаётся на месте, а высоты берёт вдвое меньше. */
@media (max-height: 700px) {
  .itemslink { min-height: 34px; font-size: var(--fs-note); }
}
/* Шеврон смотрит туда, куда поедет панель: вправо закрыто, вниз открыто. */
.itemslink[aria-expanded="true"] { color: var(--ink); }
.itemslink[aria-expanded="true"] .itemslink-c { transform: scaleX(-1) rotate(-90deg); }
.itemslink-c { transition: transform .22s ease; }

/* Раскрытый состав — выдвижной ящик ПОВЕРХ экрана, а не блок в потоке. В потоке
   он раздвигал колонку: коробка уезжала под нижнюю панель, а строка комментария
   вообще за край экрана. Поверх — ничего не двигается, состав просто закрывает
   коробку, пока его читают, и уходит обратно.
   Ниже нижней панели по слоям (у неё z-index 20): затвор обязан остаться и
   видимым, и нажимаемым.

   Анимируем max-height, а не height: высоту содержимого заранее никто не знает,
   а «auto» браузеры не анимируют. Цель — потолок, а не измеренная высота:
   короткий список доедет до своей высоты раньше конца анимации, длинный
   остановится на потолке и дальше прокручивается внутри себя. */
#s-shoot .top { position: relative; z-index: 15; }
.itemsbox {
  position: absolute;
  left: 0; right: 0; top: 100%;
  max-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;   /* дотянул до конца — экран под панелью не едет */
  background: var(--card);
  transition: max-height .28s ease;
}
.itemsbox.open {
  max-height: 46vh;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 10px 18px rgba(0, 0, 0, .16);
}
/* Внутри панели таблица прижата к тем же краям, что и на своём экране. */
.itemsbox .itemcount { padding-top: var(--s2); }
.itemsbox .itable { margin-bottom: var(--s2); }
/* На низком экране потолок ниже: там и коробка меньше, и терять её обиднее. */
@media (max-height: 700px) {
  .itemsbox.open { max-height: 38vh; }
}

/* Маршрут перемещения. В одну строку с обрезкой из «Центральный → Радужный»
   на 375 px оставалось «Центральный →»: куда едет палета — половина смысла
   строки. Две строки, обрезка по второй. */
.shot-title .party {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  white-space: normal;
  overflow: hidden;
}

/* «Разобрать» — плитка: кадров бывает много и их выбирают пачкой. */
.strip {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--gap) var(--gap) calc(var(--bar-h) + 40px + var(--safe-b));
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--gap);
  align-content: start;
}

/* Съёмка: коробка стоит в верхней трети рабочей области, всё, что ниже, —
   галерея сделанных кадров. Раньше коробка висела по центру, а под ней было
   пусто: галерее места не оставалось. Отступы в колонке поэтому скупые. */
.shoot-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Нижняя панель фиксированная и перекрывает контент: кнопка съёмки в ней
     выше обычной кнопки и торчит над полосой. Отдельной пилюли счётчика над
     панелью больше нет — её текст ушёл в заголовок над коробкой, — поэтому
     запас считаем от самой панели, а освободившееся отдаём коробке и сетке. */
  padding-bottom: calc(var(--bar-h) + 46px + var(--safe-b));
}

/* Полоска шагов. Точка на кадр: пройденные закрашены, текущая вытянута в
   пилюлю и видна боковым зрением, будущие бледные. Кладовщик сразу видит,
   сколько всего снимать — четыре точки на приёмке, шесть на отгрузке. */
.steps {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--s2);
  height: 18px;
  padding: var(--s1) var(--gap) 0;
}
.steps .sdot {
  width: 10px;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--line);
  transition: width .25s ease, background-color .25s ease;
}
.steps .sdot.done { background: var(--accent); opacity: .5; }
.steps .sdot.now {
  width: 30px;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-bg);
}

.boxhint {
  flex: 0 0 auto;
  text-align: center;
  padding: 5px var(--gap) 2px;
  font-size: var(--fs-lead);
  font-weight: 600;
  color: var(--muted);
}

/* ── Сетка кадров ─────────────────────────────────────────────────
   Все кадры дела разом, каждый с подписью, что он такое. Раньше под коробкой
   висела ещё и лента с теми же кадрами — она показывала их второй раз и
   съедала треть экрана. Осталась одна сетка, и включает её кнопка в правом
   верхнем углу: коробка и сетка — два взгляда на одно и то же, а не два
   этажа одного экрана.

   Три кадра в ряд — ровно три, а не «сколько влезет». Плана всего четыре-шесть
   мест, и при трёх столбцах он виден целиком, без прокрутки. Прежний auto-fill
   считал столбцы сам и держал три ровно до 320 px, а на всём, что уже (узкие
   Android, раскладной телефон сложенным), срывался на два и растягивал план на
   лишний ряд. Ширина столбца от этого не пострадала: на 320 px это те же 96 px,
   что давал auto-fill. Потолок — чтобы на планшете три плитки не раздулись во
   весь экран: остальной экран съёмки тоже свёрстан под телефон. */
.gallery.grid {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
  align-content: start;
  overflow-y: auto;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  /* Сверху 4 px не для красоты: у плитки «снимаем сейчас» обводка нарисована
     снаружи (box-shadow 3 px), а сетка прокручивается — с нулевым отступом
     верх обводки обрезался краем прокрутки и налезал на строку «Снято N из M».
     Отступ должен быть больше обводки, иначе она снова упрётся в край. */
  padding: var(--s1) var(--gap) var(--s2);
  /* Если план всё-таки не влез (кадров сверх плана бывает много), полоса
     прокрутки должна быть видна, а не всплывать на секунду от касания:
     иначе про второй ряд кадров просто не знают. */
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.gallery.grid::-webkit-scrollbar { width: 4px; }
.gallery.grid::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-pill); }

.gitem { position: relative; min-width: 0; }
.gcap {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-note);
  font-weight: 700;
  color: var(--muted);
  text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Кадр, на который человек смотрит сейчас: коробка повёрнута к нему, либо это
   очередной шаг плана. Обводка снаружи — сам снимок она не съедает. */
.gitem.now .frame,
.gitem.now .gslot { box-shadow: 0 0 0 3px var(--accent); }
.gitem.now .gcap { color: var(--accent); }

/* Пустое место плана: серая плашка с номером. Коробка отвечает на вопрос «что
   уже снято», а сетка — ещё и на «чего не хватает». */
.gslot {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-ctl);
  border: 1px dashed var(--line);
  background: var(--bg);
}
/* Номер пустого места в сетке. Прозрачность .5 опускала его до 2.6:1 —
   а это ответ на вопрос «чего не хватает», ради которого сетку и открывают. */
.gnum { font-size: var(--fs-num); font-weight: 800; color: var(--muted); }

/* Вид «сетка»: коробка и слот погрузки уходят, место целиком у плитки.
   Заголовок над коробкой уходит вместе с ней: он называет сторону, к которой
   коробка повёрнута, а без коробки поворачивать нечего — и то же самое уже
   сказано в заголовке сетки («сейчас Передняя»). */
.shoot-main.gridview .boxstage,
.shoot-main.gridview .boxhint,
.shoot-main.gridview .truckslot { display: none; }
/* ── Слот погрузки ────────────────────────────────────────────────
   Кадр после погрузки коробке не принадлежит, поэтому у него своя рамка —
   пунктирная, пока пусто, с силуэтом машины внутри. Пропорции широкие: в
   кадре борт, а не палета. */
.truckslot {
  position: relative;
  flex: 0 0 auto;
  align-self: center;
  width: 226px;
  height: 80px;
  margin: 0 auto 4px;
  border-radius: var(--r-ctl);
  border: 2px dashed var(--line);
  background: var(--card);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: var(--muted);
  transition: border-color .25s ease, box-shadow .25s ease;
}
.truckslot .truckart { width: 96px; height: 45px; opacity: .55; }
.truckslot .truckcap {
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
/* Очередь дошла до этого кадра — рамка сплошная и подсвечена, как грань. */
.truckslot.next {
  border-style: solid;
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 18px rgba(11, 107, 203, .3);
}
.truckslot.next .truckart { opacity: 1; }
/* Кадр снят — силуэт больше не нужен, в рамке лежит сам снимок. */
.truckslot.shot { border-style: solid; }
.truckslot.shot .truckart,
.truckslot.shot .truckcap { visibility: hidden; }
.truckslot .fslot { position: absolute; inset: 4px; }
.truckslot .fslot .frame {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  aspect-ratio: auto;
  border-radius: var(--r-chip);
}

/* ── Комментарий ко всей съёмке ──────────────────────────────────────
   Широкая строка над нижней панелью: замечание вроде «пришло без пломбы»
   относится ко всей отгрузке, и место у него должно быть своё, а не угол
   случайного кадра. Пустая — приглашение, заполненная — сам текст. */
.sesscm {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  width: calc(100% - var(--gap) * 2);
  min-height: var(--tap);
  margin: 2px var(--gap) 4px;
  padding: 0 12px;
  border-radius: var(--radius);
  border: 1px dashed var(--line);
  background: var(--card);
  color: var(--muted);
  text-align: left;
}
.sesscm svg { flex: 0 0 auto; }
.sesscm-b { flex: 1 1 auto; min-width: 0; }
.sesscm-t { display: block; font-size: var(--fs-body); font-weight: 700; color: var(--ink); }
.sesscm-s {
  display: block;
  font-size: var(--fs-note);
  margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sesscm:active { background: var(--accent-bg); }
/* Комментарий написан — строка перестаёт быть приглашением и становится фактом */
.sesscm.has { border-style: solid; border-color: var(--accent); color: var(--accent); }
.sesscm.has .sesscm-s { color: var(--ink); font-weight: 600; }

/* Строчка про снимки дела с сервера. Стоит между шагами и заголовком коробки:
   это ответ на вопрос «а не снял ли это уже кто-то», и задают его до первого
   нажатия на затвор. Мелко, но не мельче 13 px — ниже на складе не читают. */
.srvnote {
  flex: 0 0 auto;
  padding: 0 var(--gap) 2px;
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--ok);
  text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Не загрузились — это не поломка, а обстоятельство: тон спокойный. */
.srvnote.bad { color: var(--muted); font-weight: 600; letter-spacing: 0; }

/* Заголовок галереи: сколько снято из плана и сколько сверх него. Пилюля
   внизу отвечает за «что снимаем сейчас», а здесь — «сколько уже есть». */
.galcap {
  flex: 0 0 auto;
  /* Колонка съёмки тесная: сверху воздуха не даём, он уходит коробке. */
  padding: 0 var(--gap) var(--s1);
}

/* ── Коробка ────────────────────────────────────────────────────────
   Четыре боковые грани и верхняя, лёгкая перспектива, вид сверху-сбоку.
   Всё на градиентах: ни одной картинки, приложение живёт офлайн. */

/* Сцена коробки. Ленты кадров под ней больше нет, и всё, что она занимала,
   отошло сцене: коробка здесь единственное, по чему видно, что снято, а что
   нет, и смотреть на неё приходится через стекло в перчатке.
   Запас снизу — под тень: она выходит за габарит обёртки. */
.boxstage {
  position: relative;
  flex: 1 1 auto;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px 0 10px;
  perspective: 900px;
  perspective-origin: 50% 42%;
  overflow: hidden;
  /* Коробку крутят пальцем — горизонтальный ход браузеру не отдаём.
     Экран съёмки не прокручивается, отнимать у него нечего. */
  touch-action: none;
}

/* Стрелки листания сторон. О свайпе по коробке никто не догадывался, поэтому
   рядом стоит видимый способ сделать то же самое. Полупрозрачные и прижаты к
   краям сцены: цель нажатия полного размера, но коробку они не закрывают. */
.boxarrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  width: var(--tap);
  height: var(--tap);
  transform: translateY(-50%);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Непрозрачные и с собственной подложкой. Прежние .5 прозрачности
     превращали знак в 4.7:1 на тёмной теме и 3.2:1 на светлой — а стрелка
     наполовину лежит на картонной грани, где фон вообще не наш. Теперь под
     ней всегда карточный фон с рамкой, знак 14.6–17.8:1 в любом месте. */
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
  box-shadow: 0 1px 6px rgba(0, 0, 0, .22);
  transition: background-color .15s ease;
}
.boxarrow.left { left: 2px; }
.boxarrow.right { right: 2px; }
/* Правая — та же стрелка зеркально: два одинаковых знака в спрайте не нужны */
.boxarrow.right svg { transform: scaleX(-1); }
.boxarrow:active { background: var(--accent-bg); }

.boxwrap {
  --bw: 268px;    /* ширина (передняя грань) */
  --bh: 202px;    /* высота */
  --bd: 182px;    /* глубина (боковая грань) */
  position: relative;
  width: var(--bw);
  height: var(--bh);
  transform-style: preserve-3d;
}

/* Наклон отдельным слоем: поворот к грани идёт внутри него, иначе коробка
   при вращении заваливается набок. */
.boxtilt {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateX(-17deg) rotateY(-26deg);
  transition: transform .45s cubic-bezier(.34, .12, .2, 1);
}

/* Первый шаг — общий кадр. Коробка чуть дальше и развёрнута сильнее сверху:
   видно её целиком и видно крышку, на которую ляжет снимок. */
.boxwrap.overview .boxtilt { transform: rotateX(-31deg) rotateY(-26deg) scale(.88); }
/* Боковые грани на этом шаге не зовут к себе: снимаем не их. Приглушение
   мягче прежних .45 — те опускали номер до 1.3:1, и «не зовёт к себе»
   превращалось в «не видно совсем». */
.boxwrap.overview .bface:not(.bf-top) .fnum { opacity: .82; }

/* Шаг погрузки: коробка собрана и уступает место слоту под ней. Совсем
   убирать её нельзя — снятые кадры должны оставаться на виду. */
.boxwrap.aside .boxtilt { transform: rotateX(-24deg) rotateY(-26deg) scale(.66); }
.boxwrap.aside { opacity: .72; }

.box3d {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateY(var(--yaw, 0deg));
  transition: transform .5s cubic-bezier(.34, .12, .2, 1);
}
/* Пока коробку ведут пальцем, плавность мешает: она должна идти за рукой
   один в один. Доворот к грани на отпускании класс снимает — и он анимирован. */
.box3d.turning { transition: none; }

.bface {
  position: absolute;
  left: 0;
  top: 0;
  backface-visibility: hidden;
  border-radius: var(--r-mark);
  /* Фактура картона: продольные волокна плюс поперечный рубчик гофры. */
  background:
    linear-gradient(rgba(0, 0, 0, var(--shade, 0)), rgba(0, 0, 0, var(--shade, 0))),
    linear-gradient(rgba(255, 250, 240, var(--light, 0)), rgba(255, 250, 240, var(--light, 0))),
    repeating-linear-gradient(92deg, rgba(255, 255, 255, .07) 0 1px, rgba(120, 74, 30, .06) 1px 4px),
    repeating-linear-gradient(2deg, rgba(120, 74, 30, .05) 0 2px, rgba(255, 255, 255, .04) 2px 7px),
    linear-gradient(155deg, #dcb686 0%, #cda067 48%, #bd8c50 100%);
  box-shadow:
    inset 0 0 0 1px rgba(103, 64, 23, .45),
    inset 0 -16px 26px rgba(103, 64, 23, .16),
    inset 0 12px 18px rgba(255, 244, 224, .16);
}

/* Свет падает слева-сверху: грани разной яркости, иначе объём не читается. */
.bf-front { width: var(--bw); height: var(--bh); --shade: .02; transform: translateZ(calc(var(--bd) / 2)); }
.bf-back  { width: var(--bw); height: var(--bh); --shade: .26; transform: rotateY(180deg) translateZ(calc(var(--bd) / 2)); }
.bf-right { width: var(--bd); height: var(--bh); --shade: .19; left: calc((var(--bw) - var(--bd)) / 2); transform: rotateY(90deg) translateZ(calc(var(--bw) / 2)); }
.bf-left  { width: var(--bd); height: var(--bh); --shade: .10; left: calc((var(--bw) - var(--bd)) / 2); transform: rotateY(-90deg) translateZ(calc(var(--bw) / 2)); }
.bf-top {
  width: var(--bw);
  height: var(--bd);
  --shade: 0;
  --light: .17;   /* крышка ловит свет — самая светлая грань */
  top: calc((var(--bh) - var(--bd)) / 2);
  transform: rotateX(90deg) translateZ(calc(var(--bh) / 2));
}
/* Шов сложенных клапанов посередине крышки */
.bf-top::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 2px;
  background: rgba(103, 64, 23, .38);
}
/* Полоска скотча поверх шва */
.bf-top .tape {
  position: absolute;
  left: -3px; right: -3px;
  top: 50%;
  height: 26px;
  transform: translateY(-50%);
  background:
    linear-gradient(180deg, rgba(255, 252, 240, .5), rgba(226, 208, 176, .32) 42%, rgba(255, 252, 240, .42));
  box-shadow: 0 1px 2px rgba(103, 64, 23, .25), inset 0 0 0 1px rgba(255, 255, 255, .3);
  z-index: 2;
}

/* Слой содержимого крышки: сам кадр и подпись кладутся сюда, а скотч и шов
   остаются на месте — innerHTML самой грани стёр бы их начисто. */
.bf-top .fslot {
  position: absolute;
  inset: 0;
  z-index: 3;
}

/* Номер пустой грани: видно, какую сторону снимать. */
.bface .fnum {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 46 px вне шкалы кегля намеренно: это не подпись, а цифра на грани
     коробки, её размер задаёт рисунок. */
  font-size: 46px;
  font-weight: 800;
  /* Было rgba(103,64,23,.34) — 1.3–1.6:1 на картоне, номер грани угадывался,
     а не читался. Тёмно-коричневый держит 3.9–8.1:1 на всех пяти гранях,
     включая самую тёмную заднюю. */
  color: #2e1b06;
}
/* На крышке вместо номера слово: «общее» объясняет шаг без счётчика внизу. */
.bface .fnum.word {
  font-size: var(--fs-title);
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* Следующая по очереди грань подсвечена */
.bface.next {
  box-shadow:
    inset 0 0 0 3px var(--accent),
    inset 0 -16px 26px rgba(103, 64, 23, .16),
    0 0 18px rgba(11, 107, 203, .35);
}
/* Подсвеченная грань. Цвет номера здесь больше не меняем и правила для
   него не осталось: акцентный синий на картоне давал 2.0:1 в светлой теме
   и 1.0–1.5:1 в тёмной — то есть ровно на той грани, которую надо снять,
   цифра и пропадала. Что грань следующая, говорит её рамка (см. .bface.next
   выше): сплошная, акцентная, со свечением — этого видно с метра, а цифра
   остаётся читаемой. */

/* Кадр на грани — как фотография, вписанная в неё. */
.bface .frame {
  position: absolute;
  inset: 7px;
  width: auto;
  height: auto;
  aspect-ratio: auto;
  border-radius: var(--r-mark);
  border: 3px solid #fbf7f0;
  background: #fbf7f0;
  box-shadow: 0 2px 6px rgba(60, 35, 10, .45);
}
/* Кнопок на гранях коробки больше нет: после поворота крестик получался
   13×41 точки и стоял в пяти точках от стрелки поворота. Нажатие на грань
   открывает просмотрщик, там «Удалить» и «Комментарий» полноразмерные. */
.bface .frame .tag { font-size: var(--fs-note); }


/* Мягкая тень под коробкой. На тёмной теме чёрное по чёрному не читается,
   поэтому под коробку кладём чуть подсвеченный «пол» — на нём тень видно. */
.boxshadow {
  position: absolute;
  left: 50%;
  /* Ниже нижней грани: коробка из-за наклона выступает за габарит обёртки,
     и тень, поставленная вплотную, целиком уходит под неё. */
  bottom: -54px;
  width: calc(var(--bw) * 1.22);
  height: 40px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0) 68%);
}
@media (prefers-color-scheme: dark) {
  .boxstage { background: radial-gradient(56% 46% at 50% 64%, rgba(255, 255, 255, .055), transparent 72%); }
  .boxshadow { background: radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, .7), rgba(0, 0, 0, 0) 70%); }
}

/* Поворот к грани при prefers-reduced-motion гасит общее правило в конце
   файла (`* { transition: none }`), а паузу перед поворотом снимает app.js:
   без анимации показывать кадру «полсекунды на посмотреть» незачем. */

.frame {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-ctl);
  overflow: hidden;
  background: var(--line);
  border: 1px solid var(--line);
}
.frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.frame.sel { outline: 4px solid var(--accent); outline-offset: -4px; }

/* Кадр на «Разобрать» — настоящая кнопка: её выбирают, а не просто смотрят.
   Сброс кнопочных умолчаний, иначе браузер добавит свои отступы и рамку.
   Пунктирная рамка внутри — единственный признак того, что плитка вообще
   выбирается: раньше невыбранная выглядела просто картинкой, и человек
   упирался в две отключённые кнопки внизу, не понимая, чем их оживить. */
button.frame {
  display: block;
  width: 100%;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}
/* Только на «Разобрать»: в разделе администратора и на «Фото за день» кадры
   такие же, но их не выбирают — пунктир там обещал бы действие, которого нет. */
#sort-grid button.frame::after {
  content: '';
  position: absolute;
  inset: 5px;
  border: 2px dashed rgba(255, 255, 255, .55);
  border-radius: var(--r-chip);
  pointer-events: none;
}
#sort-grid button.frame[aria-pressed="true"]::after { display: none; }

.frame .kill {
  position: absolute;
  top: 4px; right: 4px;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .62);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
}
/* Подпись на кадре: время, кто снял, состояние. Две беды сразу.
   Первая — верх градиента был полностью прозрачен, и на светлом снимке
   (солнце от ворот) первая строка белого текста пропадала. Теперь к тому
   месту, где начинается текст, подложка уже плотная: белое держит 11.7:1
   даже поверх совершенно белого кадра.
   Вторая — обрезка. На плитке 112 px подписи «21:46 · в очереди» нужно
   128, и хвост срезался ВСЕГДА, даже на коротких. Две строки: подпись
   должна читаться целиком, а не намекать на себя. */
.frame .tag {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 10px 6px 5px;
  font-size: var(--fs-note);
  font-weight: 700;
  line-height: 1.25;
  color: #fff;
  background: linear-gradient(
    rgba(0, 0, 0, 0) 0,
    rgba(0, 0, 0, .62) 7px,
    rgba(0, 0, 0, .8) 13px,
    rgba(0, 0, 0, .84) 100%);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.frame .badge {
  position: absolute;
  left: 4px; top: 4px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-note);
  font-weight: 700;
  /* .74, а не .62: метка лежит на снимке, и на светлом кадре её белые буквы
     держат 10.1:1 вместо 6.2:1. */
  background: rgba(0, 0, 0, .74);
  color: #fff;
}
/* Кадр прошлой съёмки: виден, но неприкосновенен — крестика у него нет. */
.frame .badge.past { background: rgba(0, 0, 0, .8); }

/* Кружок автора в правом верхнем углу кадра: слева уже живут метки состояния,
   и спорить с ними за место незачем. Цвет из имени, белые буквы на насыщенном
   фоне читаются и на светлом снимке, и на тёмном. Белая обводка отделяет
   кружок от кадра, каким бы тот ни был. */
.frame .ava {
  position: absolute;
  right: 4px; top: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border-radius: var(--r-ctl);
  border: 1.5px solid rgba(255, 255, 255, .9);
  background: hsl(var(--h) 42% 38%);
  color: #fff;
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .3px;
}

/* Кадр, снятый другим телефоном и уже уехавший. Зелёная метка с галочкой —
   тот же язык, что у отправленных в очереди: «это уже на сервере». Отличать
   его от своего надо с одного взгляда, иначе кладовщик потянется переснимать
   то, что снимать не нужно. */
.frame .badge.srv {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: var(--ok);
  color: var(--on-ok);
}
/* Рамка чужого кадра — тем же зелёным: на грани коробки метка мелкая, а
   отличие должно читаться боковым зрением. */
.frame.srv { outline: 2px solid var(--ok); outline-offset: -2px; }

/* Карандаш комментария. Стоит в левом нижнем углу, там же, где начинается сам
   комментарий: значок и первая строка текста читаются как одна строка, а не
   как две разные пометки. Верхние углы заняты крестиком и признаками.

   Значок мельче 56 px намеренно: это подсказка на миниатюре, а не основной
   путь. Основной — нажать кадр и работать в просмотрщике, где и «Удалить», и
   «Комментарий» полного размера. */
.frame .note {
  position: absolute;
  left: 4px; bottom: 4px;
  width: 38px; height: 38px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .62);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  z-index: 3;
}
/* Комментарий уже есть — значок другой и цветной: видно без чтения подписи. */
.frame .note.has { background: var(--accent); color: var(--on-accent); }
/* Первые строки комментария ложатся справа от карандаша, а не под него. */
.frame.edit .tag { padding-left: 46px; min-height: 38px; }

/* Круглая кнопка съёмки — самая большая цель в приложении. */
.shutter {
  width: 96px; height: 96px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .25);
  flex: 0 0 auto;
}
.shutter:active { transform: scale(.96); }
.bottom.shoot { justify-content: space-between; padding-bottom: calc(12px + var(--safe-b)); }
/* Ширину задаёт панель, а не надпись: правая кнопка меняет слово по смыслу
   («Готово» → «Закрыть»), и если бы она подгонялась под текст, то на глазах
   прыгала бы вместе с «Галереей». На складе это читается как сбой, поэтому
   min-width: 0 — обе кнопки делят строку поровну, что бы на них ни стояло,
   а nowrap не даёт длинному слову развалить панель на две строки. */
.bottom.shoot .btn { flex: 1 1 0; min-width: 0; max-width: 34vw; white-space: nowrap; }


/* ── Состав документа ───────────────────────────────────────────────
   Строк бывает под полсотни, поэтому список простой и плотный: имя,
   характеристика мельче и серым, количество справа. */

.submeta {
  padding: 0 var(--gap) 10px;
  font-size: var(--fs-body);
  color: var(--muted);
}

/* Счётчик строк документа — та же рубрика, что и .grouphead (см. базу выше),
   свой только нижний отступ: под ним сразу идут строки состава. */
.itemcount { padding: var(--s1) var(--edge-in) var(--s2); }

/* Состав документа — таблица, а не плашка на каждую позицию. Плашки читались
   по одной, а состав сверяют со сборочным листом целиком: нужно, чтобы числа
   стояли столбцом и глаз шёл по нему сверху вниз. Текст мельче и обычной
   насыщенности: полсотни позиций жирным превращаются в сплошную чёрную стену,
   и в ней не видно ни одной. */
.itable {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
}
.itable td {
  padding: var(--s2) var(--s1);
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
/* Номер строки — то, по чему сверяются с бумагой. Выравнивание по левому краю,
   а не по правому: так первая цифра стоит на той же вертикали, что заголовок
   над таблицей и текст карточек выше по экрану. При правом выравнивании край
   столбца гулял бы между однозначными и двузначными номерами. */
.itable .it-n {
  width: 2.4em;
  padding-left: var(--edge-in);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.it-name { width: 100%; font-weight: 400; overflow-wrap: anywhere; }
/* Характеристика и содержание — со своей строки: в одну они путаются с именем,
   а имя у плитки бывает длиной в три слова и в пять. */
.it-char { display: block; font-size: var(--fs-note); font-weight: 400; color: var(--muted); margin-top: 2px; }
/* Числа столбцом: моноширинные цифры, чтобы разряды стояли под разрядами. */
.itable .it-qty {
  padding-right: var(--edge-in);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.it-qty span { color: var(--muted); font-weight: 400; margin-left: 3px; }
/* Срок строки — под количеством, а не отдельным столбцом: на телефоне четвёртый
   столбец съел бы наименование. Заказ поставщику приходит частями, и строки
   одного документа ждут разных дат; общий срок в шапке про это молчит, а именно
   по нему решают, всё ли приехало. */
.it-when { display: block; margin: 2px 0 0; font-size: var(--fs-note); color: var(--muted); font-weight: 400; }
/* Факт против плана: опоздание видно цветом, а не вычитанием в уме. Слово
   «факт» остаётся на месте в обоих случаях — цвет здесь второй признак, не
   единственный: на складе бывают дальтоники и выгоревшие экраны. */
.it-when b { font-weight: 600; }
.it-when .late { color: var(--warn); }
.it-when .ontime { color: var(--ok); }
/* Недопоставка: сколько из заказанного уже пришло. Только на незакрытых
   строках — на закрытых это повторяло бы количество строкой выше.
   Селектор с родителем намеренно: `.it-qty span` задаёт серый цвет и отступ
   слева, и одиночный класс ему проигрывает по специфичности. */
.it-qty .it-part {
  display: block;
  margin: 1px 0 0;
  font-size: var(--fs-note);
  color: var(--warn);
  font-weight: 600;
}
.it-qty .it-when { margin-left: 0; }

/* Связанные заказы под составом: для кого закуплено или из чего собирается.
   Отделены воздухом сверху — это ответ на другой вопрос, чем состав, и слипаться
   с последней строкой таблицы он не должен. */
.rel-head { margin-top: var(--s4); }
/* Строка связанного заказа — настоящая кнопка: по ней переходят в этот заказ.
   Ячейку отдаём ей целиком, свои отступы она держит сама, иначе цель нажатия
   оказалась бы меньше строки, которую человек видит. */
.itable .rel-cell { padding: 0; }
.rel-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  min-height: var(--tap);
  padding: var(--s2) var(--edge-in);
  font: inherit;
  color: inherit;
  text-align: left;
}
.rel-row:active { background: var(--accent-bg); }
.rel-b { flex: 1 1 auto; min-width: 0; }
/* Шеврон говорит, что строка ведёт дальше: без него она читается как подпись. */
.rel-go { flex: 0 0 auto; color: var(--muted); transform: scaleX(-1); }
/* Контрагент — это и есть ответ, поэтому он единственное, что здесь выделено. */
.rel-party { display: block; font-weight: 600; }

/* Состава нет — говорим честно и не мешаем снимать. */
.nodata {
  margin: var(--gap) 0;
  padding: var(--s4);
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-lead);
  font-weight: 600;
}
.nodata .sub { font-weight: 500; color: var(--muted); margin-top: 6px; font-size: var(--fs-body); }

/* Единственная кнопка внизу — делаем её крупнее обычной. */
.btn.big { min-height: 64px; font-size: var(--fs-head); }

/* ── Кадры, которые не могут уехать ──────────────────────────────────
   Пять кадров пропали вместе с хранилищем непривязанного приложения, и
   приложение об этом ни разу не сказало. Плашка стоит на главном экране и в
   очереди — в двух местах, куда кладовщик заходит за день десяток раз.

   Два тона намеренно. Ждём сети — спокойный серо-оранжевый: подождать, и всё
   уедет. Не привязано устройство — красный с полосой слева: само это не
   пройдёт, и до администратора дойти придётся. */
.stuckwarn {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: var(--gap) var(--gap) 0;
  padding: var(--s3);
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.35;
}
.stuckwarn svg { flex: 0 0 auto; }
.stuckwarn-t { flex: 1 1 auto; min-width: 0; }
.stuckwarn.hard {
  background: var(--stop-bg);
  color: var(--stop);
  box-shadow: inset 6px 0 0 var(--stop);
  padding-left: 18px;   /* 12 своих + 6 полосы: текст не залезает на неё */
  font-weight: 700;
}
/* Кнопка ввода кода — цель на 44 px: её жмут в перчатке и один раз в жизни,
   но именно она и есть выход из тупика. */
.stuckwarn-go {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--r-chip);
  background: var(--stop);
  color: var(--on-stop);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.stuckwarn-go:active { filter: brightness(.9); }

/* ── Очередь ────────────────────────────────────────────────────────── */

.qitem {
  display: flex;
  gap: 12px;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--gap);
}
.qitem img { width: 62px; height: 62px; object-fit: cover; border-radius: var(--r-chip); flex: 0 0 auto; background: var(--line); }
.qitem .body { flex: 1 1 auto; min-width: 0; }
.qitem .body .t { font-size: var(--fs-lead); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qitem .body .s { font-size: var(--fs-cap); margin-top: 2px; }
/* Строка ведёт на состав своего документа, и выглядеть должна нажимаемой:
   стрелка справа и отклик на палец — тем же цветом, что и карточки задач. */
.qitem[data-go] { cursor: pointer; }
.qitem[data-go]:active { background: var(--accent-bg); }
.qgo { flex: 0 0 auto; color: var(--muted); transform: scaleX(-1); }
.s.err { color: var(--stop); }
.s.wait { color: var(--warn); }
.s.done { color: var(--ok); }

details.sent { margin-top: var(--s4); }
details.sent > summary {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  font-weight: 700;
  font-size: var(--fs-lead);
  list-style: none;
}
details.sent > summary::-webkit-details-marker { display: none; }
details.sent[open] > summary { margin-bottom: var(--gap); }

/* ── Нижний лист (комментарий, дата, выбор заказа) ──────────────────── */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  background: rgba(0, 0, 0, .45);
}
.sheet[hidden] { display: none; }

/* Лист поднимается, а не возникает. До этого он появлялся рывком, и на
   экране съёмки это читалось как подмена экрана: только что была коробка —
   и вдруг чужая панель во весь низ.

   Анимируется только появление. Закрытие оставлено мгновенным намеренно:
   человек уже нажал «Готово», и ждать, пока панель уедет, ему незачем.
   Оба правила гасит общее `prefers-reduced-motion` в конце файла. */
@keyframes лист-поднялся {
  from { transform: translateY(14%); }
  to   { transform: none; }
}
@keyframes подложка-проявилась {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.sheet { animation: подложка-проявилась .16s ease both; }
.sheet .panel { animation: лист-поднялся .22s cubic-bezier(.2, .8, .2, 1) both; }
.sheet .panel {
  width: 100%;
  max-height: 86%;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border-radius: var(--r-big) var(--r-big) 0 0;
  box-shadow: var(--over);
  padding: var(--s4) var(--gap) calc(var(--s4) + var(--safe-b));
  gap: var(--gap);
  overflow-y: auto;       /* клавиатура кодов и крупный снимок бывают выше экрана */
}
.sheet h2 { margin: 0 0 4px; font-size: var(--fs-head); }
/* Заголовок по центру там, где он не подпись к полю, а шапка самого листа:
   имя сотрудника над кнопками во всю ширину и над строкой версии, которая
   по центру и так. Слева оно висело само по себе и читалось как обрывок. */
.sheet h2.mid { text-align: center; }
.sheet textarea, .sheet input[type="date"], .sheet input[type="time"] {
  width: 100%;
  min-height: var(--tap);
  padding: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg);
  font-size: var(--fs-lead);
}
.sheet textarea { min-height: 96px; resize: vertical; }
.sheet .row { display: flex; gap: var(--gap); }
.sheet .list { overflow-y: auto; flex: 1 1 auto; min-height: 120px; }
.sheethint { margin: 0; color: var(--muted); font-size: var(--fs-cap); }

/* Быстрые подсказки комментария. Набирать текст в перчатке почти никто не
   станет — а ткнуть в готовое слово станет, и замечание вообще появится.
   Поэтому подсказки полноразмерные цели, а поле ввода — уже для тех, кому
   нужны подробности. Нажатая добавляет слово, нажатая ещё раз — убирает своё:
   промах пальцем не должен приводить к вычищению букв. */
.hints { display: flex; flex-wrap: wrap; gap: var(--gap); }
.hint {
  min-height: var(--tap);
  padding: 0 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink);
  font-size: var(--fs-lead);
  font-weight: 600;
}
.hint[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* ── Просмотр кадра на весь экран ────────────────────────────────────
   Тёмный фон здесь не тема оформления, а работа: на белом полотне кадр со
   склада теряет тени и полутона, а решают именно по ним. Цвет один в обеих
   темах намеренно — это лайтбокс, а не экран приложения. */
.viewer {
  position: fixed;
  inset: 0;
  z-index: 35;               /* ниже нижнего листа: комментарий открывается поверх */
  display: flex;
  flex-direction: column;
  background: #0b0d10;
  color: #fff;
}
.vhead {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: calc(env(safe-area-inset-top, 0px) + 8px) var(--gap) 8px;
}
.vfacts { flex: 1 1 auto; min-width: 0; }
.vwho { font-size: var(--fs-head); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vsub { font-size: var(--fs-cap); color: #a9b2be; margin-top: 2px; line-height: 1.3; }
.vclose { border-color: rgba(255, 255, 255, .24); background: rgba(255, 255, 255, .08); color: #fff; }

/* Листание — родная прокрутка со scroll-snap: инерцию пальца и отмену на
   полпути браузер отработает лучше любого расчёта жеста. */
.vstage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.vstage::-webkit-scrollbar { display: none; }
.vslide {
  flex: 0 0 100%;
  width: 100%;
  scroll-snap-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
}
.vslide img { max-width: 100%; max-height: 100%; object-fit: contain; }

.vcm {
  flex: 0 0 auto;
  padding: 6px var(--gap);
  max-height: 22vh;
  overflow-y: auto;
  font-size: var(--fs-body);
}
.vcm-l { margin-top: 4px; overflow-wrap: anywhere; }
.vcm-t {
  color: #a9b2be;
  font-size: var(--fs-note);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  margin-right: 6px;
}
/* У черновика кнопок четыре — «Удалить», «Переснять», «Сторона» и
   комментарий, — и в одну строку на 390 px они не встают: «Изменить
   комментарий» самая длинная подпись в приложении. Поэтому ряд переносится, а
   не ужимает цели нажатия до ногтя. У сданного кадра кнопок остаётся две, и
   переносить нечего. */
.vbar {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
  padding: 10px var(--gap) calc(10px + var(--safe-b));
}
.vbar .btn {
  flex: 1 1 auto;
  /* Уже этого кнопка перестаёт читаться с метра, и перенос честнее сжатия. */
  min-width: 96px;
  padding: 0 12px;
  font-size: var(--fs-lead);
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .22);
  color: #fff;
}
/* «Удалить» и «Сторона» — слова короткие, ширину отдаём комментарию. */
.vbar .btn.danger { flex: 0 0 auto; background: rgba(179, 38, 30, .3); border-color: #f2726a; color: #ffb9b4; }
.vbar .btn.vface { flex: 0 0 auto; }
/* «Переснять» — то, ради чего кадр и открывают крупно у себя на съёмке:
   решение «годится или переснять» принимают именно по большому снимку. */
.vbar .btn.vredo {
  flex: 0 0 auto;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
/* Комментарий забирает остаток строки, а когда его не осталось — уходит на
   вторую целиком: короче эта подпись не станет. */
.vbar .btn.wide { flex: 1 1 160px; }

/* Кнопки «Удалить» у кладовщика на уехавшем кадре нет — вместо неё эта
   строчка. Молчащая полоса читалась бы как поломка. */
.vwhy {
  flex: 0 0 auto;
  padding: 4px var(--gap) 0;
  font-size: var(--fs-cap);
  color: #cfd6df;
}
.vwhy[hidden] { display: none; }

/* ── Администратор ──────────────────────────────────────────────────
   Раздел намеренно скучный и плотный: им пользуются раз в неделю,
   и важнее не запутать, чем понравиться. Никаких своих цветов —
   те же токены, что и на остальных экранах. */

/* Вкладок четыре. Зазор между ними такой же, как везде: 6 px — это промах
   перчаткой в соседний раздел. А то, что «Состояние» в 17 px не влезает на
   узком экране, — вопрос ширины, и решается он в запросе по ширине внизу
   файла, а не уменьшенным кеглем для всех подряд. */
.admintabs { padding: 0 var(--gap) var(--s2); gap: var(--gap); }
.admintabs .tab { padding: 0 2px; }

.scroll.admin { padding-bottom: calc(var(--safe-b) + 32px); }

/* Выбор дня: стрелки по краям, дата посередине — читается одним взглядом */
.adbar { display: flex; align-items: center; gap: var(--gap); margin-bottom: var(--gap); }
.adday { flex: 1 1 auto; text-align: center; font-size: var(--fs-lead); font-weight: 700; }
.adnext svg { transform: rotate(180deg); }
/* Та же история, что и у .btn: .35 прозрачности давало 1.75:1 — стрелка
   «следующий день» выглядела не отключённой, а стёртой. */
.iconbtn[disabled] {
  opacity: 1;
  background: var(--line);
  border-color: var(--line);
  color: var(--muted);
}

.adfield { display: block; margin-bottom: var(--gap); }
.adfield > span { display: block; font-size: var(--fs-cap); color: var(--muted); margin-bottom: 4px; }
.adfield select, .adfield input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  font-size: var(--fs-lead);
}

/* Пояснение под полем: зачем это число и что оно двигает. */
.adhint { display: block; font-size: var(--fs-cap); color: var(--muted); margin-top: 4px; }

.adcount { font-size: var(--fs-cap); color: var(--muted); margin: 2px 0 8px; }

.adgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--gap);
}
.adgrid .frame { width: 100%; padding: 0; display: block; }

.adcenter { margin-top: var(--s4); display: flex; justify-content: center; }

/* Пояснение мелким, но не мельче 14 px — правило экрана. */
.adnote { font-size: var(--fs-cap); color: var(--muted); line-height: 1.4; margin: 4px 0 12px; }
.adnote.bad { color: var(--stop); font-weight: 700; }

/* ── Люди и коды ─────────────────────────────────────────────────── */

.adperson {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: var(--gap);
}
.adname { font-size: var(--fs-lead); font-weight: 700; }
.adpills { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.pill.accent { background: var(--accent-bg); color: var(--accent); }
/* Действия в строку с переносом: три кнопки по 56 px на 360 px не влезают */
.adacts { display: flex; flex-wrap: wrap; gap: var(--gap); margin-top: var(--s2); }
.adacts .btn { flex: 1 1 auto; font-size: var(--fs-body); padding: 0 12px; }

/* ── Состояние ───────────────────────────────────────────────────── */

/* Цветная грань — внутренней тенью, а не рамкой: рамка сдвигала бы текст
   этих строк вправо относительно соседних блоков экрана. */
.adstate {
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: inset 6px 0 0 var(--line);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--gap);
}
.adstate-t { font-size: var(--fs-cap); color: var(--muted); }
/* Цвет дублируется словами в самой строке: «молчит с 13:40» читается и без него */
.adstate-v { font-size: var(--fs-head); font-weight: 700; margin-top: 2px; }
.adstate.ok { box-shadow: inset 6px 0 0 var(--ok); }
.adstate.warn { box-shadow: inset 6px 0 0 var(--warn); }
.adstate.stop { box-shadow: inset 6px 0 0 var(--stop); }
.adstate.ok .adstate-v { color: var(--ok); }
.adstate.warn .adstate-v { color: var(--warn); }
.adstate.stop .adstate-v { color: var(--stop); }
/* Ответ на «Проверить обновление» — словами, там же, где кнопка. Тостом его
   давать нельзя: тост исчезает, а ответ читают и перечитывают вслух по
   телефону. */
.adsay {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: var(--r-ctl);
  background: var(--bg);
  font-size: var(--fs-body);
}

/* ── Привязка телефона ───────────────────────────────────────────── */

/* Код читают с чужого экрана, через стол и не всегда в очках. Поэтому крупно,
   моноширинно и с разрядкой: похожих знаков в наборе нет, но G и 6 всё равно
   надо различить с полуметра. */
.paircode-big {
  margin: 8px 0 4px;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: clamp(34px, 12vw, 48px);
  font-weight: 700;
  letter-spacing: .14em;
  text-align: center;
  color: var(--ink);
  user-select: all;              /* долгое нажатие выделяет код целиком */
}

/* Переключатель «общий телефон»: цель нажатия — вся строка, а не квадратик
   в 16 px, до которого в перчатке не добраться. */
.adcheck {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  margin-top: 8px;
  font-size: var(--fs-lead);
  color: var(--ink);
}
.adcheck input { width: 26px; height: 26px; flex: 0 0 auto; accent-color: var(--accent); }
.adcheck small { color: var(--muted); font-size: var(--fs-cap); }

/* Поле ввода кода на экране входа: тот же крупный моноширинный набор, что и у
   администратора на экране — человек сверяет то, что видит, с тем, что набрал. */
.paircode {
  display: block;
  width: 100%;
  margin: 12px 0 4px;
  padding: 10px 12px;
  min-height: var(--tap);
  box-sizing: border-box;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--fs-num);
  font-weight: 700;
  letter-spacing: .18em;
  text-align: center;
  text-transform: uppercase;
}

/* Объяснение под клавиатурой входа: почему код кладовщика сейчас не подойдёт
   и что с этим делать. Больше человеку прочитать негде. */
#login-pairbox { max-width: 340px; margin: var(--s4) auto 0; }
.pairwhy {
  margin: 0 0 10px;
  font-size: var(--fs-cap);
  line-height: 1.4;
  color: var(--muted);
  text-align: left;
}

/* ── Крупный просмотр снимка ─────────────────────────────────────── */

.adbig {
  flex: 0 0 auto;         /* в колонке листа картинка иначе схлопывается */
  width: 100%;
  /* Ниже — причина и кнопки; их не должно приходиться искать прокруткой */
  max-height: 32vh;
  object-fit: contain;
  border-radius: var(--r-ctl);
  background: var(--bg);
}
.adfacts { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: var(--fs-body); }
.adfacts dt { color: var(--muted); }
.adfacts dd { margin: 0; overflow-wrap: anywhere; }

/* ── Место кадра ────────────────────────────────────────────────────
   Список мест плана: занятое место так и подписано, а не отказывает молча.
   Строки — те же карточки, что в списке задач: рука уже знает их размер. */
.facerow .num { font-size: var(--fs-head); }

/* Обмен местами показываем картинками: два кадра рядом и куда какой поедет.
   Словами это описание из трёх придаточных, а решение принимают глазами. */
.swaprow { display: flex; gap: var(--gap); }
.swapone { flex: 1 1 0; min-width: 0; margin: 0; }
.swapone img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--r-ctl);
  background: var(--line);
}
.swapone figcaption { margin-top: 4px; font-size: var(--fs-note); color: var(--muted); text-align: center; }

/* Удаление — единственное необратимое действие раздела, и оно выглядит так */
.btn.danger { background: var(--stop-bg); border-color: var(--stop); color: var(--stop); }
/* Кнопка во всю ширину нижнего листа (у .wide другая задача — делить строку) */
.btn.block { width: 100%; }
/* «Отмена» в меню сотрудника. Соседняя кнопка закрывает смену, и промах по
   ней в перчатке стоит повторного входа всей бригаде. */
.wm-cancel { margin-top: var(--s4); }

/* Оверлей сканера QR */
.scan { position: fixed; inset: 0; z-index: 50; background: #000; display: flex; flex-direction: column; }
.scan[hidden] { display: none; }
.scan video { flex: 1 1 auto; width: 100%; object-fit: cover; }
.scan .bar { padding: 12px var(--gap) calc(12px + var(--safe-b)); background: #000; display: flex; gap: var(--gap); align-items: center; }
.scan .hint { color: #fff; flex: 1 1 auto; font-size: var(--fs-lead); }

/* Тост — единственный «модальный» элемент, и тот сам исчезает. */
/* Тост — единственный «модальный» элемент, и тот сам исчезает. Внутри может
   оказаться кнопка отмены («Вернуть» после удаления кадра), поэтому это
   строка из текста и цели нажатия, а не просто текст. */
.toast {
  position: fixed;
  left: var(--gap); right: var(--gap);
  bottom: calc(var(--bar-h) + var(--safe-b) + 60px);
  /* Выше всего, включая видоискатель приложения (70): сообщения о кадре
     приходят как раз во время съёмки, и прятать их за камерой нельзя. */
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  padding: 10px var(--s4);
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--bg);
  font-size: var(--fs-lead);
  font-weight: 600;
  text-align: center;
}
/* Текст занимает всё, что не забрала кнопка. */
.toast-t { flex: 1 1 auto; min-width: 0; }
/* «Вернуть». Единственный способ отменить удаление, живёт секунды — значит,
   настоящая цель нажатия в 44 px, а не подчёркнутое слово. Цвета вывернуты
   наизнанку относительно тоста: на тёмной подложке это светлая плашка,
   16.3:1, её видно раньше, чем прочитан сам текст. */
.toast-a {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 var(--s4);
  border-radius: var(--r-chip);
  background: var(--bg);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 700;
  white-space: nowrap;
}
.toast-a:active { background: var(--line); }
.toast[hidden] { display: none; }

/* Полоса состояния сети/демо поверх всего */
.flag {
  padding: 6px var(--gap);
  font-size: var(--fs-cap);
  font-weight: 700;
  text-align: center;
  background: var(--warn-bg);
  color: var(--warn);
}
.flag[hidden] { display: none; }

/* Окно «есть новая версия». Показывается только тогда, когда обновиться само
   приложение не может: человек на экране съёмки или кадры ещё не сданы.
   Раньше на этом месте была тонкая полоса сверху — её не замечали неделями,
   и чтобы получить новую версию, приложение закрывали и ставили заново.
   Окно поперёк экрана мимо взгляда не проходит.
   Навсегда оно ничего не загораживает: «Позже» убирает его на десять минут,
   а не до следующей установки. */
.upd {
  position: fixed;
  inset: 0;
  /* Выше нижнего листа (40) и сканера (50), ниже тоста (60): короткое
     сообщение вроде «кадр отправлен» должно читаться и поверх окна. */
  z-index: 55;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gap);
  background: rgba(0, 0, 0, .5);
}
.upd-p {
  width: 100%;
  max-width: 420px;
  max-height: 90%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  padding: var(--s5) var(--gap) var(--s4);
  border-radius: var(--r-big);
  background: var(--card);
}
.upd-h { margin: 0; font-size: var(--fs-title); }
/* Пояснение здесь не украшение: без него «Обновить» посреди съёмки читается
   как «потерять снятое», и нажимать его никто не станет. */
.upd-s { margin: 0 0 4px; font-size: var(--fs-body); color: var(--muted); }
/* «Позже» намеренно тише: выбор по умолчанию — обновиться. */
.upd-later { background: transparent; border-color: transparent; color: var(--muted); }

/* Версия оболочки мелкой строкой — на входе и в меню сотрудника. */
.verline { margin-top: var(--s5); text-align: center; font-size: var(--fs-note); color: var(--muted); }
/* В нижнем листе зазор уже задан самим листом — свой отступ здесь лишний. */
.sheet .verline { margin: 0; }

/* ── Низкие экраны ───────────────────────────────────────────────────
   Блок стоит в самом конце файла намеренно. Раньше он лежал в середине,
   до объявлений, которые он перебивает, и половина его правил молча не
   работала: базовое правило шло ниже и выигрывало одинаковой специфичностью. */

/* Экраны пониже. Съёмка — единственный экран без прокрутки: коробка, слот
   погрузки и строка комментария обязаны уместиться целиком. На отгрузке в
   колонке на один блок больше, и на 667 px в высоту коробке срезало верх.
   Поэтому на низких экранах всё сжимается разом. */
@media (max-height: 800px) {
  .boxwrap { --bw: 224px; --bh: 168px; --bd: 152px; }
  .boxstage { min-height: 168px; }
  .truckslot { width: 196px; height: 70px; }
  .truckslot .truckart { width: 76px; height: 36px; }
}
/* Самый тесный случай: экран 667 px, отгрузка (шесть шагов), слот погрузки и
   строка комментария. Место отбираем у всех понемногу, чтобы коробка осталась
   коробкой: она здесь единственное, по чему видно, что снято, а что нет. */
@media (max-height: 700px) {
  .boxwrap { --bw: 176px; --bh: 132px; --bd: 118px; }
  .boxstage { min-height: 132px; }
  .truckslot { width: 176px; height: 56px; }
  .truckslot .truckart { width: 62px; height: 28px; }
  .truckslot .truckcap { font-size: var(--fs-note); }
  .bface .fnum { font-size: 34px; }
  .bface .fnum.word { font-size: var(--fs-head); }
  .boxhint { padding: 3px var(--gap) 0; font-size: var(--fs-body); }
  .galcap { padding: 0 var(--gap) 2px; }
  .sesscm { min-height: 50px; margin: 0 var(--gap) 2px; }
  .sesscm-t { font-size: var(--fs-cap); }
  .sesscm-s { font-size: var(--fs-note); }
  /* Сетка кадров. На 360x640 в окно 294 px не влезал второй ряд из трёх
     плиток: содержимому нужно было 346, и о хвосте никто не догадывался.
     Квадратная миниатюра вместо 3:4 отдаёт 40 px на ряд — план из шести
     мест виден целиком, без прокрутки. Кадр от этого не страдает: в сетке
     он и так обрезан по object-fit, а решают по крупному просмотру. */
  .gallery.grid .frame,
  .gallery.grid .gslot { aspect-ratio: 1 / 1; }
  /* Шапка съёмки отдаёт сетке весь воздух, какой может. Номер и слово
     операции остаются в полном кегле — по ним сверяют, тот ли это
     документ, — а отступы вокруг них ужимаются. */
  .shot-top { padding: var(--s1) var(--gap); gap: var(--s2); }
  .numrow { row-gap: 0; }
}

/* ── Узкие экраны ────────────────────────────────────────────────────
   До этой правки в файле не было НИ ОДНОГО запроса по ширине: все пять
   были по высоте, а решения «на 360-пиксельном экране» и «на 390» стояли
   прямо в базовых правилах и действовали на планшете так же, как на
   раскладушке. Точек две: 360 — обычный узкий Android, 340 — сложенный
   телефон и самые старые iPhone. Всё, что здесь лежит, зависит именно от
   ширины: сколько слов помещается в строку. */

@media (max-width: 360px) {
  /* Четвёртая вкладка «Состояние» в основном кегле сюда не встаёт. */
  .admintabs .tab { font-size: var(--fs-body); }
  /* Нижняя панель делится с счётчиком очереди, и «Снять без заказа» надо
     оставить одной строкой: ужимаем подпись, а не режем её. */
  .free-shot { padding: 0 var(--s2); font-size: var(--fs-body); }
  /* Две кнопки съёмки делят строку поровну — на узком им нужны все 50%. */
  .bottom.shoot .btn { max-width: 50%; padding: 0 var(--s2); }
  /* Имя сотрудника в шапке: на узком экране важнее вкладки, а не подпись. */
  /* Дата на кнопке «Фото за день» уступает ступень названию кнопки: иначе
     на 360 px ей не хватает 10 px и название ломается на две строки. */
  .homenum.day { font-size: var(--fs-note); }
  /* Панель «Разобрать»: три кнопки в 320 px не встают, «Указать заказ»
     уезжала за край. Подпись теряет средняя — она реже всех нужна и её
     значок однозначен; название остаётся в ярлыке для озвучки. */
  #sort-gallery .btn-t { display: none; }
  #s-sort .bottom .btn { padding: 0 var(--s3); font-size: var(--fs-body); }
}

@media (max-width: 340px) {
  /* Ниже этого подпись кнопки уже не сокращается — переносим её на две
     строки, но с плотным межстрочным, чтобы панель осталась в 76 px. */
  .free-shot { white-space: normal; line-height: 1.1; }
  /* В просмотрщике кнопок четыре, и 96 px на каждую здесь уже нет. */
  .vbar .btn { min-width: 88px; padding: 0 var(--s2); }
  /* Три фильтра в 320 px не встают: ряд прокручивается вбок, но третий чипс
     при этом стоит обрезанным, и его просто не видно. Ужимаем поля, а не
     подписи: «Я снимал» короче уже не станет. */
  .chip { padding: 0 var(--s3); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}


/* display:flex у кнопок и панелей перебивает браузерное [hidden]. Правило одно
   на всё приложение: иначе каждый новый скрываемый блок ловит эту же ловушку. */
[hidden] { display: none !important; }

/* Кнопка «Разобрать» на главном: не действие по расписанию, а сигнал, что
   остались непривязанные кадры. Отсюда предупреждающий цвет. */
.homebtn.warnbtn { border-color: var(--warn); }
.homebtn.warnbtn .homet { color: var(--warn); }
.homebtn.warnbtn .homenum { color: var(--warn); }

/* ── Заготовка списка ────────────────────────────────────────────────
   Что видно, пока первый ответ сервера в пути. Силуэт карточек, а не
   надпись «загрузка»: пустой экран со словами читается как отказ, а
   силуэт — как «сейчас будет».

   Блик намеренно медленный и почти незаметный (1.4 с, размах 6 %): его
   задача — показать, что экран жив, а не привлечь взгляд. Общее правило
   prefers-reduced-motion в конце файла гасит его целиком, и остаются
   просто серые прямоугольники — они и сами по себе работают. */
.skel { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s2) 0; }
.skel-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: var(--lift);
  padding: var(--s3);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.skel-l {
  height: 14px;
  border-radius: var(--r-mark);
  background: var(--line);
  animation: заготовка-дышит 1.4s ease-in-out infinite;
}
.skel-l.w1 { width: 58%; height: 18px; }
.skel-l.w2 { width: 34%; }
/* Вторая строка отстаёт на треть такта: волна идёт по карточке сверху вниз,
   как взгляд, а не мигает всей карточкой разом. */
.skel-l.w2 { animation-delay: .45s; }
@keyframes заготовка-дышит {
  0%, 100% { opacity: 1; }
  50% { opacity: .94; }
}
