/* Sklad MOF — общие design tokens и переиспользуемые компоненты.
   Источник: drafts/module9_design_spec.md (раздел 1), выжимка из
   design_handoff hifi-прототипа. Шрифт — системный стек, без Google
   Fonts CDN (CSP в проде без внешних исключений, see main.py). */

:root {
  --red: oklch(0.5 0.19 25);
  --red-hover: oklch(0.42 0.19 25);
  --sidebar-bg: oklch(0.19 0.01 30);
  --sidebar-border: oklch(0.28 0.01 30);
  --sidebar-text: oklch(0.72 0.01 30);
  --sidebar-dot: oklch(0.5 0.01 30);
  --sidebar-muted: oklch(0.6 0.01 30);
  --sidebar-hover: oklch(0.24 0.01 30);
  --page-bg: oklch(0.98 0.004 80);
  --text: oklch(0.22 0.01 260);
  --text-secondary: oklch(0.45 0.01 260);
  --text-muted: oklch(0.52 0.01 260);
  --text-quiet: oklch(0.55 0.01 260);
  --card-border: oklch(0.91 0.006 80);
  --input-border: oklch(0.88 0.006 80);
  --row-border: oklch(0.95 0.006 80);
  --thead-bg: oklch(0.98 0.004 80);
  --scrollbar-thumb: oklch(0.85 0.006 80);
  --option-hover: oklch(0.96 0.006 80);

  --badge-ok-bg: oklch(0.94 0.05 145); --badge-ok-fg: oklch(0.4 0.13 145);
  --badge-info-bg: oklch(0.94 0.03 250); --badge-info-fg: oklch(0.42 0.13 250);
  --badge-warn-bg: oklch(0.95 0.04 80); --badge-warn-fg: oklch(0.45 0.13 80);
  --badge-danger-bg: oklch(0.95 0.04 40); --badge-danger-fg: oklch(0.5 0.15 40);
  --badge-neutral-bg: oklch(0.95 0.006 80); --badge-neutral-fg: oklch(0.45 0.01 260);

  --dot-green: oklch(0.6 0.14 145);
  --dot-blue: oklch(0.6 0.12 250);
  --dot-amber: oklch(0.7 0.15 80);
  --dot-red: oklch(0.55 0.19 25);

  --shadow-anchor: 0 12px 32px rgba(0,0,0,0.14);
  --shadow-anchor-strong: 0 12px 32px rgba(0,0,0,0.18);
  --shadow-modal: 0 20px 60px rgba(0,0,0,0.25);
}

/* Светлая тема — Фаза 1 (10.09.2026, ТЗ "Сворачиваемый сайдбар + тема"):
   ТОЛЬКО сайдбар. Раздел "Дашборд" уже использует общие светлые токены
   (--page-bg/--text/--card-border и т.д.) без единого dark-специфичного
   переопределения — проверено grep'ом перед реализацией, отдельных правил
   для него здесь нет, потому что реально нечего переключать. Остальные
   разделы вообще не тронуты — на них нет ни одного [data-theme="light"]
   правила, значит переключатель на них физически не может подействовать.
   Значения — НЕ новая палитра, а переиспользование уже существующих
   (проверенных на контраст) --text/--text-secondary/--card-border/
   --option-hover токенов основного (всегда светлого) контента, а не
   изобретение параллельного набора цветов. */
:root[data-theme="light"] {
  --sidebar-bg: oklch(0.985 0.003 80);
  --sidebar-border: var(--card-border);
  --sidebar-text: var(--text);
  --sidebar-dot: var(--text-muted);
  --sidebar-muted: var(--text-secondary);
  --sidebar-hover: var(--option-hover);
}
.nav-item:hover { background: var(--sidebar-hover); }

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  color: var(--text);
  background: var(--page-bg);
}
a { color: var(--red); }
a:hover { color: var(--red-hover); }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 8px; }
button { font-family: inherit; }
[hidden] { display: none !important; }

/* !important на обеих базовых утилитах — иначе компонентный класс с той
   же специфичностью (пример: .card-list{display:flex}, app.css ниже),
   идущий позже в каскаде, перебивает display:none и карточки остаются
   видны на десктопе одновременно с таблицей (баг найден 2026-08-06 при
   live-проверке Отгрузки, но затрагивал все разделы с card-list). Для
   .desktop-only внутри медиа-запроса !important уже был — не хватало
   симметричной пары для базового (десктопного) состояния .mobile-only. */
.mobile-only { display: none !important; }
.desktop-only { display: block; }
@media (max-width: 900px) {
  .desktop-only { display: none !important; }
  .mobile-only { display: block !important; }
}

/* --- Layout shell -------------------------------------------------- */

.app-shell { display: flex; height: 100vh; width: 100%; overflow: hidden; position: relative; }

.sidebar {
  --sidebar-w-open: 236px; --sidebar-w-collapsed: 76px;
  width: var(--sidebar-w-open); height: 100vh; background: var(--sidebar-bg); flex-shrink: 0;
  display: flex; flex-direction: column;
  transition: width .28s cubic-bezier(.4,0,.2,1);
}
.sidebar__logo {
  padding: 22px 20px 18px; border-bottom: 1px solid var(--sidebar-border);
}
.sidebar__logo img { height: 38px; width: auto; object-fit: contain; filter: brightness(1.15); }
.sidebar__logo-mark { display: none; height: 34px; width: 34px; border-radius: 50%; }
.sidebar__subtitle {
  font-size: 11px; letter-spacing: 0.06em; color: var(--sidebar-muted);
  margin-top: 6px; text-transform: uppercase;
}
.sidebar__nav { flex: 1; overflow-y: auto; padding: 12px 10px; }
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border-radius: 10px; margin-bottom: 2px; cursor: pointer; font-size: 13.5px;
  font-weight: 500; color: var(--sidebar-text); background: none; border: none;
  width: 100%; text-align: left; position: relative; white-space: nowrap;
}
.nav-item__icon { width: 20px; height: 20px; flex-shrink: 0; display: flex; color: var(--sidebar-dot); }
.nav-item__icon svg { width: 100%; height: 100%; }
.nav-item:hover .nav-item__icon { color: var(--sidebar-text); }
.nav-item.is-active { font-weight: 600; color: #fff; background: var(--red); }
.nav-item.is-active .nav-item__icon { color: #fff; }
.nav-item__label { overflow: hidden; text-overflow: ellipsis; }
.sidebar__footer {
  padding: 12px 12px 14px; border-top: 1px solid var(--sidebar-border);
  display: flex; align-items: center; gap: 8px;
}
.sidebar__footer-user { flex: 1; overflow: hidden; }
.sidebar__user { font-size: 13px; color: var(--sidebar-text); font-weight: 600; margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar__logout {
  background: none; border: 1px solid var(--sidebar-border); color: var(--sidebar-text);
  font-size: 12.5px; padding: 6px 12px; border-radius: 8px; cursor: pointer; margin-top: 8px;
}
.sidebar__collapse-btn {
  flex-shrink: 0; width: 34px; height: 34px; border-radius: 8px;
  border: 1px solid var(--sidebar-border); background: none; color: var(--sidebar-text);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.sidebar__collapse-btn:hover { color: var(--sidebar-text); border-color: var(--sidebar-text); }
.sidebar__collapse-btn svg { transition: transform .28s cubic-bezier(.4,0,.2,1); }

/* Свёрнутое состояние — ТОЛЬКО десктоп (>=901px, см. медиа-запрос ниже
   sidebar.is-collapsed переопределяется обратно на мобильной ширине):
   мобильный сайдбар — всегда полноразмерный off-canvas оверлей, "свёрнуть
   в иконки" там не имеет смысла (см. .sidebar.is-open ниже) и не должно
   протекать из сохранённого десктопного предпочтения пользователя. */
@media (min-width: 901px) {
  .sidebar.is-collapsed { width: var(--sidebar-w-collapsed); }
  .sidebar.is-collapsed .sidebar__logo { padding: 20px 0 14px; display: flex; justify-content: center; }
  .sidebar.is-collapsed .sidebar__logo-full,
  .sidebar.is-collapsed .sidebar__subtitle { display: none; }
  .sidebar.is-collapsed .sidebar__logo-mark { display: block; }
  .sidebar.is-collapsed .nav-item { justify-content: center; padding: 11px 0; gap: 0; }
  .sidebar.is-collapsed .nav-item__label { display: none; }
  .sidebar.is-collapsed .sidebar__footer-user { display: none; }
  .sidebar.is-collapsed .sidebar__footer { justify-content: center; }
  .sidebar.is-collapsed .sidebar__collapse-btn svg { transform: rotate(180deg); }
}

/* Подсказка при наведении на иконку в свёрнутом виде — один переиспользуемый
   элемент, position:fixed (не ::after внутри .nav-item): .sidebar__nav
   скроллится (overflow-y:auto), из-за чего браузер сам вычисляет
   overflow-x как auto тоже (спека — оба overflow-* не могут быть visible,
   если хоть один не visible) и обрезал бы всплывающую подсказку, которая
   должна выходить ЗА пределы узкой 76px-колонки. position:fixed полностью
   выходит из-под любого overflow предков, координаты — из
   getBoundingClientRect() наведённого пункта (см. core.js). */
.sidebar-tooltip {
  position: fixed; background: oklch(0.16 0.006 30); color: #f0e9e5;
  border: 1px solid var(--sidebar-border); padding: 6px 10px; border-radius: 7px;
  font-size: 12.5px; font-weight: 600; white-space: nowrap; pointer-events: none;
  box-shadow: 0 8px 20px -6px rgba(0,0,0,0.5); z-index: 60;
  opacity: 0; transition: opacity .12s;
}
.sidebar-tooltip.is-visible { opacity: 1; }

.mobile-header {
  display: none; align-items: center; gap: 12px; padding: 12px 16px;
  background: #fff; border-bottom: 1px solid var(--card-border); flex-shrink: 0;
}
.mobile-header img { height: 24px; width: auto; object-fit: contain; }
.mobile-header__title { margin-left: auto; font-size: 13px; font-weight: 600; color: var(--text-secondary); }
.burger {
  width: 38px; height: 38px; border-radius: 10px; border: 1px solid var(--input-border);
  background: #fff; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; cursor: pointer;
}
.burger span { width: 16px; height: 2px; background: oklch(0.3 0.01 260); }

.backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 40;
}

.main-col { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100vh; }
.main-content { flex: 1; overflow-y: auto; padding: clamp(16px,3vw,36px); }

@media (max-width: 900px) {
  .sidebar {
    position: fixed; top: 0; left: 0; z-index: 50; width: 280px; height: 100vh;
    transform: translateX(-100%); transition: transform .2s ease;
  }
  .sidebar.is-open { transform: translateX(0); }
  .mobile-header { display: flex; }
  .app-shell.is-mobile-nav-open .backdrop { display: block; }
  /* Сворачивание в иконки — только десктоп (см. @media min-width:901px
     выше) — на мобильном сайдбар всегда полноразмерный off-canvas,
     кнопка сворачивания здесь не нужна и не должна путать. */
  .sidebar__collapse-btn { display: none; }
}
@media (min-width: 901px) {
  .backdrop { display: none !important; }
}

/* --- Section headings ------------------------------------------------ */

.section-title {
  font-size: 24px; font-weight: 800; margin: 0; color: #fff; background: var(--red);
  display: inline-block; padding: 8px 18px; border-radius: 10px;
}
.section-title--lg { font-size: 26px; }
.section-header {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 18px;
}
.section-sub { font-size: 13px; color: var(--text-muted); margin-top: 4px; }

/* --- Buttons ----------------------------------------------------------- */

.btn {
  border: none; border-radius: 10px; font-size: 13.5px; font-weight: 600;
  cursor: pointer; padding: 10px 18px;
}
.btn-primary { background: var(--red); color: #fff; }
.btn-primary:hover { background: var(--red-hover); }
.btn-secondary {
  background: #fff; color: var(--text-secondary); border: 1px solid var(--input-border);
  padding: 9px 16px; border-radius: 9px; font-size: 13px; font-weight: 400; cursor: pointer;
}
.btn-icon {
  background: var(--red); color: #fff; border: 1px solid var(--red);
  width: 42px; height: 42px; border-radius: 10px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.btn-icon:hover { background: var(--red-hover); border-color: var(--red-hover); }
.btn-row-action {
  background: none; border: 1px solid oklch(0.85 0.006 80); color: var(--text-secondary);
  width: 30px; height: 30px; border-radius: 7px; font-size: 13px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-row-action svg { width: 15px; height: 15px; flex-shrink: 0; }
.btn-row-action--wide {
  width: auto; padding: 6px 10px; border-radius: 7px; font-size: 12px;
  gap: 6px; /* icon+текст (2026-09-14, Н7, Отгрузка) — зазор между svg и лейблом,
               не влияет на icon-only .btn-row-action без --wide. */
}
.btn-updown {
  background: none; border: 1px solid oklch(0.85 0.006 80); border-radius: 6px;
  width: 26px; height: 26px; cursor: pointer; font-size: 12px;
}

/* --- Inputs / custom dropdowns ----------------------------------------- */

.input {
  padding: 9px 12px; border: 1px solid var(--input-border); border-radius: 9px;
  font-size: 13px; font-family: inherit; outline: none; background: #fff; width: 100%;
}
.input-search { max-width: 360px; }
.dd-field {
  padding: 9px 12px; border: 1px solid var(--input-border); border-radius: 9px;
  font-size: 13px; outline: none; background: #fff; width: 100%; cursor: pointer;
  box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  color: var(--text);
}
.dd-field__caret { color: var(--text-quiet); }
/* Обрезка длинного значения многоточием (2026-09-11, Приход "Позиции" →
   "Тип") — до этого длинное значение (напр. "Готовая продукция") просто
   раздвигало .dd-field/.dd-wrap по min-content, а вместе с ним и всю
   grid-колонку .prihod-item-row (см. .dd-wrap min-width:0 ниже), ломая
   выравнивание остальных полей строки — тот же класс бага, что уже
   чинили в .row-card/.dash-row (overflow-wrap) и .dash-block/.kpi-grid
   (min-width:0), просто на flex-элементе, а не grid. Общий селектор
   (не только Тип в Приходе) — то же самое может произойти в любом
   .dd-field с длинным значением (Unit, фильтры и т.д.), исправление
   безопасно для всех: короткие значения выглядят как раньше. */
.dd-field > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.dd-wrap { position: relative; min-width: 0; }
/* position:fixed (не absolute) — left/top/width задаются в JS от
   trigger.getBoundingClientRect() (см. core.js createDropdown). Иначе
   любой предок со scroll (.modal-card { overflow-y:auto }) обрезает
   панель по своей границе независимо от z-index — 2026-08-13, найдено
   в модалке "Редактирование позиции" и потенциально во всех остальных. */
.dd-panel {
  position: fixed; background: #fff;
  border: 1px solid var(--input-border); border-radius: 12px; padding: 10px;
  max-height: 240px; display: flex; flex-direction: column;
  box-shadow: var(--shadow-anchor); box-sizing: border-box; z-index: 70;
}
.dd-panel--wide { padding: 14px; max-height: 280px; box-shadow: var(--shadow-anchor-strong); }
.dd-panel__list { overflow-y: auto; }
.dd-search {
  padding: 8px 10px; border: 1px solid var(--input-border); border-radius: 8px;
  font-size: 12.5px; outline: none; margin-bottom: 6px; box-sizing: border-box; width: 100%;
}
.dd-option {
  padding: 8px 10px; font-size: 13px; border-radius: 7px; cursor: pointer; color: oklch(0.3 0.01 260);
  overflow-wrap: break-word;
}
.dd-option:hover { background: var(--option-hover); }
/* Отмеченная строка в многозначном dd-popup (2026-09-07, "Тип операции"
   в Журнале операций — без <input type="checkbox">, отмеченность видна
   только визуально) — тот же язык, что .tab.is-active (жирный + красный
   текст), фон — var(--option-hover), тот же токен, что уже даёт hover
   у обычной .dd-option, не новый цвет. */
.dd-option.is-selected { background: var(--option-hover); font-weight: 600; color: var(--red); }
.dd-use-custom {
  background: none; border: none; color: var(--red); font-size: 12.5px; font-weight: 600;
  cursor: pointer; text-align: left; padding: 6px 10px; margin-top: 6px;
}

/* --- Cards / forms ------------------------------------------------------ */

.card {
  background: #fff; border: 1px solid var(--card-border); border-radius: 14px; padding: 18px;
}
.form-card { margin-bottom: 16px; }
.form-card__title { font-weight: 700; font-size: 14px; margin-bottom: 12px; }
.form-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px;
}
.form-actions { display: flex; gap: 8px; margin-top: 14px; }

/* --- Отгрузка: позиции документа (2026-08-05) ------------------------------ */
.otg-items { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.otg-item-row {
  display: grid; grid-template-columns: 2fr 90px 110px 1.4fr auto; gap: 8px; align-items: center;
}
@media (max-width: 700px) {
  .otg-item-row { grid-template-columns: 1fr 1fr; }
}
/* "Выбрать ячейку вручную" (2026-09-15, Фаза 1, партионный учёт) — 6-й
   ребёнок грида .otg-item-row (5 колонок в шаблоне), grid-column:1/-1
   переносит его на отдельную полноширинную строку — тот же приём, что
   .notice[data-item-warning] в Толлинге (tolling.js), не ломает
   фиксированное число колонок основного ряда (см. предупреждение выше
   про CSS-баг card-list/2026-08-06 от повторного использования класса
   строки с разной формой содержимого). */
.otg-item-manual-batch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px; }
.otg-item-manual-batch label { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.otg-item-manual-batch [data-batch-wrap] { flex: 1; min-width: 220px; }
/* Списание — тот же чекбокс+дропдаун приём, один на документ, не на
   позицию (Списание без positions), см. .otg-item-manual-batch выше. */
.spis-manual-batch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px; }
.spis-manual-batch label { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.spis-manual-batch #spis-batch-wrap { flex: 1; min-width: 220px; }

/* --- Приход: позиции документа (2026-08-12) -------------------------------- */
.prihod-items { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.prihod-item-row {
  display: grid; grid-template-columns: 1.6fr 80px 90px 130px 1fr 1fr auto; gap: 8px; align-items: center;
}
@media (max-width: 900px) {
  .prihod-item-row { grid-template-columns: 1fr 1fr; }
}

/* --- Отгрузка на производство: строки-получатели (2026-08-13) -------------
   Отдельный класс, НЕ переиспользует .prihod-item-row/.otg-item-row —
   другая форма строки (подразделение+кол-во+комментарий, без наименования/
   ед.изм./типа/локации). Прецедент .otg-item-row (см. CLAUDE.md, CSS-баг
   card-list/2026-08-06) — переиспользование строки-класса с разной формой
   содержимого уже один раз ломало вёрстку, не повторяем. */
.prod-items { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.prod-item-row {
  display: grid; grid-template-columns: 1.6fr 90px 1fr auto; gap: 8px; align-items: center;
}
@media (max-width: 700px) {
  .prod-item-row { grid-template-columns: 1fr 1fr; }
}

/* --- Толлинг: позиции отправки в переработку (2026-08-24) ------------------
   Отдельный класс, НЕ переиспользует .otg-item-row/.prod-item-row — своя
   форма строки (товар+кол-во+чекбокс "Обезличить"+комментарий), см.
   комментарий-прецедент выше про .prod-item-row. */
.ts-items { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.ts-item-row {
  display: grid; grid-template-columns: 1.8fr 90px 110px 140px 1.2fr auto; gap: 8px; align-items: center;
}
.ts-item-row label { display: flex; align-items: center; gap: 4px; font-size: 12.5px; white-space: nowrap; }
@media (max-width: 760px) {
  .ts-item-row { grid-template-columns: 1fr 1fr; }
}

/* --- Толлинг: позиции приёма возврата от КонтрА (2026-08-27) --------------
   Отдельный класс, НЕ переиспользует .ts-item-row (своя форма строки:
   наименование+кол-во+тип+локация+комментарий, без чекбокса "Обезличить" —
   тот же прецедент-предупреждение выше про .prod-item-row/.otg-item-row). */
.tr-items { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.tr-item-row {
  display: grid; grid-template-columns: 1.6fr 90px 130px 1fr 1fr auto; gap: 8px; align-items: center;
}
@media (max-width: 900px) {
  .tr-item-row { grid-template-columns: 1fr 1fr; }
}

/* --- Логотип юрлица: превью + загрузка файла (Справочники → Юрлица) ------- */

.logo-upload { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.logo-upload__preview {
  width: 56px; height: 56px; border-radius: 10px; border: 1px solid var(--card-border);
  background: var(--thead-bg); display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0;
}
.logo-upload__preview img { width: 100%; height: 100%; object-fit: contain; }
.logo-upload__btn { display: inline-flex; align-items: center; }
.logo-thumb {
  width: 32px; height: 32px; border-radius: 7px; object-fit: contain;
  border: 1px solid var(--card-border); background: var(--thead-bg);
}

/* --- Tables -------------------------------------------------------------- */

.table-wrap { background: #fff; border: 1px solid var(--card-border); border-radius: 14px; overflow: hidden; }
.table-scroll { overflow-x: auto; }
table.data-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.data-table thead tr {
  text-align: left; color: var(--text-muted); font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.03em; background: var(--thead-bg);
}
.data-table th { padding: var(--cell-pad-y, 11px) var(--cell-pad-x, 14px); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative; }

/* Аналитика — заголовки не режутся эллипсисом до 1-2 символов (2026-09-08,
   шаг 3 мобильного аудита). НЕ карточный паттерн .data-table--chain/
   --tolling-summary: те — узкие МОДАЛЬНЫЕ таблицы с 3-4 колонками, где
   даже полная ширина не спасает; здесь таблицы на всю ширину страницы,
   2-5 колонок — при table-layout:fixed на телефоне заголовок вроде
   "ПОСЛ. ДВИЖЕНИЕ" сжимается до "ПО…", хотя место для переноса в 2
   строки есть. table-layout:auto вместо fixed — колонки считаются по
   контенту; если сумма их естественной ширины всё равно не помещается,
   лишнее уходит в горизонтальный скролл (.table-scroll{overflow-x:auto}
   уже есть у каждой таблицы) вместо нечитаемого обрезания. */
.data-table--wrap-headers { table-layout: auto; }
.data-table--wrap-headers thead th { white-space: normal; overflow-wrap: break-word; min-width: 70px; }
/* Drag столбцов мышью (table-settings.js enableHeaderDrag) — только на
   th[draggable], не на служебном последнем <th></th> (действия), у
   которого нет data-col-key/draggable. */
.data-table th[draggable="true"] { cursor: grab; }
.data-table th.col-dragging { opacity: 0.4; }
.data-table th.col-drop-target { box-shadow: inset 2px 0 0 var(--red), inset -2px 0 0 var(--red); }
/* Resize столбца мышью (table-settings.js enableHeaderResize) — узкая
   полоса на правой границе th, поверх draggable-области родителя (сам
   handle draggable="false", см. докстринг table-settings.js). Ширина
   зоны шире видимой линии (6px) — легче попасть курсором. */
.col-resize-handle { position: absolute; top: 0; right: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 2; }
.col-resize-handle:hover, body.col-resizing .col-resize-handle { background: var(--red); opacity: 0.4; }
body.col-resizing { cursor: col-resize; user-select: none; }
.data-table td { padding: var(--cell-pad-y, 11px) var(--cell-pad-x, 14px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.data-table tbody tr { border-top: 1px solid var(--row-border); }
.data-table td.is-primary { font-weight: 500; }
.data-table td.is-secondary { color: oklch(0.45 0.01 260); }

/* Колонка действий (последний служебный th/td, вне columns[]/TableSettings
   — не имеет data-col-key, поэтому НЕ резайзится вручную через ⚙, в
   отличие от обычных колонок). При table-layout:fixed без объявленной
   ширины делила ширину поровну со всеми остальными колонками — на
   таблицах с 2-3 кнопками действий (Справочники -> "На проверке" ->
   "Сохранить в номенклатуру") это схлопывало кнопки в нечитаемое "…"
   (реальный отчёт пользователя, кнопка была на месте, но визуально не
   видна). Фиксированная ширина под худший случай + перенос в 2 строки
   вместо обрезания, если всё равно не хватает места. */
.data-table th.data-table__actions { width: 210px; }
.data-table td.data-table__actions { overflow: visible; text-overflow: clip; white-space: normal; }
.data-table__actions-inner { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* Модалка "Цепочка поступлений" (Остатки, 2026-08-18, доработано
   2026-08-19) — не подключена к table-settings.js (нет колонок, которые
   пользователь может подвинуть), поэтому базовый .data-table td
   (nowrap+ellipsis, рассчитан на таблицы с ручным resize) здесь обрезал
   длинные имена контрагентов без способа расширить колонку. Перенос
   разрешён ТОЛЬКО у "Производство-Поставщик" (переменная длина, имя
   контрагента) — "Дата прихода"/"Количество" фиксированной ширины и
   nowrap (короткий, предсказуемый формат: "04.08.2026", "3018 шт."),
   иначе при первой правке (просто white-space:normal на всех td) они
   сами начали обрезаться/переноситься — второй баг с прода 2026-08-19.
   thead/th:first-child и tbody/td:first-child (не th/td просто) —
   чтобы не задеть td.data-table__total ниже, у которого первая ячейка
   colspan=2 и своя логика выравнивания. */
.data-table--chain-scroll { max-height: 60vh; overflow-y: auto; }
.data-table--chain tbody td { white-space: normal; }
/* Заголовки (ДАТА ПРИХОДА/КОЛИЧЕСТВО, uppercase+letter-spacing) шире
   своих данных — унаследованный от .data-table th nowrap+ellipsis резал
   и их; разрешаем перенос в шапке (короткие 1-2 слова, две строки
   выглядят нормально), а nowrap оставляем только на данных, где формат
   всегда однострочный ("04.08.2026", "3018 шт."). */
.data-table--chain thead th { white-space: normal; overflow-wrap: break-word; }
.data-table--chain thead th:first-child, .data-table--chain tbody td:first-child { width: 108px; }
.data-table--chain thead th:last-child, .data-table--chain tbody td:last-child { width: 132px; }
.data-table--chain tbody td:first-child, .data-table--chain tbody td:last-child { white-space: nowrap; }
.data-table--chain tbody td:last-child { text-align: right; }
/* Sticky шапка и "Итого" внутри data-table--chain-scroll — тело таблицы
   может быть длиннее модалки (.modal-card само уже max-height:80vh,
   scroll на всё окно; здесь отдельный, более узкий scroll — только на
   список партий, чтобы заголовок и "Итого" не терялись при прокрутке
   длинной цепочки). */
.data-table--chain thead th { position: sticky; top: 0; background: #fff; z-index: 1; }
.data-table--chain tfoot td { position: sticky; bottom: 0; background: #fff; z-index: 1; }
.data-table__total td { border-top: 2px solid var(--row-border); font-weight: 600; }
.data-table__total td:first-child { text-align: right; }
/* .shelf-bd (2026-09-17) — модалка "Разбивка по ячейкам" (Остатки) по
   макету директора: ячейка — карточка со строкой-кнопкой, партии —
   вложенные карточки, "Итого" — плашка. Одна разметка на обе ширины;
   колонки ячейки, заголовков и "Итого" — один и тот же .shelf-bd__grid,
   поэтому числа стоят друг под другом без table-layout. Шрифт — общий
   шрифт приложения, как в модалке "Детали" Толлинга (моно-шрифт из
   первого макета директор отменил 17.09); tabular-nums держит цифры
   одной ширины. */
.shelf-bd__head {
  align-items: flex-start; gap: 14px; margin: 0 -22px 18px; padding: 0 22px 18px;
  border-bottom: 1px solid var(--card-border);
}
.shelf-bd__head-icon {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 10px;
  background: var(--badge-danger-bg); color: var(--red);
  display: flex; align-items: center; justify-content: center;
}
.shelf-bd__head-icon svg { width: 20px; height: 20px; }
.shelf-bd__head-text { flex: 1; min-width: 0; }
.shelf-bd__head .modal-header__title { line-height: 1.3; overflow-wrap: break-word; }
.shelf-bd__head-sub { font-size: 13px; color: var(--text-muted); margin-top: 3px; }
.shelf-bd__num, .shelf-bd__date, .shelf-bd__qty { font-variant-numeric: tabular-nums; }
/* border: 1px transparent у заголовков и "Итого" — та же ширина рамки,
   что у карточки ячейки, иначе колонки съезжают на 1px. */
.shelf-bd__grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 88px 88px 96px;
  align-items: center; column-gap: 8px;
}
.shelf-bd__grid > :not(:first-child) { text-align: right; }
.shelf-bd__cols {
  padding: 0 14px 8px; border: 1px solid transparent; border-top: 0; border-bottom: 0;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: var(--text-muted);
}
.shelf-bd__shelf {
  border: 1px solid var(--card-border); border-radius: 12px; margin-bottom: 12px;
  overflow: hidden; background: #fff;
}
.shelf-bd__row {
  width: 100%; background: none; border: none; font: inherit; color: inherit;
  cursor: pointer; padding: 13px 14px; text-align: left;
}
.shelf-bd__row:hover { background: var(--option-hover); }
.shelf-bd__shelf.is-open .shelf-bd__row { background: var(--thead-bg); border-bottom: 1px solid var(--card-border); }
.shelf-bd__name { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 15px; min-width: 0; }
.shelf-bd__name > span:last-child { overflow-wrap: anywhere; }
.shelf-bd__chev { display: inline-flex; color: var(--text-muted); transition: transform 0.15s; }
.shelf-bd__chev svg { width: 16px; height: 16px; }
.shelf-bd__shelf.is-open .shelf-bd__chev { transform: rotate(90deg); }
.shelf-bd__row .shelf-bd__num { font-size: 14px; }
.shelf-bd__row .shelf-bd__num:last-child { font-weight: 700; }
.shelf-bd__body { padding: 12px; }
.shelf-bd__batch { border: 1px solid var(--card-border); border-radius: 10px; overflow: hidden; background: #fff; }
.shelf-bd__batch + .shelf-bd__batch { margin-top: 10px; }
.shelf-bd__batch-title { background: oklch(0.96 0.01 75); padding: 9px 12px; font-weight: 600; font-size: 13px; }
.shelf-bd__moves { list-style: none; margin: 0; padding: 0; }
.shelf-bd__move {
  display: grid; grid-template-columns: 86px minmax(0, 1fr) auto; column-gap: 12px;
  align-items: center; padding: 9px 12px; font-size: 13px;
}
.shelf-bd__move + .shelf-bd__move { border-top: 1px solid var(--row-border); }
.shelf-bd__date { color: var(--text-muted); }
.shelf-bd__what { min-width: 0; overflow-wrap: break-word; }
.shelf-bd__qty { text-align: right; white-space: nowrap; }
.shelf-bd__pill {
  display: inline-block; margin-left: 2px; padding: 1px 8px; border: 1px solid var(--card-border);
  border-radius: 999px; font-size: 11.5px; color: var(--text-secondary); white-space: nowrap; vertical-align: 1px;
}
.shelf-bd__empty { padding: 9px 12px; font-size: 13px; color: var(--text-muted); }
.shelf-bd__total {
  background: oklch(0.96 0.01 75); border: 1px solid transparent; border-radius: 12px;
  padding: 13px 14px; font-weight: 700; font-size: 14px;
}
.shelf-bd__total > :first-child { text-align: right; }
.shelf-bd__total-left { color: var(--red); }
.shelf-bd__foot { font-size: 12px; color: var(--text-muted); text-align: right; margin-top: 10px; }
/* Узкая модалка (телефон) — .modal-card сам сужается до ширины экрана:
   узкие числовые колонки, строка расхода в две строки (дата+количество
   сверху, описание на всю ширину снизу), чтобы длинный УПД не сжимал
   колонки. 640px — по ширине модалки (modal-card--lg 720px), не по
   общему мобильному брейкпоинту 900px. */
@media (max-width: 640px) {
  .shelf-bd__head { gap: 12px; }
  /* Остаток шире остальных: в "Итого" он с единицей ("2995 шт."). */
  .shelf-bd__grid { grid-template-columns: minmax(0, 1fr) 44px 44px 72px; column-gap: 6px; }
  .shelf-bd__cols { padding: 0 10px 6px; font-size: 10.5px; letter-spacing: 0; }
  .shelf-bd__row { padding: 12px 10px; }
  .shelf-bd__body { padding: 8px; }
  .shelf-bd__move { grid-template-columns: minmax(0, 1fr) auto; row-gap: 3px; padding: 9px 10px; }
  .shelf-bd__what { grid-column: 1 / -1; grid-row: 2; }
  .shelf-bd__total { padding: 12px 10px; }
  .shelf-bd__foot { text-align: left; }
}
/* tfoot ("Итого:" / сумма) намеренно не трогаем здесь — существующее
   .data-table__total td:first-child/:last-child (выше, line ~507/539)
   уже держит и подпись, и число по правому краю рядом друг с другом,
   ровно то же соглашение, что уже в "Цепочка поступлений" (было
   ошибочно переопределено на left в первой версии этой правки —
   отсюда и разъезд "Итого"/числа, который заметил директор). */
.data-table__total td:last-child { text-align: right; }
/* Фактический остаток (2026-08-24) — отдельная строка ПОД таблицей/"Итого",
   не в tfoot: это другая по смыслу цифра (текущий stock, не сумма прихода
   за всё время), приглушённый цвет специально отличает её от "Итого". */
.income-chain-current-stock { text-align: right; font-size: 12.5px; color: var(--text-quiet); margin-top: 6px; }

/* .ostatki-loc (2026-09-17) — кнопка "Локация" (Остатки, таблица и
   мобильная карточка), открывает "Разбивку по ячейкам". Заменила
   .ostatki-cell-link (подчёркнутый текст, О1/О3 2026-09-15): по макету
   директора красная только иконка ICONS.grip, текст — цвет колонки;
   наведение — тот же нейтральный фон, что у .otg-print-icon.
   vertical-align: top + line-height: inherit — центр иконки и текста
   совпадает с подписью "Локация:" в мобильной карточке. */
.ostatki-loc {
  background: none; border: none; font: inherit; line-height: inherit; color: inherit;
  cursor: pointer; padding: 2px 6px 2px 4px; margin: -2px 0 -2px -4px; border-radius: 5px;
  display: inline-flex; align-items: center; gap: 6px; vertical-align: top; text-align: left;
}
.ostatki-loc svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--red); }
/* В таблице ячейка td режет по ellipsis, но на inline-flex кнопку это не
   действует — текст обрезался без «…». Многоточие ставим на сам текст;
   в мобильной карточке длинная локация по-прежнему переносится. */
.data-table td .ostatki-loc { max-width: calc(100% + 4px); }
.data-table td .ostatki-loc span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ostatki-loc:hover { color: var(--text); background: oklch(0.94 0.005 80); }
.ostatki-loc:hover svg { color: var(--red-hover); }

/* "Сводка по товару" в модалке "Детали" КонтрА (Толлинг, 2026-08-28) —
   тот же прецедент, что .data-table--chain выше: базовый .data-table td
   (nowrap+ellipsis) обрезал бы длинные наименования товаров. Перенос
   разрешён только в колонке "Товар" (первой) — остальные фиксированного
   формата ("12 шт.", "04.08.2026"). Без своего max-height/sticky —
   модалка обычно короткая (несколько товаров), .modal-card уже даёт
   scroll на 80vh, дублировать незачем. */
.data-table--tolling-summary thead th { white-space: normal; overflow-wrap: break-word; }
/* data-col-key, не :first-child (2026-08-29) — колонки переставляются
   мышью (TableSettings), "Товар" не всегда первый физически. */
.data-table--tolling-summary tbody td[data-col-key="product"] { white-space: normal; }
.data-table--tolling-summary tbody td:last-child { width: 90px; text-align: right; }

/* --- Badges -------------------------------------------------------------- */

.badge {
  display: inline-block; padding: 4px 10px; border-radius: 20px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.badge--ok { background: var(--badge-ok-bg); color: var(--badge-ok-fg); }
.badge--info { background: var(--badge-info-bg); color: var(--badge-info-fg); }
.badge--warn { background: var(--badge-warn-bg); color: var(--badge-warn-fg); }
.badge--danger { background: var(--badge-danger-bg); color: var(--badge-danger-fg); }
.badge--neutral { background: var(--badge-neutral-bg); color: var(--badge-neutral-fg); }

.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.dot--green { background: var(--dot-green); }
.dot--blue { background: var(--dot-blue); }
.dot--amber { background: var(--dot-amber); }
.dot--red { background: var(--dot-red); }

/* --- Modal (центрированный попап настроек) -------------------------------- */

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.modal-card {
  background: #fff; border-radius: 16px; padding: 22px; max-width: min(520px,100%);
  width: 100%; max-height: 80vh; overflow-y: auto; box-shadow: var(--shadow-modal);
  box-sizing: border-box;
}
.modal-card--sm { max-width: min(420px,100%); }
.modal-card--md { max-width: min(480px,100%); }
.modal-card--lg { max-width: min(720px,100%); }
.modal-header {
  display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;
}
.modal-header__title { font-weight: 700; font-size: 16px; }
.modal-close {
  background: none; border: none; font-size: 20px; line-height: 1; cursor: pointer;
  color: oklch(0.5 0.01 260);
}
/* Закреплённая шапка модалки "Детали" КонтрА (2026-08-29) — структурно
   (flex-column + overflow:hidden на карточке, скроллится только вложенный
   .modal-scroll-body), НЕ через position:sticky на шапке. Причина: живая
   проверка (Playwright) показала, что position:sticky с ненулевым top на
   заголовке ТАБЛИЦЫ ниже (нужен был отступ под высоту шапки) рендерится
   некорректно в этом браузере — пробовали и sticky на <th>, и на <thead>
   целиком, оба проваливают строку заголовка внутрь первой строки данных.
   top:0 работает исправно (см. правило thead th ниже) — только его и
   используем, а "прибить" шапку модалки над скроллом решаем структурно,
   не вторым уровнем sticky. Scoped-модификатор, не общий .modal-card —
   остальные модалки короткие, им нет смысла терять простой overflow-y:auto
   ради этого. */
.modal-card--scroll-body { display: flex; flex-direction: column; overflow: hidden; }
.modal-card--scroll-body > .modal-header { flex: 0 0 auto; }
.modal-card--scroll-body > .modal-scroll-body { overflow: auto; min-height: 0; }
/* Заголовок таблицы "Сводка по товару" — sticky top:0 относительно
   .modal-scroll-body (её собственный scroll-контейнер, не всей карточки)
   — тот же рабочий паттерн, что .data-table--chain thead th (Остатки,
   "Цепочка поступлений"), см. выше в этом файле. */
.data-table--tolling-summary thead th {
  position: sticky; top: 0; z-index: 2; background: var(--thead-bg);
}
/* Таблица "Сводка по товару" в этой модалке — БЕЗ обёртки .table-scroll
   (в отличие от всех остальных data-table в проекте), см. комментарий в
   templates/index.html у modal-tolling-details: .table-scroll{overflow-x:
   auto} без overflow-y по спеке CSS Overflow Module Level 3 получает
   вычисленный overflow-y НЕ visible — а по MDN (position:sticky) ЛЮБОЕ
   не-visible значение (даже hidden/clip, не только auto/scroll) делает
   элемент "ближайшим scroll-контейнером" для sticky thead внутри,
   перехватывая эту роль у настоящего .modal-scroll-body. Горизонтальный
   скролл при широких колонках (ручной resize через ⚙) даёт сам
   .modal-scroll-body (уже `overflow: auto` — оба направления, см. выше)
   — один общий контейнер на всё содержимое модалки, а не вложенный
   конфликтующий.

   Той же причине — #tolling-details-table-wrap (.table-wrap обычно даёт
   border-radius+overflow:hidden для скруглённых углов таблицы) здесь
   явно overflow:visible: overflow:hidden — тоже "scroll-контейнер" по
   MDN, перехватил бы sticky точно так же, как .table-scroll выше. Теряем
   скруглённые уголки именно у этой таблицы — не критично внутри и так
   скруглённой модалки. */
.table-wrap#tolling-details-table-wrap { overflow: visible; }
.modal-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 6px 0;
  border-bottom: 1px solid var(--row-border);
}
.modal-row__label { flex: 1; min-width: 90px; font-size: 13px; color: oklch(0.3 0.01 260); }
.modal-num {
  width: 60px; padding: 5px 8px; border: 1px solid var(--input-border); border-radius: 7px;
  font-size: 12.5px;
  -moz-appearance: textfield; /* Firefox — убирает спиннер type=number */
}
.modal-num--wide { width: 70px; }
/* Нативный спиннер type=number (визуально похож на лишнюю иконку ⇕,
   особенно на узких полях "авто") — убран везде, где .modal-num
   используется (табличные настройки всех разделов, одна общая модалка). */
.modal-num::-webkit-inner-spin-button,
.modal-num::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

/* --- Панель раздела с таблицей: "+ Добавить" + шестерёнка настроек ------- */

.sprav-panel__toolbar {
  display: flex; justify-content: flex-start; align-items: center; gap: 10px; margin-bottom: 14px;
}

/* --- Tabs (Остатки / Справочники) ----------------------------------------- */

.tabs { display: flex; border-bottom: 1px solid var(--card-border); flex-wrap: wrap; }
.tab {
  padding: 10px 4px; margin-right: 22px; font-size: 13.5px; font-weight: 500;
  color: oklch(0.5 0.01 260); border-bottom: 2px solid transparent; background: none;
  border-top: none; border-left: none; border-right: none; cursor: pointer;
}
.tab.is-active { font-weight: 700; color: var(--red); border-bottom-color: var(--red); }

/* --- Mobile card list (замена таблицы для Приход/Отгрузка/Списание) ------- */

.card-list { display: flex; flex-direction: column; gap: 10px; }
.row-card {
  background: #fff; border: 1px solid var(--card-border); border-radius: 12px; padding: 14px;
  overflow-wrap: break-word;
}
.row-card__top { display: flex; justify-content: space-between; gap: 8px; align-items: flex-start; }
.row-card__top > * { min-width: 0; }
.row-card__title { font-weight: 600; font-size: 14px; }
.row-card__grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-top: 10px; font-size: 12.5px;
}
.row-card__grid > * { min-width: 0; }
.row-card__grid span.label { color: var(--text-quiet); }
.row-card__note { font-size: 12.5px; color: oklch(0.5 0.01 260); margin-top: 8px; }

/* --- Пользователи: грид чекбоксов прав (2026-09-07, мобильный пилот) —
   вынесен из инлайн-стиля users.js, чтобы медиа-запрос ниже мог схлопнуть
   его в 1 колонку на мобильном ("по чекбоксу в строке"). Наложение
   текста пояснения "(Нельзя — останется без администратора)" фиксится
   ОТДЕЛЬНО, структурой самой ячейки (см. usersExpandDetailHtml в
   users.js) — не этим медиа-запросом, работает при любой ширине. ------- */
.perm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap: 8px 20px; }
@media (max-width: 900px) {
  .perm-grid { grid-template-columns: 1fr; }
}

/* --- Отгрузка: группировка по документу (2026-08-06) — шапка один раз +
   список позиций внутри карточки/строки-группы (см. otgruzka.js) -------- */
.otg-doc-items { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--row-border); display: flex; flex-direction: column; gap: 8px; }
.otg-doc-item + .otg-doc-item { padding-top: 8px; border-top: 1px solid var(--row-border); }
.otg-doc-item__row { display: flex; justify-content: flex-start; align-items: center; gap: 8px; font-size: 12.5px; }
.data-table tr.otg-doc-row { background: var(--thead-bg); font-weight: 600; }
.data-table tr.otg-doc-items-row td { white-space: normal; color: var(--text-secondary); font-weight: 400; }
/* .data-table td по умолчанию обрезает содержимое эллипсисом (для
   ДАННЫХ) — ячейка с кнопками действий (✎/История/Печать всех) не
   текст, три кнопки в узкой колонке переносятся на вторую строку, а не
   прячутся под "…" (2026-08-07, добавление 3-й кнопки сделало обрезку
   заметной). */
/* Специфичность .data-table td (класс+элемент) выше, чем у одного
   класса — нужен селектор минимум такой же специфичности, иначе
   переопределение проигрывает по каскаду (та же природа, что и
   .mobile-only/.card-list баг выше в этом файле). */
.data-table td.otg-doc-actions-cell { overflow: visible; text-overflow: clip; white-space: normal; min-width: 90px; }
.otg-doc-actions-cell button { margin: 2px 3px 2px 0; }

/* Иконка печати этикетки — маленькая, приглушённая, перед наименованием
   позиции (2026-08-07), не отдельная кнопка-столбец справа. */
.otg-print-icon {
  background: none; border: none; cursor: pointer; color: var(--text-quiet);
  font-size: 13px; line-height: 1; padding: 3px 5px; border-radius: 5px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  /* min-width/height (2026-09-11, аудит Прихода Ф5) — было 24×20px
     фактически (замер на 390px, .otg-doc-item__row внутри мобильных
     карточек), ниже минимума WCAG 2.2 SC 2.5.8 (24×24px). Один и тот же
     класс на десктопе и мобильном — держим минимум глобально, не только
     под медиа-запросом: на десктопе клик мышью, лишние 4px по высоте не
     мешают, а не разводить два разных touch-target размера ради 24px. */
  min-width: 24px; min-height: 24px;
}
.otg-print-icon:hover { color: var(--text-secondary); background: oklch(0.94 0.005 80); }
.otg-print-icon svg { width: 14px; height: 14px; flex-shrink: 0; }
/* Отдельная окраска действий, а не только нейтральная (2026-09-11,
   "Вариант A" — приход/аналог демо): "Отгрузить в производство" реально
   двигает остаток (не просмотр, как История/Правка) — акцент var(--red),
   тот же токен, что и у основных кнопок-действий (.btn-primary) в
   проекте, не новый цвет. "Куда ушло на производство" — маркер статуса
   частичной отгрузки, акцент var(--badge-warn-fg) — тот же токен, что
   уже даёт "жёлтый/предупреждающий" смысл в badge--warn по всему
   проекту. */
.otg-print-icon.is-action { color: var(--red); }
.otg-print-icon.is-action:hover { color: var(--red-hover); background: oklch(0.95 0.04 40); }
.otg-print-icon.is-marker { color: var(--badge-warn-fg); }
.otg-print-icon.is-marker:hover { color: var(--badge-warn-fg); background: var(--badge-warn-bg); }

/* --- Печать этикетки заказчика (Отгрузка) — PDF-предпросмотр в iframe +
   разбивка количества на несколько этикеток + отступы, см.
   static/js/sections/otgruzka.js (2026-08-06 отступы/предпросмотр,
   2026-08-07 разбивка количества, объединено в одну модалку) ------------- */
.label-preview__layout { display: flex; gap: 20px; flex-wrap: wrap; }
.label-preview__frame-wrap {
  flex: 1 1 260px; background: oklch(0.93 0.005 260); border: 1px solid var(--card-border);
  border-radius: 10px; min-height: 320px; display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.label-preview__frame { width: 100%; height: 320px; border: none; background: #fff; }
.label-preview__controls { flex: 1 1 220px; min-width: 220px; }
.label-qty-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.label-qty-row input { width: 100%; }
.label-qty-total { font-size: 12.5px; color: var(--text-secondary); margin-top: 8px; }
.label-qty-total--mismatch { color: oklch(0.55 0.15 55); font-weight: 600; }

/* --- Юрлица: визуальный drag-редактор макета этикетки (2026-08-11,
   третий раунд — заменил числовые поля позиционирования, см.
   static/js/sections/spravochniki.js). 290×300px = 58×60мм × 5px/мм
   (LABEL_EDITOR_SCALE в JS) — держать в синхроне при правке. -------- */
.label-editor-layout { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; }
.label-editor {
  position: relative; width: 290px; height: 300px; flex: 0 0 auto;
  background: #fff; border: 1px dashed oklch(0.6 0.01 260); border-radius: 4px;
  overflow: hidden; touch-action: none;
}
.label-editor__el { position: absolute; cursor: move; user-select: none; box-sizing: border-box; }
.label-editor__logo {
  border: 1px dashed oklch(0.6 0.15 260 / 0.7); background: oklch(0.97 0.005 260 / 0.6);
  display: flex; align-items: center; justify-content: center;
}
.label-editor__logo img { width: 100%; height: 100%; object-fit: fill; pointer-events: none; }
.label-editor__placeholder { font-size: 10px; color: var(--text-secondary); pointer-events: none; }
.label-editor__resize {
  position: absolute; right: -4px; bottom: -4px; width: 10px; height: 10px;
  background: oklch(0.55 0.15 260); border: 1px solid #fff; border-radius: 2px; cursor: nwse-resize;
}
.label-editor__qr {
  border: 1px solid oklch(0.5 0.01 260); background: oklch(0.92 0.005 260);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; color: var(--text-secondary); font-weight: 600;
}
.label-editor__address {
  color: oklch(0.55 0.2 30); font-size: 9px; line-height: 11px; font-weight: 600;
  max-width: 160px; padding: 1px 2px; background: oklch(1 0 0 / 0.65); border-radius: 2px;
}
/* Блок наименования (2026-08-11, четвёртый раунд) — двигается целиком,
   без ресайза (нет .label-editor__resize внутри) — ширина фиксирована
   TEXT_BLOCK_WIDTH_MM (JS/services/labels.py, держать в синхроне). */
.label-editor__textblock {
  width: 200px; font-size: 9px; line-height: 12px; color: var(--text-primary);
  background: oklch(1 0 0 / 0.75); padding: 2px 3px; border-radius: 2px;
  border: 1px dashed oklch(0.6 0.01 260 / 0.6);
}
.label-editor__textblock div:first-child { font-weight: 700; }

/* Список загруженных логотипов юрлица (превью-миниатюра + удалить) —
   над drag-редактором, каждая строка = один legal_entity_logos. */
.logo-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.logo-list:empty { display: none; }
.logo-list__item {
  position: relative; width: 44px; height: 44px; border: 1px solid var(--card-border);
  border-radius: 6px; overflow: hidden; background: oklch(0.97 0.005 260);
}
.logo-list__item img { width: 100%; height: 100%; object-fit: contain; }
.logo-list__remove {
  position: absolute; top: 0; right: 0; width: 16px; height: 16px;
  background: oklch(0.55 0.15 25); color: #fff; border: none;
  border-bottom-left-radius: 4px; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
/* svg вместо сырого × (2026-09-15, Н8) — .logo-list__remove мельче
   .btn-row-action (16px против 30px), поэтому своя, меньшая иконка. */
.logo-list__remove svg { width: 10px; height: 10px; flex-shrink: 0; }

/* --- История изменений (Приход, 2026-08-05) — модалка с отфильтрованным
   срезом audit_log по одной записи, см. static/js/sections/prihod.js ------ */
.history-entry { padding: 10px 0; border-bottom: 1px solid var(--row-border); }
.history-entry:last-child { border-bottom: none; }
.history-entry__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; }
.history-entry__when { color: var(--text-quiet); white-space: nowrap; }
.history-entry__who { font-weight: 600; }
.history-entry__changes { margin: 6px 0 0; padding-left: 18px; font-size: 12.5px; color: var(--text-secondary); }
.history-entry__changes li { margin-top: 2px; }
.history-entry__reason { font-size: 12.5px; margin-top: 6px; color: oklch(0.5 0.01 260); }

/* --- Misc ------------------------------------------------------------------ */

.notice {
  display: flex; align-items: center; gap: 8px; background: var(--badge-neutral-bg);
  border-radius: 10px; padding: 10px 14px; margin-bottom: 16px; font-size: 12.5px;
  color: var(--text-secondary);
}
.filters-bar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; }
.progress-track { height: 7px; border-radius: 5px; background: oklch(0.94 0.006 80); overflow: hidden; }
.progress-fill { height: 100%; background: var(--red); }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px,1fr)); gap: 14px; margin-bottom: 22px; }
/* Мобильный: явная 2-колоночная сетка вместо auto-fit (2026-09-07) — при
   узком экране auto-fit с minmax(200px,1fr) даёт 1 плитку на строку и
   плитки визуально теряются/обрезаются по краю; явные 2 колонки читаемы
   на 375px и не обрезаются (ТЗ мобильного пилота, независимая правка). */
@media (max-width: 900px) {
  .kpi-grid { grid-template-columns: repeat(2, 1fr); }
}
.kpi-card { background: #fff; border: 1px solid var(--card-border); border-radius: 14px; padding: 18px; position: relative; min-width: 0; }
.kpi-card__label { font-size: 12px; text-transform: uppercase; color: var(--text-muted); padding-right: 20px; }
.kpi-card__value { font-size: 28px; font-weight: 800; color: oklch(0.2 0.01 260); margin-top: 8px; overflow-wrap: break-word; }
.kpi-card__delta { font-size: 12px; margin-top: 6px; }
/* Скрыть/показать KPI-плитку (2026-08-17) — глаз в углу видимой плитки,
   скрытая плитка не рендерится в сетке вообще (не занимает места),
   вместо неё — приглушённый чип в отдельной строке ниже, тем же глазом
   возвращает обратно (тот же прецедент, что скрытие столбцов/блоков —
   table-settings.js/block-settings.js, только видимый триггер прямо на
   плитке, а не только в модалке). */
.kpi-card__eye {
  position: absolute; top: 10px; right: 10px; background: none; border: none; cursor: pointer;
  font-size: 13px; line-height: 1; padding: 3px; border-radius: 6px; opacity: 0.3;
}
.kpi-card__eye:hover { opacity: 1; background: var(--option-hover); }
.kpi-hidden-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: -8px; margin-bottom: 22px; }
.kpi-hidden-row:empty { display: none; margin: 0; }
.kpi-chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; padding: 6px 10px;
  border-radius: 999px; border: 1px dashed var(--input-border); background: var(--option-hover);
  color: var(--text-quiet); cursor: pointer;
}
.kpi-chip:hover { color: var(--text); border-color: var(--text-quiet); }
/* Скрыть карточку подрядчика в Толлинге (2026-08-29) — тот же приём и
   .kpi-hidden-row/.kpi-chip, что у KPI-плиток Дашборда выше, но НЕ
   .kpi-card__eye как есть (position:absolute в углу конфликтовал бы с
   уже существующим бейджем статуса там же) — тот же визуальный язык
   (приглушённая иконка, подсветка на hover), в потоке рядом с бейджем. */
.tolling-card__eye {
  background: none; border: none; cursor: pointer; font-size: 13px; line-height: 1;
  padding: 3px; border-radius: 6px; opacity: 0.3;
}
.tolling-card__eye:hover { opacity: 1; background: var(--option-hover); }
.text-muted { color: var(--text-quiet); }
.spinner-row { padding: 20px; text-align: center; color: var(--text-quiet); font-size: 13px; }

/* --- Дашборд: сетка блоков (настраиваемая — см. block-settings.js) -------- */

.dash-blocks { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 700px) {
  .dash-blocks { grid-template-columns: repeat(auto-fit, minmax(280px,1fr)); }
}
.dash-block--full { grid-column: 1 / -1; }
/* min-width:0 (2026-09-07, мобильный пилот) — без него grid-track
   ширины 1fr наследует min-content ОТ САМОГО ШИРОКОГО потомка блока, а
   не от доступной ширины экрана — на узких viewport'ах это раздувало
   ВЕСЬ .dash-blocks (и вместе с ним kpi-grid, т.к. .dash-block--full
   делит с ним одну колонку) шире 375px, отсюда и обрезанные по краю
   KPI-плитки из ТЗ — корень был не в самой kpi-grid, а в соседнем блоке
   (тогда — #dash-tolling-table style="min-width:520px" внутри
   .table-scroll; сама таблица 11.09.2026 переведена на общий
   .table-wrap desktop-only/.card-list mobile-only паттерн, как и
   остальные 5 таблиц дашборда, старого горизонтального скролла у неё
   больше нет — но общее защитное правило остаётся полезным на будущее
   для любого другого широкого содержимого блока). */
.dash-block { min-width: 0; }

/* Сворачивание карточки целиком (2026-09-11, аудит Дашборда) — клик по
   заголовку/шеврону, состояние в localStorage (см. initDashTollingCollapse
   в dashboard.js) — тот же чисто визуальный/сессионный уровень
   хранения, что kpiState/BlockSettings, НЕ user_preferences (не тот
   уровень значимости, чтобы синхронизировать между устройствами).
   Первое использование — "Толлинг — статус у подрядчиков" (список
   может быть длинным), но класс общий на случай, если понадобится
   другому блоку. */
.dash-block__header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 12px; cursor: pointer; user-select: none;
}
.dash-block__header .expand-caret { margin-left: 0; }
#dash-tolling-body.is-collapsed { display: none; }

/* Drag-reorder карточек мышью (2026-08-17, block-settings.js
   enableBlockDrag + tolling.js initTollingCardDrag) — общие классы для
   любого карточного блока с draggable="true" (Дашборд, Толлинг), тот же
   визуальный язык, что и у drag столбцов таблиц (table-settings.js). */
[data-block][draggable="true"], .card[draggable="true"], .kpi-card[draggable="true"] { cursor: grab; }
.block-dragging { opacity: 0.4; }
.block-drop-target { box-shadow: 0 0 0 2px var(--red) inset; }

/* --- Дашборд: списки-карточки, категории, активность ----------------------- */

.dash-row {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 10px;
  padding: 11px 0; border-top: 1px solid var(--row-border);
}
.dash-row:first-child { border-top: none; }
/* overflow-wrap (2026-09-11, аудит Дашборда, финальная шлифовка) — тот же
   пробел, что уже чинили в .row-card 09.09 (см. CLAUDE.md/progress.md):
   длинное значение без пробелов (наименование товара и т.п.) иначе
   вылезает за пределы своей карточки на соседние блоки — найдено живым
   Playwright-тестом с синтетическим товаром на throwaway-копии БД
   ("Последние отгрузки"/"операции"/"движения с подрядчиками" реально
   перекрывали друг друга). */
.dash-row__title { font-size: 13.5px; font-weight: 500; color: oklch(0.25 0.01 260); line-height: 1.35; overflow-wrap: break-word; }
.dash-row__sub { font-size: 12px; color: var(--text-quiet); margin-top: 4px; overflow-wrap: break-word; }
.dash-row__right { text-align: right; flex-shrink: 0; }
.dash-row__qty { font-size: 13.5px; font-weight: 600; white-space: nowrap; }
.dash-row__date { font-size: 12px; color: var(--text-quiet); margin-top: 4px; white-space: nowrap; }

.cat-row { margin-bottom: 14px; }
.cat-row__top { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 6px; }
/* min-width:0 (2026-09-07, мобильный пилот) — flex-детям по умолчанию
   min-width:auto, длинное название категории отказывалось сжиматься и
   выталкивало .cat-track шире карточки на узком экране; ellipsis вместо
   переноса, т.к. соседняя .cat-row__value должна остаться на той же
   строке и не сжиматься (flex-shrink:0). */
.cat-row__label { color: oklch(0.35 0.01 260); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-row__value { color: var(--text-muted); flex-shrink: 0; }
.cat-track { height: 8px; background: oklch(0.94 0.006 80); border-radius: 6px; overflow: hidden; width: 100%; box-sizing: border-box; }
.cat-fill { height: 100%; }

.activity-row { display: flex; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--row-border); }
.activity-row:last-child { border-bottom: none; }
.activity-row__dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 4px; flex-shrink: 0; }
.activity-row__text { font-size: 13px; color: oklch(0.25 0.01 260); overflow-wrap: break-word; }
.activity-row__meta { font-size: 11.5px; color: var(--text-quiet); margin-top: 2px; overflow-wrap: break-word; }

/* --- Остатки: раскрываемая строка ------------------------------------------ */

.expand-caret { color: var(--text-quiet); font-size: 11px; margin-left: 6px; }
tr.is-expandable { cursor: pointer; }
tr.expand-detail td { background: var(--page-bg); padding: 12px 14px; }
.expand-detail__fields { display: flex; flex-wrap: wrap; gap: 24px; }
.expand-detail__fields .label { color: var(--text-quiet); font-size: 12.5px; margin-right: 4px; }
.expand-detail__fields .value { font-weight: 500; }
