@charset "UTF-8";
/* MZone UI — reference design system (предоставлена заказчиком). Premium B2B SaaS / fintech. */

/* Шрифт (Inter, файлы в проекте) объявлен в /static/fonts.css — его подключает
   каждый каркас страницы ПЕРЕД этим файлом: app_base.html, экран входа, лендинг,
   калькуляторы, юридические тексты. Держать @font-face здесь нельзя: публичные
   страницы этот файл не грузят, и до 04.09.2026 они объявляли Inter, не имея его. */

:root {
  color-scheme: light;
  --mzone-navy-950: #06142b;
  --mzone-navy-900: #0b1f3a;
  --mzone-navy-850: #0b2a3a;
  --mzone-navy-800: #0e2d4a;
  --mzone-navy-700: #163a59;
  --mzone-teal-700: #0a7373;   /* ссылки этим цветом давали 4.46 при пороге 4.5 */
  --mzone-teal-800: #085c5c;
  --mzone-teal-900: #064a4a;
  --mzone-teal-600: #0fa3a3;
  /* Фирменный цвет Wildberries — только для ссылок «открыть карточку на WB» */
  --mzone-wb-brand: #cb11ab;
  --mzone-wb-brand-dark: #a00d86;
  --mzone-teal-500: #16b8b5;
  --mzone-cyan-400: #2ed6cf;
  --mzone-cyan-200: #a9efec;
  --mzone-cyan-100: #e9fbfa;
  --mzone-bg: #f5f7f9;
  --mzone-surface: #ffffff;
  --mzone-surface-soft: #f8fafc;
  --mzone-surface-muted: #f1f5f9;
  --mzone-text: #0b1f3a;
  /* 4.43 к фону страницы — ниже порога WCAG AA (4.5). Подписи под заголовками,
     метки метрик и колонки таблиц набраны именно этим цветом, поэтому axe нашёл
     81 нарушение контраста. #5b6b80 даёт 5.07 и визуально почти не отличается. */
  --mzone-text-muted: #5b6b80;
  --mzone-text-soft: #6b7a8f;   /* плейсхолдеры: было 2.56 к белому, стало 4.37 */
  --mzone-text-inverse: #ffffff;
  --mzone-border: #e2e8f0;
  --mzone-border-soft: #eef2f6;
  --mzone-overlay: rgba(6, 20, 43, 0.54);
  --mzone-wb: #a21caf;
  --mzone-ozon: #0069ff;
  /* На своём светлом фоне давал 2.93 при пороге 4.5: цифра в зелёной плашке
     читалась хуже обычного текста. Стало 4.69. */
  --mzone-success: #0f7f51;
  --mzone-success-bg: #eafbf3;
  --mzone-danger: #c81e1e;   /* было 3.44 на розовой плашке, стало 5.24 */
  --mzone-danger-bg: #fef2f2;
  --mzone-warning: #a1560b;   /* было 3.0 на своей плашке, стало 5.14 */
  --mzone-warning-bg: #fff7e6;
  --mzone-info: #2563eb;
  --mzone-info-bg: #eff6ff;
  --mzone-purple: #9333ea;
  --mzone-purple-bg: #f5edff;
  --mzone-font-sans: "Inter", "Manrope", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mzone-font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
  /* Кегли и высота строки — по мотивам Wedges (lmsqueezy/wedges): у каждого
     размера СВОЯ высота строки, иначе мелкий текст слипается. Прошлая шкала
     держала пять размеров в диапазоне 11–15px: ступени не читались, а всё
     вместе было слишком мелким. Ступень поднята на один шаг (14.08): рабочие
     экраны читались бледнее лендинга, где базовый текст 16, а заголовки крупные. */
  --mzone-font-size-xs: 0.8125rem;    /* 13 */
  --mzone-font-size-sm: 0.875rem;     /* 14 */
  --mzone-font-size-md: 0.9375rem;    /* 15 */
  --mzone-font-size-base: 1rem;       /* 16 */
  --mzone-font-size-lg: 1.0625rem;    /* 17 */
  --mzone-font-size-xl: 1.375rem;     /* 22 */
  --mzone-font-size-2xl: 1.875rem;    /* 30 — заголовок страницы */
  --mzone-font-size-3xl: 2.25rem;     /* 36 — крупная цифра */
  --mzone-lh-xs: 1.125rem;
  --mzone-lh-sm: 1.25rem;
  --mzone-lh-md: 1.375rem;
  --mzone-lh-base: 1.5rem;
  --mzone-lh-lg: 1.625rem;
  --mzone-lh-xl: 1.875rem;
  --mzone-space-1: 0.25rem;
  --mzone-space-2: 0.5rem;
  --mzone-space-3: 0.75rem;
  --mzone-space-4: 1rem;
  --mzone-space-5: 1.25rem;
  --mzone-space-6: 1.5rem;
  --mzone-space-7: 1.75rem;
  --mzone-space-8: 2rem;
  --mzone-radius-xs: 0.375rem;
  --mzone-radius-sm: 0.5rem;
  --mzone-radius-md: 0.75rem;
  --mzone-radius-lg: 1.125rem;
  --mzone-radius-xl: 1.375rem;
  --mzone-radius-pill: 999px;
  --mzone-shadow-xs: 0 1px 2px 0 rgba(18,18,23,0.05);
  --mzone-shadow-card: 0 1px 2px rgba(15,23,42,0.04), 0 14px 34px rgba(15,23,42,0.035);
  --mzone-shadow-card-hover: 0 2px 4px rgba(15,23,42,0.05), 0 18px 42px rgba(15,23,42,0.075);
  --mzone-shadow-float: 0 18px 42px rgba(6,20,43,0.12);
  --mzone-focus-ring: 0 0 0 4px rgba(46,214,207,0.16);
  /* 276px хватало при мелком шрифте; после укрупнения шкалы «Данные по товарам»
     и «Товары (редактор)» обрезались многоточием. Ширина под самый длинный пункт. */
  --mzone-sidebar-width: 296px;
  --mzone-sidebar-collapsed-width: 84px;
  --mzone-topbar-height: 76px;
  --mzone-content-max-width: none;   /* дашборд на всю ширину (заказчик) */
  /* Цвета для mzone-charts.js — ЛИТЕРАЛЫ (js читает через getPropertyValue и
     подставляет в SVG-атрибуты: вложенные var() там не резолвятся → бары «исчезают») */
  --profit: #17a673;
  --loss: #c22b3f;
  --gold: #1668a5;
  /* Категориальная палитра серий (кабинеты). Порядок фиксирован: цвет закреплён за
     кабинетом, а не за его местом в списке. Проверена валидатором dataviz на белом
     фоне: L-диапазон, цветность, CVD-разделение (worst ΔE 18.7), контраст ≥3:1. */
  --cab1: #0d9488;
  --cab2: #7c3aed;
  --cab3: #ea580c;
  --cab4: #2563eb;
  --cab5: #db2777;
  /* Семантика денег в графиках: старт (продано) · удержание · поступление/итог.
     Красный и зелёный неразличимы при дейтеранопии, поэтому пара разведена по
     светлоте (ΔE 10,2 deutan) И продублирована знаком «+/−», подписью и таблицей. */
  --chart-base: #1668a5;
  --chart-expense: #c22b3f;
  --chart-income: #17a673;
  --chart-grid: #eef2f6;
  --chart-empty-bar: #e2e8f0;
  /* Тепловая карта: пять ступеней. Светлая тема — от почти белого к бирюзе. */
  --heat-0: #eef2f6; --heat-1: #cdeeec; --heat-2: #86dbd7;
  --heat-3: #2ab5b1; --heat-4: #0b7472;
  --heat-ink-lo: #0b2a3a; --heat-ink-hi: #ffffff;
  --mzone-topbar-bg: rgba(255,255,255,0.86);
  --mzone-solid: #06142b;          /* сплошная кнопка/активный сегмент */
  --mzone-solid-hover: #0a1e3a;
  --mzone-solid-ink: #ffffff;
  --mzone-transition-fast: 140ms ease;
  --mzone-transition: 180ms ease;
}

*, *::before, *::after { box-sizing: border-box; }
html { min-width: 320px; background: var(--mzone-bg); scroll-behavior: smooth; text-size-adjust: 100%; }
body { min-width: 320px; min-height: 100vh; margin: 0; background: var(--mzone-bg); color: var(--mzone-text); font-family: var(--mzone-font-sans); font-size: var(--mzone-font-size-base); line-height: var(--mzone-lh-base); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
button, input, select, textarea { margin: 0; color: inherit; font: inherit; }
button:not(:disabled), [role="button"], label[for], select { cursor: pointer; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
/* Жирный — ровно 700. По умолчанию strong это «bolder»: внутри текста весом 600
   он давал 900, и «Итого» с минусом весило больше, чем с плюсом. */
b, strong { font-weight: 700; }
/* Код и команды — моноширинным набором проекта, а не Courier по умолчанию. */
code, kbd, samp, pre { font-family: var(--mzone-font-mono); font-size: 0.92em; }
/* Поле файла: кнопка выбора рисовалась системной серой «Выберите файлы» чужим
   шрифтом. Шрифт — страницы, вид — как у обычной кнопки. */
input[type=file] { max-width: 100%; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
input[type=file]::file-selector-button { min-height: 36px; margin-right: var(--mzone-space-3); padding: 0 0.75rem; border: 1px solid var(--mzone-border); border-radius: 0.5rem; background: var(--mzone-surface); color: var(--mzone-text); font: inherit; font-weight: 500; cursor: pointer; }
::selection { background: var(--mzone-cyan-200); color: var(--mzone-navy-950); }
:focus-visible { outline: 2px solid var(--mzone-teal-600); outline-offset: 2px; }
/* Атрибут hidden обязан выигрывать у компонентных display: иначе скрытый
   блок (например запасное поле кода под ячейками) остаётся на экране. */
[hidden] { display: none !important; }
.visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }

h1, h2, h3, h4, p { margin-top: 0; }
h1 { margin-bottom: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-2xl); font-weight: 700; letter-spacing: -0.035em; line-height: 1.2; }
h2 { margin-bottom: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-xl); font-weight: 600; letter-spacing: -0.02em; line-height: 1.3; }
h3, h4 { margin-bottom: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-lg); font-weight: 600; line-height: 1.35; }
.text-muted { color: var(--mzone-text-muted) !important; }
/* Название в ячейке, которое можно переносить (регион, статья): вес как у
   .product-title, но без запрета переноса. */
.text-strong { color: var(--mzone-text); font-weight: 550; overflow-wrap: anywhere; }
.text-soft { color: var(--mzone-text-soft) !important; }
.text-small { font-size: var(--mzone-font-size-sm) !important; line-height: 1.35; }
/* Подписи-капсы набраны мелким кеглем, им нужен обычный muted, а не самый бледный. */
.text-caption { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); font-weight: 550; letter-spacing: 0.055em; line-height: 1.3; text-transform: uppercase; }
/* Табличные цифры: все знаки одной ширины, поэтому колонки чисел стоят ровно, а
   не пляшут. Раньше класс приходилось помнить и вешать руками — колонки в
   таблицах и суммы в карточках оставались без него. Теперь любая ячейка таблицы
   и любая цифра KPI получает это по умолчанию. */
.tabular-nums, .money, .metric-value, .kpi-value, .table-numeric,
.table td, .table th, .kpi-card { font-variant-numeric: tabular-nums lining-nums; }
.money, .table-numeric { white-space: nowrap; }

.app { display: grid; grid-template-columns: var(--mzone-sidebar-width) minmax(0,1fr); min-height: 100vh; background: var(--mzone-bg); }
.main { display: flex; min-width: 0; min-height: 100vh; flex-direction: column; }
.content { width: 100%; max-width: var(--mzone-content-max-width); margin: 0 auto; padding: var(--mzone-space-7); }
/* Шапка вкладки площадки: одна строка над содержимым любой её страницы —
   продано, прибыль, ДРР, баланс рекламы, свежесть. Не карточка и не KPI-сетка:
   это подпись «где я и как тут дела», она не должна спорить с содержимым страницы.
   На узком экране распадается на строки сама (flex-wrap), сетку не задаём. */
.mp-strip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--mzone-space-2) var(--mzone-space-5); margin-bottom: var(--mzone-space-4); padding: 0.625rem 0.875rem; border: 1px solid var(--mzone-border-soft); border-radius: var(--mzone-radius-lg); background: var(--mzone-surface-soft); }
.mp-stat { display: flex; min-width: 0; align-items: baseline; gap: 0.375rem; }
.mp-stat-label { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.mp-stat-value { color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 700; font-variant-numeric: tabular-nums; }
.mp-stat-sub { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); }
.mp-strip-end { display: flex; flex-wrap: wrap; align-items: center; gap: var(--mzone-space-2); margin-left: auto; }
/* Шапка страницы переносится: заголовок слева и кнопки справа в «ничейной
   зоне» 1024 px (правило 15) не помещались в строку, и правый край уезжал за
   границу страницы — обход 18.09.2026 поймал это на пяти витринах сразу. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; gap: var(--mzone-space-6); margin-bottom: 1.375rem; }
.page-title-block { display: flex; min-width: 0; flex-direction: column; gap: 0.375rem; }
.page-subtitle { margin: 0; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-base); }
/* wrap обязателен: в правый угол шапки кладут переключатель кабинетов и форму
   рядом, и на 1024 px (та самая «ничейная зона» из правила 15) шесть кабинетов
   выталкивали форму за край страницы — обход 18.09.2026 поймал выезд 57 px на
   недельном отчёте. */
.page-actions, .toolbar, .topbar-actions { display: flex; align-items: center;
  flex-wrap: wrap; gap: 0.625rem; min-width: 0; }
.toolbar { min-width: 0; flex-wrap: wrap; justify-content: space-between; margin-bottom: var(--mzone-space-4); }
/* Тулбар, вложенный прямо в карточку, отступов от неё не получал: поиск и
   фильтры прилипали к краю (жалоба про «Свой склад» — то же было и на других
   витринах). Выравниваем по внутреннему полю карточки. */
.card > .toolbar { padding-right: 1.125rem; padding-left: 1.125rem; }
.card > .toolbar:first-child { padding-top: 1.125rem; }
/* Селект-фильтр в ряду тулбара: по умолчанию input тянется на всю ширину и
   выталкивает соседей на следующую строку (прогноз запаса, 09.09.2026). В ряду
   он должен занимать столько, сколько нужно тексту. */
.input-inline { width: auto; min-width: 10rem; max-width: 16rem; }
.toolbar-group { display: flex; min-width: 0; align-items: center; gap: var(--mzone-space-2); flex-wrap: wrap; }
/* Липкая панель фильтров (магазин · период · календарь): следует за скроллом, чтобы
   период можно было сменить с любого графика внизу страницы. Прилипает ПОД топбар
   (z-index ниже топбара 30 и тултипов графиков 90); фон непрозрачный — контент
   уезжает под панель, а не просвечивает. Отрицательные поля растягивают фон на
   всю ширину .content, иначе по краям видно уезжающие цифры. */
.toolbar-sticky { position: sticky; top: var(--mzone-topbar-height); z-index: 25; margin-right: calc(-1 * var(--mzone-space-4)); margin-left: calc(-1 * var(--mzone-space-4)); padding: var(--mzone-space-3) var(--mzone-space-4); border-bottom: 1px solid var(--mzone-border-soft); background: var(--mzone-bg); }
.date-picker { display: flex; min-width: 0; align-items: center; gap: var(--mzone-space-2); margin: 0; }
.date-picker-input { width: auto; min-height: 36px; padding: 0.3125rem 0.5rem; font-size: var(--mzone-font-size-md); }
.section { margin-bottom: var(--mzone-space-4); }
.section:last-child { margin-bottom: 0; }
.section-header { display: flex; align-items: center; justify-content: space-between; gap: var(--mzone-space-4); margin-bottom: var(--mzone-space-3); }

.sidebar { position: sticky; top: 0; z-index: 40; display: flex; width: var(--mzone-sidebar-width); height: 100vh; padding: var(--mzone-space-7) 1.125rem 1.125rem; overflow: hidden; flex-direction: column; gap: var(--mzone-space-7); background: radial-gradient(circle at 0 0, rgba(46,214,207,0.15), transparent 34%), linear-gradient(180deg, var(--mzone-navy-950) 0%, #071b33 100%); color: var(--mzone-text-inverse); }
.sidebar-logo, .sidebar-brand { display: flex; min-width: 0; align-items: center; gap: var(--mzone-space-3); color: #fff; text-decoration: none; }
.sidebar-logo img { width: auto; height: 54px; max-width: 100%; }
.nav {
  display: flex; min-width: 0; flex: 1 1 auto; flex-direction: column; gap: 0.375rem;
  /* Прокручивается ТОЛЬКО список пунктов: логотип сверху и карточка тарифа снизу
     стоят на месте. Пунктов стало 17, и раньше уезжал весь сайдбар целиком.
     overflow-anchor выключен — иначе браузер сам «доводит» список при смене страницы. */
  overflow-x: hidden; overflow-y: auto; overflow-anchor: none; scrollbar-width: thin;
}
/* Пункты меню НЕ сжимаются. `.nav` — колонка с прокруткой, и её дети по правилам
   флексбокса ужимаются, когда список не влезает по высоте. У пункта есть
   min-height 48px и он выживал, а у подписи группы высоты не было: на телефоне
   она схлопывалась в ноль, и `overflow: hidden` съедал текст целиком — владелец
   увидел «пропали части наименований» (09.09.2026). Список должен прокручиваться,
   а не сплющиваться. */
.nav > * { flex: 0 0 auto; }
.nav::-webkit-scrollbar { width: 6px; }
.nav::-webkit-scrollbar-thumb { border-radius: var(--mzone-radius-pill); background: rgba(255,255,255,0.18); }
.nav-section + .nav-section { margin-top: var(--mzone-space-5); }
.nav-label { margin: 0 0.875rem var(--mzone-space-2); overflow: hidden; color: rgba(255,255,255,0.4); font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.09em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
.nav-item { position: relative; display: flex; width: 100%; min-width: 0; min-height: 48px; padding: 0 0.875rem; align-items: center; gap: var(--mzone-space-3); border: 1px solid transparent; border-radius: var(--mzone-radius-md); background: transparent; color: rgba(255,255,255,0.76); font-size: var(--mzone-font-size-md); font-weight: 550; line-height: 1.2; text-align: left; text-decoration: none; transition: color var(--mzone-transition), background-color var(--mzone-transition), border-color var(--mzone-transition); }
.nav-item:hover { background: rgba(255,255,255,0.07); color: #fff; }
.nav-item.active, .nav-item[aria-current="page"] { border-color: rgba(46,214,207,0.22); background: linear-gradient(135deg, rgba(15,163,163,0.95), rgba(6,20,43,0.2)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.08); color: #fff; }
.nav-item-icon, .nav-item > svg { width: 22px; height: 22px; flex: 0 0 22px; opacity: 0.82; }

/* Вкладка площадки: свёрнутая группа страниц одного маркетплейса. Открыта та,
   в которой сейчас работаем (решение владельца 17.08 — «для каждого своя вкладка»).
   details/summary выбраны сознательно: сворачивание работает и без JS. */
.nav-mp { margin-top: var(--mzone-space-2); }
.nav-mp-summary { display: flex; min-height: 40px; padding: 0 0.875rem; align-items: center;
  gap: var(--mzone-space-2); border-radius: var(--mzone-radius-md); cursor: pointer;
  color: rgba(255,255,255,0.76); list-style: none; }
.nav-mp-summary::-webkit-details-marker { display: none; }
.nav-mp-summary::after { margin-left: auto; color: rgba(255,255,255,0.5); content: "▼"; font-size: 0.7em;
  font-size: 0.75rem; transition: transform var(--mzone-transition); }
.nav-mp[open] > .nav-mp-summary::after { transform: rotate(180deg); }
.nav-mp-summary:hover { background: rgba(255,255,255,0.07); }
.nav-mp .nav-item { min-height: 42px; padding-left: 1.5rem; }
/* Точка беды на вкладке: красная — сломалось, жёлтая — стоит посмотреть. Стоит
   ПЕРЕД стрелкой (та прижата margin-left:auto), чтобы не прыгала по ширине названия. */
.nav-mp-dot { width: 8px; height: 8px; flex: 0 0 8px; border-radius: 50%; }
.nav-mp-dot-error { background: var(--mzone-danger); box-shadow: 0 0 0 3px rgba(220,38,38,0.25); }
.nav-mp-dot-warn { background: var(--mzone-warning); box-shadow: 0 0 0 3px rgba(217,119,6,0.22); }
/* Иконки набора — картинки, а не контурные svg: цветом их не подсветить. Активный
   пункт узнаётся фоном и рамкой, а иконке возвращаем полную непрозрачность. */
.nav-item:hover .nav-item-icon { opacity: 0.95; }
.nav-item.active .nav-item-icon, .nav-item.active > svg { color: var(--mzone-cyan-400); opacity: 1; }
/* Длинное название пункта переносится на вторую строку, а не режется многоточием.
   «Рекомендации по отгру…» не говорит, куда ведёт пункт, а высоты у строки меню
   и так 48px — две строки по 1.2 в неё помещаются (жалоба владельца 09.09.2026).
   Многоточие остаётся страховкой для совсем длинных названий. */
.nav-item-text { display: -webkit-box; min-width: 0; overflow: hidden;
  -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow-wrap: anywhere; text-overflow: ellipsis; }
.nav-item-count { display: inline-flex; min-width: 22px; height: 22px; margin-left: auto; padding: 0 0.375rem; align-items: center; justify-content: center; border-radius: var(--mzone-radius-pill); background: rgba(255,255,255,0.12); color: #fff; font-size: var(--mzone-font-size-xs); font-weight: 600; font-variant-numeric: tabular-nums; }
.plan-card { flex: 0 0 auto; margin-top: var(--mzone-space-5); padding: 1.125rem; border: 1px solid rgba(255,255,255,0.1); border-radius: 1rem; background: rgba(255,255,255,0.07); box-shadow: inset 0 1px 0 rgba(255,255,255,0.06); }
/* Карточка тарифа служебная, крупный кегль ей ни к чему: на укрупнённой шкале
   подпись «демо-режим · без биллинга» разъезжалась на три строки. */
.plan-title { display: flex; align-items: center; gap: var(--mzone-space-2); justify-content: space-between; color: #fff; font-size: var(--mzone-font-size-md); font-weight: 600; }
.plan-muted { margin: 0.375rem 0 0.875rem; color: rgba(255,255,255,0.62); font-size: var(--mzone-font-size-sm); }

.topbar { position: sticky; top: 0; z-index: 30; display: flex; min-width: 0; min-height: var(--mzone-topbar-height); padding: 0 var(--mzone-space-7); align-items: center; justify-content: space-between; gap: var(--mzone-space-6); border-bottom: 1px solid var(--mzone-border-soft); background: var(--mzone-topbar-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); }
.topbar-start, .topbar-end { display: flex; min-width: 0; align-items: center; gap: var(--mzone-space-3); }
.topbar-start { flex: 1 1 auto; }
/* Сжимается последним, но сжимается: с `0 0 auto` правый край шапки выдавливал
   страницу за экран, когда чипов оказывалось больше, чем места. */
.topbar-end { flex: 0 1 auto; }
.search-wrap { position: relative; width: min(460px, 100%); }
.search-icon { position: absolute; top: 50%; left: var(--mzone-space-4); width: 18px; height: 18px; color: var(--mzone-text-soft); pointer-events: none; transform: translateY(-50%); }
.search { width: 100%; height: 44px; padding: 0 var(--mzone-space-4) 0 2.75rem; border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); outline: 0; background: var(--mzone-surface); color: var(--mzone-text); font-size: var(--mzone-font-size-base); transition: border-color var(--mzone-transition), box-shadow var(--mzone-transition); }
.search::placeholder { color: var(--mzone-text-soft); }
.search:focus { border-color: var(--mzone-cyan-400); box-shadow: var(--mzone-focus-ring); }
/* Подсказки поиска: список товаров прямо под полем шапки. */
.search-pop { position: absolute; z-index: 60; top: calc(100% + 4px); left: 0; width: 100%; max-height: 60vh; overflow-y: auto; padding: var(--mzone-space-1); border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); box-shadow: var(--mzone-shadow-card); }
.search-item { display: flex; min-height: 44px; padding: var(--mzone-space-2); align-items: center; gap: var(--mzone-space-2); border-radius: var(--mzone-radius-sm); color: var(--mzone-text); text-decoration: none; }
.search-item:hover { background: var(--mzone-surface-muted); }
.search-item-name { min-width: 0; overflow: hidden; flex: 1 1 auto; text-overflow: ellipsis; white-space: nowrap; }
.search-item-code { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); white-space: nowrap; }
.topbar-divider { width: 1px; height: 28px; background: var(--mzone-border-soft); }
/* Не сжимается: когда шапке не хватало места, кнопка схлопывалась до 12 px, а аватар
   в 38 px вылезал за неё — на телефоне по пользователю было не попасть. Сжимается поиск. */
/* `flex: 0 0 auto` держал блок несжимаемым: на укрупнённой шкале имя с ролью
   выросли и вытолкнули страницу на 31px вправо. Пусть сжимается — имя и роль
   и так обрезаются многоточием (.user-name, .user-role). */
.user-menu { display: flex; min-width: 0; flex: 0 1 auto; align-items: center; gap: 0.625rem; border: 0; background: transparent; text-align: left; }
.avatar { display: inline-grid; width: 38px; height: 38px; flex: 0 0 38px; overflow: hidden; place-items: center; border: 1px solid rgba(15,163,163,0.18); border-radius: 50%; background: var(--mzone-cyan-100); color: var(--mzone-teal-700); font-size: var(--mzone-font-size-md); font-weight: 700; }
.user-copy { display: flex; min-width: 0; flex-direction: column; }  /* имя и роль в столбик — не слипаются */
.user-name, .user-role { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-name { color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; }
.user-role { margin-top: 1px; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); }

/* Меню под именем: профиль, токены, админка, выход. Настройки открывают редко,
   а место в левом меню они занимали постоянно (решение владельца 17.08). */
.user-wrap { position: relative; }
.user-dropdown { position: absolute; z-index: 40; top: calc(100% + 0.5rem); right: 0;
  display: flex; min-width: 232px; flex-direction: column; padding: 0.375rem;
  border: 1px solid var(--mzone-border); border-radius: 0.625rem;
  background: var(--mzone-surface); box-shadow: var(--mzone-shadow-card-hover); }
.user-dropdown[hidden] { display: none; }
.user-dropdown form { margin: 0; }
.user-dropdown-item { display: block; width: 100%; padding: 0.5rem 0.625rem; border: 0;
  border-radius: 0.5rem; background: transparent; color: var(--mzone-text); cursor: pointer;
  font-size: var(--mzone-font-size-md); text-align: left; text-decoration: none; }
.user-dropdown-item:hover { background: var(--mzone-surface-soft); }

.btn, .icon-btn { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; border: 1px solid var(--mzone-border); outline: 0; background: var(--mzone-surface); color: var(--mzone-text); font-weight: 500; text-decoration: none; transition: background-color var(--mzone-transition), border-color var(--mzone-transition), box-shadow var(--mzone-transition), color var(--mzone-transition); }
.btn { min-height: 40px; padding: 0 0.75rem; gap: var(--mzone-space-2); border-radius: 0.5rem;
  font-size: var(--mzone-font-size-md); line-height: var(--mzone-lh-lg); white-space: nowrap; }
.icon-btn { width: 40px; height: 40px; padding: 0; border-radius: 0.5rem; }
.btn svg, .icon-btn svg { width: 18px; height: 18px; flex: 0 0 18px; }
.btn:hover, .icon-btn:hover { border-color: #cbd5e1; background: var(--mzone-surface-soft); }
.btn:focus-visible, .icon-btn:focus-visible { outline: 2px solid var(--mzone-teal-600);
  outline-offset: 2px; box-shadow: none; }
.btn-primary { border-color: var(--mzone-solid); background: var(--mzone-solid); box-shadow: 0 8px 20px rgba(6,20,43,0.16); color: var(--mzone-solid-ink); }
.btn-primary:hover { border-color: var(--mzone-solid-hover); background: var(--mzone-solid-hover); }
/* teal-800, а не teal-600: белый текст на teal-600 давал контраст 2.9:1 — провал
   WCAG AA, а teal-700 балансирует на 4.46 при пороге 4.5 (см. комментарий палитры).
   teal-800 даёт ~7:1 с запасом (axe, обход 24.08). */
.btn-accent { border-color: var(--mzone-teal-800); background: var(--mzone-teal-800); color: #fff; }
.btn-accent:hover { border-color: var(--mzone-teal-900); background: var(--mzone-teal-900); }
.btn-ghost { border-color: transparent; background: transparent; box-shadow: none; }
.btn-sm { min-height: 34px; padding-inline: 0.75rem; border-radius: var(--mzone-radius-sm); font-size: var(--mzone-font-size-md); }
/* Переносится и не шире своего места: в переключатель кладут ВСЕ кабинеты
   пользователя, у владельца их восемь. Пока полоса была неразрывной, она
   выталкивала соседнюю форму за край страницы на 1024 px (обход 18.09.2026,
   недельный отчёт). Сами кнопки внутри остаются nowrap — рвать «ИП Шарова
   М. А.» посередине хуже, чем перенести целиком. */
.segmented { display: inline-flex; min-height: 40px; max-width: 100%; flex-wrap: wrap;
  padding: var(--mzone-space-1); align-items: center; gap: var(--mzone-space-1);
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md);
  background: var(--mzone-surface); }
.segmented button, .segmented-item { display: inline-flex; min-height: 30px; padding: 0 0.875rem; align-items: center; justify-content: center; gap: 0.375rem; border: 0; border-radius: 0.5625rem; background: transparent; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-md); font-weight: 550; text-decoration: none; white-space: nowrap; transition: color var(--mzone-transition), background-color var(--mzone-transition); }
.segmented button:hover, .segmented-item:hover { color: var(--mzone-text); }
.segmented button.active, .segmented button[aria-selected="true"], .segmented-item.active, .segmented-item[aria-selected="true"] { background: var(--mzone-solid); box-shadow: 0 3px 10px rgba(6,20,43,0.12); color: var(--mzone-solid-ink); }
/* Магазин в шапке. Отдельный класс, а не `.date-chip`: тот прячется с 1440 px как
   дубль фильтров страницы, а имя кабинета нигде больше не продублировано — его
   убираем только на телефоне, где чипы кабинетов и так на первом экране. */
.cab-chip { display: inline-flex; max-width: 260px; min-height: 40px; padding: 0 0.875rem; align-items: center; gap: 0.5rem; overflow: hidden; border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.date-chip, .filter-chip { display: inline-flex; min-height: 40px; padding: 0 0.875rem; align-items: center; gap: 0.5rem; border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; white-space: nowrap; }
.filter-chip.active, .filter-chip[aria-pressed="true"] { border-color: rgba(15,163,163,0.3); background: var(--mzone-cyan-100); color: var(--mzone-teal-700); }

.kpi-grid, .dashboard-grid, .bottom-grid, .cards-grid, .form-grid { display: grid; gap: var(--mzone-space-4); }
/* auto-fit, а не фиксированные 6: витрина с четырьмя плитками раскладывала их
   3+1 и оставляла дыру в пол-экрана. Пустые дорожки схлопываются, оставшиеся
   растягиваются — ряд заполнен при любом числе карточек. */
/* Число колонок задаём КЛАССОМ, а не инлайн-стилем: инлайн сильнее медиазапроса,
   и такая сетка не сворачивалась на узком экране. На «Планировании» из-за этого
   четыре карточки требовали 861 px там, где было 744 (аудит 11.08). */
.kpi-grid { grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  margin-bottom: var(--mzone-space-4); }
/* Фиксированное число колонок — ПОСЛЕ .kpi-grid. Стояли выше, и при равной
   специфичности auto-fit перебивал класс: «kpi-grid kpi-3» на «Юнит-экономике»
   вычислялся в две колонки, шесть плиток «Товаров», PnL и «Рекламы» ложились 5+1
   и 4+2 (обход 24.09.2026). Шесть плиток — kpi-3 (3+3), пять — kpi-5. */
.kpi-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.kpi-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.kpi-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
/* Карточки рядом не тянутся по высоте соседа: таблица остатков в 1 600 px
   оставляла в соседней карточке 1 400 px пустоты. PnL и Сводка просили это
   инлайном в каждом месте, теперь это правило сетки. */
.dashboard-grid { grid-template-columns: minmax(0,1.35fr) minmax(320px,1fr); align-items: start; margin-bottom: var(--mzone-space-4); }
.bottom-grid { grid-template-columns: minmax(0,1.6fr) minmax(280px,1fr) minmax(280px,1fr); }
.cards-grid { grid-template-columns: repeat(3, minmax(0,1fr)); }
/* Ряд из ЧЕТЫРЁХ плиток (склад: коробки, раскладка, прогноз, магазин). Стоит
   сразу за `.cards-grid`, а не в конце файла: медиазапросы ниже сужают ряд до
   двух и одной колонки, и они обязаны выигрывать у этого правила по порядку.
   Без класса четвёртая плитка падала на свою строку и висела в пустоте. */
.cards-grid-4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
/* Широкие карточки (каталог с фото и кнопками): сколько влезет по 380px, а на
   узком экране — одна колонка. `min(380px, 100%)` вместо голых 380px: иначе на
   375px карточка шире экрана (правило 7 стайлгайда: сетка классом, не инлайном). */
.cards-grid-wide { grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 1fr)); }
/* Карточки, число которых заранее неизвестно (пояснения режимов налога): сколько
   влезет по 320px, и ряд РАСТЯГИВАЕТСЯ. Одна карточка в сетке на три колонки
   занимала треть экрана рядом с двумя пустыми (обход 24.09.2026). */
.cards-grid-fit { display: grid; gap: var(--mzone-space-4); grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.cards-grid-fit > * { min-width: 0; }
/* Длинный список полос (остатки по складам) во всю ширину — в несколько колонок. */
.metric-list.metric-cols { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); column-gap: var(--mzone-space-6); row-gap: var(--mzone-space-3); }
/* Карточки страницы одна под другой, с тем же шагом, что у сеток. */
.stack-cards { display: grid; gap: var(--mzone-space-4); margin-bottom: var(--mzone-space-4); }
.stack-cards > * { min-width: 0; }
.form-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
.col-span-2 { grid-column: span 2; }
.col-span-full { grid-column: 1 / -1; }

.card, .chart-card, .kpi-card { min-width: 0; border: 1px solid var(--mzone-border-soft); background: var(--mzone-surface); box-shadow: var(--mzone-shadow-card); }
.card { border-radius: var(--mzone-radius-lg); }
.card-padding, .card-body { padding: 1.125rem; }
/* Шапка карточки переносится: заголовок и значок рядом не влезают в 320 px, и
   значок выезжал за экран (обход 18.09.2026). Перенос лучше выезда: на узком
   экране значок просто встаёт под заголовком. */
.card-header { display: flex; flex-wrap: wrap; min-width: 0; padding: 1.125rem 1.125rem 0; align-items: flex-start; justify-content: space-between; gap: var(--mzone-space-4); }
.card-header + .card-body { padding-top: var(--mzone-space-4); }
.card-title { color: var(--mzone-text); font-size: var(--mzone-font-size-lg); font-weight: 600; line-height: 1.35; }
.card-subtitle { margin-top: var(--mzone-space-1); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
/* Подвал карточки переносится: на телефоне подсказка рядом с кнопкой не влезает
   и вылезала за экран (замер typo.mjs на 375 px, 11.09.2026). */
.card-footer { display: flex; flex-wrap: wrap; padding: var(--mzone-space-4) 1.125rem; align-items: center; justify-content: space-between; gap: var(--mzone-space-3); border-top: 1px solid var(--mzone-border-soft); }
.card-footer > .text-small { min-width: 0; overflow-wrap: anywhere; }
.card-empty { display: grid; min-height: 220px; padding: var(--mzone-space-7); place-items: center; color: var(--mzone-text-muted); text-align: center; }
/* Иконка над текстом пустого состояния. Раньше на пустой странице висела одна серая
   строчка, и она читалась как «сломалось», а не как «данных ещё нет».
   Картинку задаём переменной: у иконок набора тёмно-синяя масса, на тёмной теме её
   не видно, поэтому там подставляется вариант «-inverse» (масса и вырезы меняются
   местами, см. app/web/static/icons/README.md). Страница выбирает иконку атрибутом
   data-icon; без атрибута берётся нейтральная. */
.card-empty { --empty-icon: url("/static/icons/day-summary.svg"); }
.card-empty::before { content: ""; display: block; width: 64px; height: 64px; margin-bottom: var(--mzone-space-4); background: center / contain no-repeat var(--empty-icon); opacity: 0.65; }
.card-empty[data-icon="orders"] { --empty-icon: url("/static/icons/orders.svg"); }
.card-empty[data-icon="products"] { --empty-icon: url("/static/icons/product-catalog.svg"); }
.card-empty[data-icon="ads"] { --empty-icon: url("/static/icons/ads.svg"); }
.card-empty[data-icon="cost"] { --empty-icon: url("/static/icons/cost-price.svg"); }
.card-empty[data-icon="balance"] { --empty-icon: url("/static/icons/balance.svg"); }
.card-empty[data-icon="week"] { --empty-icon: url("/static/icons/week.svg"); }
.card-empty[data-icon="warehouse"] { --empty-icon: url("/static/icons/warehouse.svg"); }
.card-empty[data-icon="supply"] { --empty-icon: url("/static/icons/supply-plan.svg"); }
.card-empty[data-icon="bins"] { --empty-icon: url("/static/icons/bin-storage.svg"); }
.card-empty[data-icon="token"] { --empty-icon: url("/static/icons/api-key.svg"); }
.card-empty[data-icon="users"] { --empty-icon: url("/static/icons/users.svg"); }
.card-empty[data-icon="leads"] { --empty-icon: url("/static/icons/leads.svg"); }
.card-empty[data-icon="fbs"] { --empty-icon: url("/static/icons/fbs-assembly.svg"); }
.card-empty[data-icon="penalties"] { --empty-icon: url("/static/icons/penalties.svg"); }
.card-empty[data-icon="generation"] { --empty-icon: url("/static/icons/generation.svg"); }
:root[data-theme="dark"] .card-empty { --empty-icon: url("/static/icons/day-summary-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="orders"] { --empty-icon: url("/static/icons/orders-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="products"] { --empty-icon: url("/static/icons/product-catalog-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="ads"] { --empty-icon: url("/static/icons/ads-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="cost"] { --empty-icon: url("/static/icons/cost-price-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="balance"] { --empty-icon: url("/static/icons/balance-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="week"] { --empty-icon: url("/static/icons/week-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="warehouse"] { --empty-icon: url("/static/icons/warehouse-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="supply"] { --empty-icon: url("/static/icons/supply-plan-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="bins"] { --empty-icon: url("/static/icons/bin-storage-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="token"] { --empty-icon: url("/static/icons/api-key-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="users"] { --empty-icon: url("/static/icons/users-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="leads"] { --empty-icon: url("/static/icons/leads-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="fbs"] { --empty-icon: url("/static/icons/fbs-assembly-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="penalties"] { --empty-icon: url("/static/icons/penalties-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="generation"] { --empty-icon: url("/static/icons/generation-inverse.svg"); }

/* Цифра прижата к верху, подпись — к низу (`margin-top: auto` у .kpi-bottom).
   Со space-between карточка с двухстрочной подписью поднимала цифру, с
   однострочной — опускала, и цифры соседних карточек стояли на разных уровнях
   (жалоба владельца 04.09: «цифры скачут»). */
.kpi-card { display: flex; min-height: 138px; padding: 1.125rem; flex-direction: column; border-radius: var(--mzone-radius-lg); }
/* min-width: 0 обязателен: без него длинная подпись в карточке не сжимается и
   выталкивает иконку за край экрана (на 1024 юнит-экономика уезжала на 20px). */
.kpi-top { display: flex; min-width: 0; min-height: 40px; align-items: flex-start; justify-content: space-between; gap: var(--mzone-space-3); }
/* Подпись служебная, крупный кегль ей не нужен: рядом с иконкой 40px длинная
   подпись вроде «Заказы · 30 дней» вставала в две строки и карточка росла.
   line-height 1.3: две строки подписи (≈36px) остаются ниже иконки (40px), и
   цифра под двухстрочной подписью стоит на той же высоте, что у соседей —
   с базовым межстрочным она уезжала на 8px вниз («Чистая прибыль» на 768). */
.kpi-label { min-width: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-sm); font-weight: 550; line-height: 1.3; overflow-wrap: anywhere; }
.kpi-icon, .accent-icon { display: grid; width: 40px; height: 40px; flex: 0 0 40px; place-items: center; border-radius: var(--mzone-radius-md); background: var(--mzone-cyan-100); color: var(--mzone-teal-600); }
.kpi-icon svg, .accent-icon svg { width: 20px; height: 20px; }
/* Иконка из своего набора в плитке цифры. Задаётся атрибутом data-icon, картинка —
   переменной: на тёмной теме подставляется вариант «-inverse», иначе тёмно-синяя
   масса пропадает на тёмной плитке. Плашку под ней убираем: у набора свои цвета,
   и бирюзовый квадрат под бирюзовой иконкой превращается в кашу. */
.kpi-icon[data-icon] { background: none; }
.kpi-icon[data-icon]::before { content: ""; width: 30px; height: 30px; background: center / contain no-repeat var(--kpi-icon); }
.kpi-icon[data-icon="profit"] { --kpi-icon: url("/static/icons/profit.svg"); }
.kpi-icon[data-icon="sold"] { --kpi-icon: url("/static/icons/buyout.svg"); }
.kpi-icon[data-icon="pnl"] { --kpi-icon: url("/static/icons/profit-and-loss.svg"); }
.kpi-icon[data-icon="balance"] { --kpi-icon: url("/static/icons/balance.svg"); }
.kpi-icon[data-icon="commission"] { --kpi-icon: url("/static/icons/commission.svg"); }
.kpi-icon[data-icon="tax"] { --kpi-icon: url("/static/icons/tax.svg"); }
.kpi-icon[data-icon="cost"] { --kpi-icon: url("/static/icons/cost-price.svg"); }
.kpi-icon[data-icon="logistics"] { --kpi-icon: url("/static/icons/logistics.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="profit"] { --kpi-icon: url("/static/icons/profit-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="sold"] { --kpi-icon: url("/static/icons/buyout-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="pnl"] { --kpi-icon: url("/static/icons/profit-and-loss-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="balance"] { --kpi-icon: url("/static/icons/balance-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="commission"] { --kpi-icon: url("/static/icons/commission-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="tax"] { --kpi-icon: url("/static/icons/tax-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="cost"] { --kpi-icon: url("/static/icons/cost-price-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="logistics"] { --kpi-icon: url("/static/icons/logistics-inverse.svg"); }
.kpi-value { margin-top: var(--mzone-space-3); color: var(--mzone-text); font-size: clamp(1.3rem, 1.05rem + 0.55vw, 1.7rem); font-weight: 700; letter-spacing: -0.015em; line-height: 1.1; white-space: nowrap; }
/* Словесное значение вместо цифры («2 дня назад», «нет данных»): кегль меньше и
   перенос разрешён, иначе фраза не влезает в карточку. Цифры этим классом не
   помечаются — они везде одного размера. */
.kpi-value.kpi-value-text { font-size: var(--mzone-font-size-xl); letter-spacing: 0; line-height: 1.2; white-space: normal; overflow-wrap: anywhere; }
/* `flex-wrap` на контейнере + `flex: 0 0 auto` на подписях: подпись, которой не
   хватило места, уходит на новую строку ЦЕЛИКОМ. Раньше wrap жил внутри каждой
   подписи, и строки двух соседних («рентабельность» и «к пр. периоду»)
   сплетались в кашу (жалоба владельца 04.09). max-width страхует одиночную
   длинную фразу: она всё ещё переносится по словам внутри себя. */
.kpi-bottom { display: flex; min-width: 0; margin-top: auto; padding-top: var(--mzone-space-2); flex-wrap: wrap; align-items: baseline; justify-content: space-between; column-gap: var(--mzone-space-3); row-gap: 0.125rem; }
.kpi-bottom > .kpi-trend { flex: 0 0 auto; max-width: 100%; }
.kpi-trend { display: inline-flex; min-width: 0; align-items: center; gap: 0.375rem; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); flex-wrap: wrap; }
.kpi-trend strong { font-weight: 600; }
/* Цвет говорит о знаке, а не о весе. Жирные цветные числа стояли в одной строке
   с обычными чёрными («276 037 ₽» рядом с жирными «130 145 ₽» и «47,15%»), и
   владелец читал это как разнобой шрифтов (жалоба 04.09, обход 24.09.2026). */
.kpi-trend.positive strong, .value-positive { color: var(--mzone-success) !important; }
.kpi-trend.negative strong, .value-negative { color: var(--mzone-danger) !important; }
/* Цветная цифра остаётся того же веса, что и чёрная: вес 600 из .value-* делал
   «Чистую прибыль» и «Все удержания» тоньше соседей — читалось как разнобой шрифтов. */
.kpi-value.value-positive, .kpi-value.value-negative { font-weight: 700; }
/* «нет данных» вместо цифры. Не серая цифра, а отсутствие цифры: правило CLAUDE.md
   «лучше показать нет данных, чем неточное число». Размер мельче, потому что это
   слова, а не число — иначе крупный текст читается как значение. */
.kpi-value.value-muted {
  color: var(--mzone-text-muted);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0;
}
/* спарклайн тянется на всё свободное место справа от тренда — широкая карточка
   выглядит как мини-график, а не «число слева + пустота справа» */
.kpi-sparkline { flex: 1 1 auto; width: auto; min-width: 64px; max-width: 320px; height: 38px; margin-left: var(--mzone-space-3); color: var(--mzone-cyan-400); }

/* ── Панель показателей «Сводки» ──────────────────────────────────────────
   Стена KPI-карточек разделами. Каждая карточка объясняет своё число: подпись
   «что это», база под значением, динамика к прошлому периоду и сворачиваемое
   «как считается». Без последнего верный расчёт остаётся необъяснимым — ровно
   на это владелец и жаловался при прошлом копировании чужой витрины. */
.panel-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--mzone-space-3); margin: var(--mzone-space-6) 0 var(--mzone-space-3); }
.panel-head-title { margin: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-xs); font-weight: 650; letter-spacing: 0.055em; line-height: 1.3; text-transform: uppercase; }
.panel-head-note { min-width: 0; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); overflow-wrap: anywhere; }
/* Подпись под заголовком карточки: словами, что это за число. */
.kpi-caption { margin-top: var(--mzone-space-1); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); line-height: 1.35; overflow-wrap: anywhere; }
/* База/знаменатель числа. Процент без неё обманывает: «выкуп 44,6%» из двенадцати
   заказов и из тысячи — разные утверждения. */
.kpi-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.375rem; margin-top: var(--mzone-space-2); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); overflow-wrap: anywhere; }
.kpi-note strong { color: var(--mzone-text); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Без курсива: у Inter нет курсивного файла, и браузер наклонял буквы сам. */
.kpi-note-missing { color: var(--mzone-text-muted); }
/* Важность находки точкой цвета (разбор кампании): эмодзи рисовались чужим шрифтом. */
.sev-dot { display: inline-block; width: 8px; height: 8px; margin-right: 6px; border-radius: 50%; background: var(--mzone-text-muted); vertical-align: 1px; }
.sev-dot.sev-crit { background: var(--mzone-danger); }
.sev-dot.sev-warn { background: var(--mzone-warning); }
/* Оговорка, которую нельзя прятать под раскрытие: число посчитано, но неполно. */
.kpi-warn { margin-top: var(--mzone-space-2); padding: var(--mzone-space-2) var(--mzone-space-3); border-radius: var(--mzone-radius-sm); background: var(--mzone-warning-bg); color: var(--mzone-text); font-size: var(--mzone-font-size-xs); line-height: 1.4; overflow-wrap: anywhere; }
/* «Как считается» — нативный details, без JS: работает пальцем и скринридером. */
.kpi-why { margin-top: var(--mzone-space-3); padding-top: var(--mzone-space-2); border-top: 1px solid var(--mzone-border-soft); }
.kpi-why > summary { color: var(--mzone-teal-600); cursor: pointer; font-size: var(--mzone-font-size-xs); font-weight: 600; list-style: none; }
.kpi-why > summary::-webkit-details-marker { display: none; }
.kpi-why > summary::after { content: " ↓"; }
.kpi-why[open] > summary::after { content: " ↑"; }
.kpi-why p { margin: var(--mzone-space-2) 0 0; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); line-height: 1.45; overflow-wrap: anywhere; }
.kpi-why-source { color: var(--mzone-text-soft); }
/* ── Карточка панели, 24.09.2026 ─────────────────────────────────────────
   Владелец: «на сводке всё как будто скомкано». Причин было три, все здесь.
   1. Сетка резала строку на столбцы от 178 px, и в карточку шириной ~190 px
      ложилось шесть уровней текста: подпись и пояснение шли в две-три строки.
      Своя сетка панели — столбец от 232 px, на 1440 четыре карточки в ряд.
   2. Место под иконку (min-height 40px у .kpi-top) держалось, хотя иконок в
      панели нет: между названием и цифрой стояла пустота.
   3. Пояснение «что это» над цифрой то в одну строку, то в две сдвигало цифру,
      и цифры одного ряда стояли на разной высоте — та же беда, что «цифры
      скачут» 04.09. Пояснение и «как считается» ушли под кружок «?» в углу
      (`.kpi-help`, как у контроля FBS): цифра теперь сразу под названием. */
.kpi-grid.kpi-grid-panel { grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  row-gap: 0; column-gap: var(--mzone-space-4); }
/* Карточки одного ряда делят СТРОКИ сетки (subgrid): название, цифра, база,
   оговорка, динамика. Длинное название («Себестоимость на продажу» в две строки)
   раньше опускало свою цифру ниже соседних — теперь строка названия общая на весь
   ряд, и цифры стоят на одной высоте при любом переносе. Оговорка одной карточки
   занимает свою строку, а не растягивает середину соседних. Строка у каждой части
   своя явно: у карточки без оговорки динамика не уезжает на её место. */
.kpi-card.kpi-card-panel { display: grid; grid-row: span 5; grid-template-rows: subgrid;
  row-gap: 0; min-height: 0; margin-bottom: var(--mzone-space-4); padding: var(--mzone-space-5); }
.kpi-card-panel > .kpi-top { grid-row: 1; min-height: 0; padding-right: var(--mzone-space-7); }
.kpi-card-panel > .kpi-value { grid-row: 2; margin-top: var(--mzone-space-2); }
.kpi-card-panel > .kpi-note { grid-row: 3; align-self: start; }
.kpi-card-panel > .kpi-warn { grid-row: 4; align-self: start; }
.kpi-card-panel > .kpi-bottom { grid-row: 5; align-self: end; margin-top: 0; padding-top: var(--mzone-space-3); }
.kpi-card-panel .kpi-help { top: var(--mzone-space-4); right: var(--mzone-space-4); }
/* Окно пояснения не шире карточки: у крайней левой при 300 px оно заходило
   на меню слева на три десятка пикселей. */
.kpi-card-panel .kpi-help-body { width: min(256px, 78vw); }
.kpi-help-body p { margin: var(--mzone-space-2) 0 0; color: var(--mzone-text-muted); line-height: 1.45; }
.kpi-help-body .kpi-help-term { color: var(--mzone-text); font-weight: 600; }
.kpi-help-body .kpi-help-source { color: var(--mzone-text-soft); font-size: var(--mzone-font-size-xs); }
/* Одна строка-легенда над панелью вместо 27 повторов «к прошлому периоду». */
.panel-legend { margin: var(--mzone-space-6) 0 0; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.panel-legend + .panel-head { margin-top: var(--mzone-space-4); }
/* Разделы панели дышат: между «Продажами» и «Финансами» было 24 px — столько
   же, сколько между карточками одного ряда, и разделы сливались в одну стену. */
.panel-head { margin-top: var(--mzone-space-8); }
/* Воронка без инлайн-стилей (правило 2). */
.funnel-list { display: grid; gap: var(--mzone-space-3); }
.funnel-label { font-size: var(--mzone-font-size-sm); font-weight: 650; }
/* Денежный ряд «Чистая прибыль · Продано · …» нужен телефону: там он поднимается
   первым экраном (data-mobile-first), чтобы утром прибыль была видна без
   прокрутки. На широком экране он повторял раздел «Финансы» панели в третий
   раз за страницу, другим оформлением рубля, — и прятать его там незачем. */
@media (min-width: 768px) {
  .kpi-grid[data-mobile-first] { display: none; }
}
/* На телефоне этот ряд — первый экран, и пять крупных карточек в столбик
   занимали ~800 px до фильтров, с пустотой под несуществующей иконкой.
   Две колонки, прибыль во всю ширину: деньги видны одним взглядом. */
@media (max-width: 767px) {
  .kpi-grid[data-mobile-first] { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--mzone-space-3); }
  .kpi-grid[data-mobile-first] > .kpi-card:first-child { grid-column: 1 / -1; }
  .kpi-grid[data-mobile-first] > .kpi-card { min-height: 0; padding: var(--mzone-space-4); }
  .kpi-grid[data-mobile-first] > .kpi-card:not(:first-child) .kpi-top { min-height: 0; }
}
.metric-list { display: grid; gap: var(--mzone-space-3); }
.metric-row { display: flex; align-items: center; justify-content: space-between; gap: var(--mzone-space-3); }
.metric-label { min-width: 0; color: var(--mzone-text-muted); }
/* `flex: 0 0 auto` не давал длинному значению сжаться, и строка вида «остаток 340 шт ·
   заказов за 30 дн: 45 · хватит на 12 дн.» выдавливала страницу за экран (аудит 11.08,
   «Рекомендации по отгрузкам» на 1024 px). Значение сжимается и переносится. */
.metric-value { flex: 0 1 auto; min-width: 0; color: var(--mzone-text); font-weight: 600; text-align: right; overflow-wrap: anywhere; }

.chart-card { padding: var(--mzone-space-5); border-radius: 1.25rem; }
.chart-header { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--mzone-space-2) var(--mzone-space-4); margin-bottom: var(--mzone-space-4); }
.chart-area { position: relative; width: 100%; height: 280px; min-height: 220px; }
.chart-line-primary { fill: none; stroke: var(--mzone-navy-950); stroke-linecap: round; stroke-linejoin: round; stroke-width: 3; }
.chart-line-accent { fill: none; stroke: var(--mzone-cyan-400); stroke-linecap: round; stroke-linejoin: round; stroke-width: 3; }
.chart-area-fill { fill: rgba(46,214,207,0.11); }
.chart-grid-line { stroke: #eaf0f6; stroke-width: 1; }
.chart-axis-text { fill: var(--mzone-text-muted); font-family: var(--mzone-font-sans); font-size: var(--mzone-font-size-sm); }
/* Воронка Сводки: подпись, полоса, число. Фиксированные 150px + 120px плюс
   отступы не влезают в 320px, и строка выезжала вбок (обход 18.09.2026).
   На узком экране подпись и число встают над полосой. */
/* Правка ставки прямо в строке таблицы налогов. Без этого селект режима держит
   10rem (общий .input-inline), и на телефоне строка растягивалась на четыре
   уровня пустоты (замер 18.09.2026). */
/* Поля в ОДНУ линию: таблица и так прокручивается вбок, а перенос внутри ячейки
   растягивал строку на четыре уровня и делал таблицу нечитаемой на телефоне. */
.tax-edit { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--mzone-space-2); }
/* Название режима целиком: «УСН «доходы − расходы»» обрезалось до «УСН «доходы:». */
.tax-edit select { min-width: 12.5rem; max-width: 15rem; }
.tax-edit input { width: 3.5rem; min-width: 3.5rem; text-align: right; }
/* На «Налогах» правка спрятана под кнопку: форма из четырёх полей в каждой строке
   держала колонку в 474 px, и таблица уходила за край уже на 1440, обрезая поле
   НДС. Раскрытая форма — с подписями полей и переносом, как на «Профиле». */
.tax-edit-box > summary { display: inline-flex; list-style: none; cursor: pointer; }
.tax-edit-box > summary::-webkit-details-marker { display: none; }
.tax-edit-box[open] > summary { margin-bottom: var(--mzone-space-2); }
.tax-edit-box .tax-edit { flex-wrap: wrap; align-items: flex-end; }
.tax-edit-field { display: grid; gap: 2px; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); }
.funnel-row { display: grid; grid-template-columns: minmax(0, 150px) minmax(0, 1fr) minmax(0, 120px); align-items: center; gap: var(--mzone-space-3); }
.funnel-row > .progress { height: 22px; border-radius: 6px; }
.funnel-row > .progress > .progress-fill { height: 100%; border-radius: 6px; }
@media (max-width: 420px) {
  .funnel-row { grid-template-columns: 1fr auto; }
  .funnel-row > .progress { grid-column: 1 / -1; order: 3; }
}
.chart-legend { display: flex; align-items: center; gap: 1.125rem; flex-wrap: wrap; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.legend-item { display: inline-flex; align-items: center; gap: 0.375rem; min-width: 0; max-width: 100%; }
.legend-dot { width: 18px; height: 4px; flex: 0 0 18px; border-radius: var(--mzone-radius-pill); }
.legend-dot.navy { background: var(--mzone-navy-950); }
.legend-dot.cyan { background: var(--mzone-cyan-400); }
.legend-dot.teal { background: var(--mzone-teal-600); }
/* Легенда столбчатых/водопадных графиков: метка повторяет форму метки данных. */
.legend-swatch { width: 12px; height: 12px; flex: 0 0 12px; border-radius: 3px; }
.legend-value { margin-left: 0.25rem; color: var(--mzone-text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── Графики (mzone-charts.js рисует ванильный SVG) ─────────────────────────
   Высоту холста считает скрипт по числу строк/точек — карточка растёт вместе
   с графиком, подписи осей никогда не обрезаются внутренним скроллом. */
.chart-area.chart-area-auto { height: auto; min-height: 0; }
.chart-canvas { width: 100%; display: block; }
.chart-footnote { margin-top: var(--mzone-space-3); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.chart-empty { display: grid; min-height: 140px; padding: var(--mzone-space-6); place-items: center; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-md); text-align: center; }

.mz-chart-caption, .mz-chart-label, .mz-chart-value, .mz-chart-value-soft, .mz-chart-axis, .mz-chart-barvalue, .mz-chart-empty { font-family: var(--mzone-font-sans); }
/* Подпись над столбцом (число заказов в час): мельче осей, чтобы 24 штуки не слиплись. */
.mz-chart-barvalue { fill: var(--mzone-text-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.mz-chart-barvalue.is-strong { fill: var(--mzone-text); font-weight: 700; }
.mz-chart-caption { fill: var(--mzone-text-soft); font-size: 11px; font-weight: 550; letter-spacing: 0.04em; text-transform: uppercase; }
.mz-chart-label { fill: var(--mzone-text-muted); font-size: 12px; }
.mz-chart-label.is-strong { fill: var(--mzone-text); font-weight: 600; }
.mz-chart-value { fill: var(--mzone-text); font-size: 12px; font-weight: 550; font-variant-numeric: tabular-nums; }
.mz-chart-value.is-strong { font-weight: 700; }
.mz-chart-value-soft { fill: var(--mzone-text-soft); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.mz-chart-axis { fill: var(--mzone-text-soft); font-size: 11px; font-variant-numeric: tabular-nums; }
.mz-chart-empty { fill: var(--mzone-text-muted); font-size: 13px; }
.mz-chart-grid, .mz-chart-connector, .mz-chart-zero, .mz-chart-cross { stroke: var(--chart-grid); stroke-width: 1; }
.mz-chart-connector { stroke: #cbd5e1; }
.mz-chart-zero { stroke: #cbd5e1; }
.mz-chart-cross { stroke: #94a3b8; }
.mz-chart-dot { stroke: var(--mzone-surface); stroke-width: 2; }
.mz-row-bg { fill: var(--mzone-surface-muted); opacity: 0; }
.mz-bar, .mz-row-bg { transition: opacity var(--mzone-transition-fast); }
.mz-hit { fill: transparent; cursor: pointer; outline: none; }
.mz-row:hover .mz-row-bg, .mz-row:focus-within .mz-row-bg { opacity: 0.8; }
.mz-row:hover .mz-bar, .mz-row:focus-within .mz-bar { opacity: 0.82; }
.mz-hit:focus-visible { fill: rgba(46,214,207,0.1); }

/* Тултип: значение — главный элемент, название серии вторично. */
.mz-tooltip { position: fixed; z-index: 90; min-width: 168px; max-width: 280px; padding: 0.5rem 0.625rem; border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); box-shadow: var(--mzone-shadow-float); color: var(--mzone-text); font-family: var(--mzone-font-sans); font-size: var(--mzone-font-size-sm); opacity: 0; pointer-events: none; transition: opacity var(--mzone-transition-fast); }
.mz-tooltip.is-open { opacity: 1; }
.mz-tooltip-title { margin-bottom: 0.375rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--mzone-border-soft); color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; }
.mz-tooltip-rows { display: grid; gap: 0.25rem; }
.mz-tooltip-row { display: flex; align-items: center; gap: 0.5rem; }
.mz-tooltip-key { width: 12px; height: 3px; flex: 0 0 12px; border-radius: var(--mzone-radius-pill); }
.mz-tooltip-name { min-width: 0; flex: 1 1 auto; overflow: hidden; color: var(--mzone-text-muted); text-overflow: ellipsis; white-space: nowrap; }
.mz-tooltip-value { flex: 0 0 auto; color: var(--mzone-text); font-weight: 600; font-variant-numeric: tabular-nums; }
.mz-tooltip-row.is-strong .mz-tooltip-value { font-size: var(--mzone-font-size-lg); font-weight: 700; }

.table-card { overflow: hidden; }
/* Тень у правого края показывает, что колонки продолжаются: без неё таблица
   выглядит обрезанной, а не прокручиваемой (жалоба на «Своём складе»). */
.table-scroll, .table-wrap { position: relative; width: 100%; overflow-x: auto; scrollbar-width: thin;
  background:
    linear-gradient(to right, var(--mzone-surface) 30%, transparent) left / 34px 100% no-repeat,
    linear-gradient(to left, var(--mzone-surface) 30%, transparent) right / 34px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(6,20,43,.16), transparent) left / 14px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(6,20,43,.16), transparent) right / 14px 100% no-repeat;
  background-attachment: local, local, scroll, scroll; }
/* Страховка для таблиц, которые лежат в карточке без обёртки .table-scroll: на
   укрупнённой шкале широкие таблицы (склад, план поставок, юнит-экономика) стали
   шире экрана и тащили вправо всю страницу. Пусть едет таблица внутри своей
   карточки, а не страница. Обёртки в шаблонах при этом остаются главным способом. */
.card:has(> .table), .card:has(> .table-block > .table) { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; border-spacing: 0; color: var(--mzone-text); font-size: var(--mzone-font-size-md); }
.table thead { background: var(--mzone-surface-soft); }
/* Липкая шапка длинной таблицы: при прокрутке списка названия колонок остаются
   на виду (жалоба владельца 18.08 — «таблица не двигается»). Обёртке нужна высота,
   иначе внутренней прокрутки нет и sticky не за что зацепиться: контейнер сам
   становится вертикально-прокручиваемым, а шапка липнет к его верху. Горизонтальная
   прокрутка (overflow-x у .table-scroll) при этом сохраняется. z-index выше ячеек. */
.table-scroll.sticky-head { max-height: calc(100vh - var(--mzone-topbar-height) - 200px); overflow: auto; }
.table-scroll.sticky-head thead th { position: sticky; top: 0; z-index: 3; background: var(--mzone-surface-soft); }
/* Шапка держит структуру таблицы, поэтому она не бледнее содержимого. Была
   text-muted при цветных названиях товаров в ячейках: взгляд цеплялся за
   повторяющиеся названия, а не за колонки. Цвет самих названий не трогаем —
   фирменный цвет WB у ссылки на карточку владелец просил осознанно (29.07). */
.table th { padding: 0.8125rem 1rem; border-bottom: 1px solid var(--mzone-border-soft); color: var(--mzone-text); font-size: var(--mzone-font-size-xs); font-weight: 650; letter-spacing: 0.01em; text-align: left; white-space: normal; }
/* Заголовок переносится ПО СЛОВАМ, а не держит строку: «После рекламы и
   логистики» в одну строку занимал 222 px над числами в 80, и таблицы «Отчёта
   маркетплейса», «Остатков», «Рекламы» уходили за правый край уже на 1440 —
   итог обрезался посреди числа (обход 24.09.2026). Короткие заголовки при этом
   не переносятся: браузер делит место между колонками и ломает строку только
   там, где его не хватает. */
/* overflow:hidden — при ресайзе колонок содержимое обязано обрезаться многоточием,
   а не залезать в соседнюю ячейку. Полное значение остаётся в title. */
.table td { padding: 0.875rem 1rem; overflow: hidden; border-bottom: 1px solid var(--mzone-border-soft); color: var(--mzone-text); text-overflow: ellipsis; vertical-align: middle; }
.table tbody tr { transition: background-color var(--mzone-transition-fast); }
.table tbody tr:hover { background: var(--mzone-surface-muted); }
.table tbody tr:last-child td { border-bottom: 0; }
.table-numeric { text-align: right !important; }
/* min-width:0 (а не 220px) — иначе при сужении колонки контент вылезал в соседнюю
   ячейку и названия наползали друг на друга (фидбэк владельца 29.07). */
.product-cell { display: flex; min-width: 0; align-items: center; gap: var(--mzone-space-3); }
/* Блок с названием обязан сжиматься: без min-width: 0 многоточие у .product-title
   не срабатывает и длинное название вылезает за карточку каталога на 375. */
.product-cell > * { min-width: 0; }
.product-thumb { width: 42px; height: 42px; flex: 0 0 42px; border: 1px solid var(--mzone-border-soft); border-radius: 0.625rem; background: var(--mzone-surface-muted); object-fit: cover; }
/* Нет фото — значок «картинка» по центру, а не три буквы артикула («ДК0»),
   прижатые к левому верхнему углу: без смысла и выглядели как сбой. Буквы в
   разметке остаются (пять шаблонов), но не рисуются; значок — фоном, поэтому
   его же видно, когда фото WB не загрузилось и mzImgErr убрал картинку. Серый
   #8a94a6 читается на подложке обеих тем. */
.product-thumb { display: grid; place-items: center; overflow: hidden; color: transparent; font-size: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a94a6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='3'/%3E%3Ccircle cx='9' cy='9' r='2'/%3E%3Cpath d='m21 15-4.5-4.5L6 21'/%3E%3C/svg%3E");
  background-position: center; background-repeat: no-repeat; background-size: 46%; }
.product-title { overflow: hidden; color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 550; text-overflow: ellipsis; white-space: nowrap; }
.product-meta { margin-top: 2px; overflow: hidden; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); text-overflow: ellipsis; white-space: nowrap; }
.table-pagination { display: flex; min-height: 58px; padding: 0.75rem 1rem; align-items: center; justify-content: space-between; gap: var(--mzone-space-3); border-top: 1px solid var(--mzone-border-soft); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-md); }
.pagination-pages { display: flex; align-items: center; gap: var(--mzone-space-1); }
.pagination-button { display: inline-grid; width: 34px; height: 34px; place-items: center; border: 1px solid transparent; border-radius: var(--mzone-radius-sm); background: transparent; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-md); font-weight: 550; }
.pagination-button.active, .pagination-button[aria-current="page"] { background: var(--mzone-solid); color: var(--mzone-solid-ink); }

.badge, .status { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; font-weight: 600; line-height: 1; white-space: nowrap; }
.badge { min-height: 24px; padding: 0 0.5rem; border-radius: 0.4375rem; font-size: var(--mzone-font-size-xs); letter-spacing: -0.01em; }
.badge-wb { background: var(--mzone-wb); color: #fff; }
.badge-ozon { background: var(--mzone-ozon); color: #fff; }
/* Яндекс Маркет: фирменный красный, белый текст (контраст проверен как у WB/Ozon). */
.badge-yandex { background: var(--mzone-yandex, #fc3f1d); color: #fff; }
.badge-neutral { background: var(--mzone-surface-muted); color: var(--mzone-text-muted); }
.badge-accent { background: var(--mzone-cyan-100); color: var(--mzone-teal-700); }
.status { min-height: 26px; padding: 0 0.625rem; gap: 0.375rem; border-radius: var(--mzone-radius-pill); font-size: var(--mzone-font-size-sm); }
.status::before { width: 6px; height: 6px; flex: 0 0 6px; border-radius: 50%; background: currentColor; content: ""; }
.status-success { background: var(--mzone-success-bg); color: var(--mzone-success); }
.status-warning { background: var(--mzone-warning-bg); color: var(--mzone-warning); }
.status-danger { background: var(--mzone-danger-bg); color: var(--mzone-danger); }
.status-info { background: var(--mzone-info-bg); color: var(--mzone-info); }
.status-neutral { background: var(--mzone-surface-muted); color: var(--mzone-text-muted); }

.freshness { display: inline-flex; align-items: center; gap: 0.375rem; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); white-space: nowrap; }
.freshness-dot { width: 7px; height: 7px; flex: 0 0 7px; border-radius: 50%; background: var(--mzone-success); box-shadow: 0 0 0 3px var(--mzone-success-bg); }
.freshness.is-stale .freshness-dot { background: var(--mzone-warning); box-shadow: 0 0 0 3px var(--mzone-warning-bg); }
.freshness.is-missing .freshness-dot { background: var(--mzone-text-soft); box-shadow: 0 0 0 3px var(--mzone-surface-muted); }
.alert { display: flex; padding: var(--mzone-space-4); align-items: flex-start; gap: var(--mzone-space-3); border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); color: var(--mzone-text); }
.alert-icon { width: 20px; height: 20px; flex: 0 0 20px; }
.alert-success { border-color: #bbf7d0; background: var(--mzone-success-bg); }
.alert-warning { border-color: #fde68a; background: var(--mzone-warning-bg); }
.alert-danger { border-color: #fecaca; background: var(--mzone-danger-bg); }
.alert-info { border-color: #bfdbfe; background: var(--mzone-info-bg); }
/* Свёрнутые пояснения о том, как читать цифры. Суть в одну строку, разбор по клику:
   раньше три плашки подряд отодвигали KPI за первый экран. */
.notes-fold { border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md); background: var(--mzone-surface); }
.notes-fold > summary { display: flex; padding: 0.75rem var(--mzone-space-4); align-items: center; gap: var(--mzone-space-3); border-radius: inherit; color: var(--mzone-text); cursor: pointer; list-style: none; }
.notes-fold > summary::-webkit-details-marker { display: none; }
.notes-fold > summary:hover { background: var(--mzone-surface-soft); }
.notes-fold > summary:focus-visible { outline: 2px solid var(--mzone-teal-600); outline-offset: 2px; }
.notes-head { min-width: 0; overflow: hidden; font-size: var(--mzone-font-size-md); text-overflow: ellipsis; white-space: nowrap; }
.notes-toggle { margin-left: auto; color: var(--mzone-teal-700); font-size: var(--mzone-font-size-sm); font-weight: 600; white-space: nowrap; }
.notes-fold[open] > summary { border-bottom: 1px solid var(--mzone-border-soft); border-bottom-right-radius: 0; border-bottom-left-radius: 0; }
.notes-fold[open] .notes-toggle::after { content: " ▲"; }
.notes-fold:not([open]) .notes-toggle::after { content: " ▼"; }
.notes-body { padding: var(--mzone-space-4) var(--mzone-space-4) 0; }
.notes-body > .alert:last-child { margin-bottom: var(--mzone-space-4) !important; }
.progress { width: 100%; height: 7px; overflow: hidden; border-radius: var(--mzone-radius-pill); background: rgba(148,163,184,0.24); }
.progress-fill { width: var(--progress, 0%); height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--mzone-teal-600), var(--mzone-cyan-400)); transition: width 320ms ease; }
.donut { --value: 0; --donut-size: 132px; --donut-track: var(--mzone-surface-muted); --donut-color: var(--mzone-cyan-400); position: relative; display: grid; width: var(--donut-size); height: var(--donut-size); place-items: center; border-radius: 50%; background: conic-gradient(var(--donut-color) calc(var(--value)*1%), var(--donut-track) 0); }
.donut::before { position: absolute; inset: 14px; border-radius: 50%; background: var(--mzone-surface); content: ""; }
.donut-content { position: relative; z-index: 1; text-align: center; }
.data-source-list, .notification-list { display: grid; gap: var(--mzone-space-2); }
.data-source-item, .notification-item { display: flex; padding: var(--mzone-space-3); align-items: center; gap: var(--mzone-space-3); border: 1px solid var(--mzone-border-soft); border-radius: var(--mzone-radius-md); background: var(--mzone-surface-soft); }
.notification-item { align-items: flex-start; }
.notification-copy { min-width: 0; flex: 1 1 auto; }

.tabs { display: flex; gap: var(--mzone-space-5); overflow-x: auto; border-bottom: 1px solid var(--mzone-border); scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { position: relative; display: inline-flex; min-height: 44px; align-items: center; border: 0; background: transparent; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-base); font-weight: 550; white-space: nowrap; }
.tab::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: var(--mzone-radius-pill); background: transparent; content: ""; }
/* Цвет активной вкладки — токеном темы, а не литералом палитры: navy-950 на тёмном
   фоне сливался с ним, и выбранная вкладка становилась невидимой. */
.tab.active, .tab[aria-selected="true"] { color: var(--mzone-text); font-weight: 600; }
.tab.active::after, .tab[aria-selected="true"]::after { background: var(--mzone-cyan-400); }

/* ── Настройка столбцов таблицы (mzone-columns.js) ───────────────────────
   Кому-то нужен ДРР, кому-то бюджет: состав и порядок столбцов выбирает
   пользователь, выбор хранится у него в браузере. */
.col-picker { position: relative; }
.col-picker > summary { list-style: none; cursor: pointer; }
.col-picker > summary::-webkit-details-marker { display: none; }
.col-picker-menu { position: absolute; top: calc(100% + var(--mzone-space-2)); right: 0; z-index: 30;
  min-width: 15rem; max-height: 24rem; padding: var(--mzone-space-3); overflow-y: auto;
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md);
  background: var(--mzone-surface); box-shadow: var(--mzone-shadow-float); }
.col-picker-list { display: flex; flex-direction: column; gap: 1px; }
.col-picker-item { display: flex; padding: var(--mzone-space-2); align-items: center; gap: var(--mzone-space-2);
  border-radius: var(--mzone-radius-sm); color: var(--mzone-text); font-size: var(--mzone-font-size-md);
  cursor: grab; user-select: none; }
.col-picker-item:hover { background: var(--mzone-surface-soft); }
.col-picker-item.is-dragging { opacity: 0.5; }
.col-picker-grip { color: var(--mzone-text-soft); font-size: var(--mzone-font-size-sm); letter-spacing: -0.15em; }
/* Скрытые столбцы: у ячеек таблицы display из table-cell перебивает атрибут hidden */
.table [hidden] { display: none; }

/* ── Фото товара в списке кампаний: оно же кнопка раскрытия ─────────────
   В кабинете WB по фото понимаешь, что рекламируется, не читая названий;
   у нас по клику раскрывается ещё и статистика по каждому артикулу. */
.camp-thumb { position: relative; display: inline-flex; padding: 0; border: 0; background: transparent;
  border-radius: 0.625rem; cursor: pointer; transition: box-shadow var(--mzone-transition-fast); }
.camp-thumb:hover { box-shadow: 0 0 0 2px var(--mzone-solid); }
.camp-thumb.is-open { box-shadow: 0 0 0 2px var(--mzone-solid); }
.camp-thumb-more { position: absolute; top: -5px; right: -7px; min-width: 20px; padding: 0 4px;
  border-radius: var(--mzone-radius-pill); background: var(--mzone-navy-950); color: #fff;
  font-size: var(--mzone-font-size-xs); font-weight: 600; line-height: 17px; text-align: center; }
:root[data-theme="dark"] .camp-thumb-more { background: var(--mzone-surface-muted); color: var(--mzone-text); }

/* ── Тип рекламной кампании (как в кабинете WB) ─────────────────────────
   Модель оплаты крупно, вид ставки чипом, площадки показа — иконками. */
.camp-type { display: flex; flex-direction: column; gap: var(--mzone-space-1); }
.camp-type-pay { color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; }
.camp-type-row { display: inline-flex; align-items: center; gap: var(--mzone-space-2); }
.camp-type-place { display: inline-flex; width: 16px; height: 16px; color: var(--mzone-text-soft); }
.camp-type-place svg { width: 100%; height: 100%; }

/* ── Формы ─────────────────────────────────────────────────────────────
   Поля оформляются либо классом (.input/.select/.textarea), либо просто
   попаданием input/select/textarea внутрь .field / .form-grid / .card.
   Топбар-поиск (.search) не задет — у него свой класс и он вне .card. */
.field { display: flex; flex-direction: column; gap: 0.375rem; margin-bottom: var(--mzone-space-4); }
.field > label, .form-label { color: var(--mzone-text); font-size: var(--mzone-font-size-sm); font-weight: 550; }
.field-hint { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.input, .select, .textarea,
.field input:not([type=checkbox]):not([type=radio]), .field select, .field textarea,
.form-grid input:not([type=checkbox]):not([type=radio]), .form-grid select, .form-grid textarea {
  width: 100%; padding: 0.5rem 1rem; border: 1px solid var(--mzone-border); border-radius: 0.5rem;
  background: var(--mzone-surface); color: var(--mzone-text); font-size: var(--mzone-font-size-base);
  line-height: var(--mzone-lh-lg); box-shadow: var(--mzone-shadow-xs);
  transition: border-color var(--mzone-transition-fast), box-shadow var(--mzone-transition-fast); }
.textarea, .field textarea, .form-grid textarea { min-height: 88px; resize: vertical; }
.input.mono, .field textarea.mono, .textarea.mono { font-family: var(--mzone-font-mono); }
.input:focus, .select:focus, .textarea:focus,
.field input:focus, .field select:focus, .field textarea:focus,
.form-grid input:focus, .form-grid select:focus, .form-grid textarea:focus {
  border-color: var(--mzone-teal-500); box-shadow: none;
  outline: 2px solid var(--mzone-teal-600); outline-offset: -1px; }
.input::placeholder, .textarea::placeholder, .field textarea::placeholder, .field input::placeholder { color: var(--mzone-text-soft); }
/* Ввод себестоимости в узкой колонке таблицы: поля НЕ сжимаются (flex:0 0),
   иначе набранные цифры уезжали из видимой части и текст казался невидимым
   (жалоба владельца 18.08). Цвет/фон задаём явно — на случай любого наследования. */
.cost-form { display: flex; align-items: center; gap: var(--mzone-space-2); flex-wrap: wrap; }
.cost-form .cost-value { flex: 0 0 96px; width: 96px; color: var(--mzone-text); background: var(--mzone-surface); }
.cost-form .cost-date { flex: 0 0 164px; width: 164px; }  /* год целиком в любой локали браузера */
.cost-form .cost-mark { flex: 0 0 auto; min-width: 12px; color: var(--mzone-success); }
.form-actions { display: flex; gap: var(--mzone-space-3); align-items: center; margin-top: var(--mzone-space-2); }
.btn-danger { border-color: var(--mzone-danger); background: var(--mzone-danger-bg); color: var(--mzone-danger); }
.btn-danger:not(:disabled):hover { background: var(--mzone-danger); color: #fff; }

/* ABC-класс (Парето) — A ценное, B среднее, C хвост. Общий для товаров и склада. */
.abc { display: inline-flex; min-width: 26px; justify-content: center; padding: 2px 8px; border-radius: var(--mzone-radius-pill); font-size: var(--mzone-font-size-xs); font-weight: 600; }
.abc.a { background: var(--mzone-success-bg); color: var(--mzone-success); }
.abc.b { background: var(--mzone-warning-bg); color: var(--mzone-warning); }
.abc.c { background: var(--mzone-danger-bg); color: var(--mzone-danger); }

.flex { display: flex !important; } .inline-flex { display: inline-flex !important; } .grid { display: grid !important; } .hidden { display: none !important; }
.items-center { align-items: center !important; } .items-start { align-items: flex-start !important; } .justify-between { justify-content: space-between !important; } .justify-end { justify-content: flex-end !important; }
.flex-wrap { flex-wrap: wrap !important; } .grow { flex-grow: 1 !important; } .min-w-0 { min-width: 0 !important; } .w-full { width: 100% !important; }
.text-right { text-align: right !important; } .text-center { text-align: center !important; } .nowrap { white-space: nowrap !important; }
.gap-1 { gap: var(--mzone-space-1) !important; } .gap-2 { gap: var(--mzone-space-2) !important; } .gap-3 { gap: var(--mzone-space-3) !important; } .gap-4 { gap: var(--mzone-space-4) !important; }
.mt-1 { margin-top: var(--mzone-space-1) !important; } .mt-2 { margin-top: var(--mzone-space-2) !important; } .mt-3 { margin-top: var(--mzone-space-3) !important; } .mt-4 { margin-top: var(--mzone-space-4) !important; }
.mb-0 { margin-bottom: 0 !important; } .mb-2 { margin-bottom: var(--mzone-space-2) !important; } .mb-3 { margin-bottom: var(--mzone-space-3) !important; } .mb-4 { margin-bottom: var(--mzone-space-4) !important; }

/* Ячейка грида по умолчанию не уже своего содержимого (min-width:auto). Из-за этого
   длинное число в `.kpi-value` (там `nowrap`) раздвигало сетку, страница ехала вправо,
   и на телефоне цифры уходили за экран. Явный min-width:0 разрешает ячейке сжиматься. */
.kpi-grid > *, .cards-grid > *, .bottom-grid > *, .dashboard-grid > *, .form-grid > * { min-width: 0; }
.kpi-card, .card { min-width: 0; }
/* Кнопка меню нужна только там, где сайдбар спрятан. Показ включает медиазапрос ниже. */
.mobile-menu-button { display: none; }

@media (max-width: 1100px) { .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); } }
/* Меню уже занимает место, а в шапке показывалось всё сразу: период, переключатель
   площадок, разделитель, тариф и имя пользователя. Правый край не помещался, и страница
   ехала вбок на КАЖДОЙ витрине (аудит 11.08: на 1024 px выезд 37–184 px, на 1280 до 53).
   Период и площадка продублированы фильтрами самих страниц, поэтому в шапке прячем
   первыми их, а не то, чего больше нигде нет. */
@media (max-width: 1439px) {
  .topbar .date-chip, .topbar .segmented, .topbar-divider, .user-copy { display: none; }
}
@media (max-width: 1279px) {
  /* 232px не хватало самому длинному пункту («Данные по товарам») уже на прежней
     шкале, а после укрупнения обрезались два пункта. Узкому экрану отдаём меню
     кеглем поменьше — это служебная навигация, крупный кегль ей не нужен. */
  :root { --mzone-sidebar-width: 248px; }
  .nav-item { font-size: var(--mzone-font-size-sm); }
  .content, .topbar { padding-right: var(--mzone-space-6); padding-left: var(--mzone-space-6); }
  /* Две карточки рядом на 1024–1279 получали около 300 и 410 px, и таблицам
     внутри («Прибыль по кабинетам», «Статистика остатков») не хватало места:
     правые колонки уходили за край. Здесь они идут одна под другой. */
  .dashboard-grid { grid-template-columns: minmax(0, 1fr); }
  .bottom-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .bottom-grid > :first-child { grid-column: 1 / -1; }
  .cards-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 1023px) {
  .app { display: block; }
  .sidebar { position: fixed; top: 0; bottom: 0; left: 0; z-index: 50; width: min(280px, calc(100vw - 48px)); height: 100dvh; transform: translateX(-105%); }
  .sidebar.is-open { transform: translateX(0); }
  .mobile-menu-button { display: inline-flex; }
  .dashboard-grid { grid-template-columns: 1fr; }
}
/* Safari принудительно приближает страницу, когда встаёшь в поле со шрифтом меньше
   16px, и обратно уже не отдаляет. Порог был 767px — на iPad в портрете (768) поля
   оставались мельче и зум срабатывал. Держим 16 на всех тач-ширинах.
   Селектор повторяет базовый один в один: у `.field input:not(...)` специфичность
   выше, чем у голого `.input`, и короткое правило проигрывало. */
@media (max-width: 1023px) {
  .input, .select, .textarea, .search,
  .field input:not([type=checkbox]):not([type=radio]), .field select, .field textarea,
  .form-grid input:not([type=checkbox]):not([type=radio]), .form-grid select, .form-grid textarea,
  input, select, textarea { font-size: 16px; }
}

@media (max-width: 767px) {
  :root { --mzone-topbar-height: 64px; }
  h1 { font-size: 1.5rem; }
  /* Палец не попадает по кнопке высотой 26–30px. Ниже 44 не опускаемся.
     `.copy-chip` и кнопки темы отдельно: они самые мелкие на витрине. */
  .segmented-item, .btn-sm, .icon-btn, .pagination-button,
  .copy-chip, .theme-switch button { min-height: 44px; }
  .segmented-item, .btn-sm { padding-right: 0.875rem; padding-left: 0.875rem; }
  .icon-btn, .pagination-button, .theme-switch button { min-width: 44px; }
  .copy-chip { min-width: 44px; padding: 0 8px; }
  .content { padding: var(--mzone-space-4); }
  .topbar { padding: 0 var(--mzone-space-4); gap: var(--mzone-space-3); }
  .topbar-end .topbar-divider, .user-copy, .topbar .date-chip, .topbar .segmented { display: none; }
  .search-wrap { width: 100%; }
  .topbar .cab-chip { display: none; }   /* на телефоне магазин виден чипами страницы */
  /* На телефоне липкая панель не должна съедать пол-экрана: всё в ОДНУ строку
     с горизонтальной прокруткой (перенос на 4 ряда закрывал бы контент). */
  .toolbar-sticky { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; padding: var(--mzone-space-2) var(--mzone-space-4); scrollbar-width: none; }
  .toolbar-sticky::-webkit-scrollbar { display: none; }
  .toolbar-sticky > * { flex: 0 0 auto; }
  .toolbar-sticky .segmented { flex-wrap: nowrap; }
  /* Кнопки и поля под палец: 40 px мало, правило 13 требует 44. */
  .btn, .select, .input { min-height: 44px; }
  .page-header { align-items: stretch; flex-direction: column; gap: var(--mzone-space-4); }
  /* Телефон: главные цифры первым экраном. Страница выкладывается колонкой, и блок
     с пометкой data-mobile-first поднимается сразу под заголовок — остальное идёт
     в исходном порядке. Разметку страниц это не меняет: только порядок и только тут. */
  .content { display: flex; flex-direction: column; }
  .content > * { order: 2; }
  /* НАВИГАЦИЯ стоит первой и на телефоне: любые вкладки-разделы (nav.tabs),
     а не только складские, — уезжать под заголовок им нельзя (жалоба владельца
     про прыжки разделов). Generic-правило, чтобы следующая секционная навигация
     не наступила на те же грабли. */
  .content > .mp-strip, .content > .breadcrumbs, .content > .page-header, .content > [data-mobile-first] { order: 1; }
  .content > nav.tabs { order: 0; }
  .kpi-grid, .cards-grid, .bottom-grid, .form-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .bottom-grid > :first-child { grid-column: 1 / -1; }
  /* 720 px — только широким таблицам. «Статья | Сумма» растягивалась до 720, и
     на экране телефона оставались одни названия статей без сумм (обход 24.09). */
  .table:has(> thead > tr > th:nth-child(6)) { min-width: 720px; }
}
@media (max-width: 479px) {
  .kpi-grid, .cards-grid, .bottom-grid, .form-grid { grid-template-columns: 1fr; }
  .bottom-grid > :first-child, .col-span-2 { grid-column: auto; }
  /* Крупная сумма вроде «3 726 666 ₽» в одну колонку помещается только так. */
  .kpi-value { font-size: 1.25rem; }
}
/* Страховка от горизонтальной прокрутки всей страницы: широкое содержимое обязано
   прокручиваться внутри своего блока (.table-scroll), а не тащить за собой экран. */
/* clip, а не hidden: hidden превращает body в прокручиваемый контейнер, и
   position: sticky перестаёт держаться — шапка с кнопкой меню и панель фильтров
   уезжали вместе со страницей на всём, что уже 1024 (обход 24.09.2026). hidden
   оставлен первой строкой для браузеров, которые clip не знают. */
@media (max-width: 1023px) { html, body { overflow-x: hidden; overflow-x: clip; } }
/* Сворачивание фиксированных сеток: ниже правил .kpi-grid, иначе они перебивают. */
@media (max-width: 1279px) {
  .kpi-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi-5 > :first-child { grid-column: 1 / -1; }
}
@media (max-width: 1100px) { .kpi-3, .kpi-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 479px) {
  .kpi-3, .kpi-4, .kpi-5 { grid-template-columns: 1fr; }
  .kpi-5 > :first-child { grid-column: auto; }
}

@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } }

/* ─────────────────────────────────────────────────────────────────────────────
   Товары: кликабельные названия и артикулы (фидбэк #5b) + интерактивные таблицы.
   Поведение — /static/mzone-ui.js, разметка — макросы templates/_ui.html.
   ───────────────────────────────────────────────────────────────────────────── */

/* Ссылка на карточку Wildberries — фирменным цветом WB, без пунктира: владелец просил,
   чтобы сразу читалось «это ссылка на товар» (фидбэк 29.07). Иконку показываем на hover. */
.wb-link { color: var(--mzone-wb-brand); text-decoration: none; transition: var(--mzone-transition-fast); }
.wb-link:hover { color: var(--mzone-wb-brand-dark); text-decoration: underline; }
.wb-link-icon { display: inline-block; width: 12px; height: 12px; margin-left: 4px; opacity: 0; vertical-align: -1px; transition: var(--mzone-transition-fast); }
.wb-link:hover .wb-link-icon { opacity: .85; }

.copy-chip { position: relative; padding: 1px 6px; border: 1px solid transparent; border-radius: var(--mzone-radius-sm);
  background: transparent; color: inherit; font: inherit; font-variant-numeric: tabular-nums; line-height: inherit;
  cursor: pointer; transition: var(--mzone-transition-fast); }
.copy-chip:hover { border-color: var(--mzone-border); background: var(--mzone-surface-muted); }
.copy-chip.is-copied { border-color: var(--mzone-success); background: var(--mzone-success-bg); color: var(--mzone-success); }
.copy-flash { position: absolute; bottom: calc(100% + 4px); left: 50%; z-index: 40; transform: translateX(-50%);
  padding: 2px 8px; border-radius: var(--mzone-radius-sm); background: var(--mzone-navy-900); color: var(--mzone-text-inverse);
  font-size: var(--mzone-font-size-xs); white-space: nowrap; pointer-events: none; }

.inline-form { display: inline-block; }
/* Форма-фильтр внутри панели (.toolbar): контролы стоят В ОДИН РЯД и каждый
   занимает столько, сколько нужно содержимому. Без этого правила `.input`
   (width: 100%) внутри inline-block формы вставал в столбик: на журнале склада
   шесть фильтров шли друг под другом, на документах даты уезжали под селект
   статуса, а панель меняла форму при каждом переключении вкладки — жалоба
   владельца 12.09.2026 «кнопки прыгают по экрану». */
.toolbar form.inline-form { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: var(--mzone-space-2); }
.toolbar form.inline-form > .input { width: auto; }
.toolbar form.inline-form > input[type="search"].input { width: 16rem; }
/* Селект, чей текст меняется от вкладки к вкладке («Все виды» / «Приёмка» /
   «Все отгрузки»): ширина фиксирована, иначе соседи сдвигаются при переходе. */
.toolbar form.inline-form > .input.input-fixed { width: 12.5rem; }

/* Интерактивные таблицы: сортировка, ресайз, перестановка колонок */
.mz-table th { position: relative; white-space: normal; cursor: pointer; user-select: none; }
.mz-table th .mz-sort { margin-left: 4px; font-size: 10px; opacity: .55; }
.mz-table th .mz-resize { position: absolute; top: 0; right: -3px; z-index: 5; width: 7px; height: 100%; cursor: col-resize; }
.mz-table th .mz-resize:hover, .mz-table th.mz-resizing .mz-resize { background: var(--mzone-border); }
.mz-table th.mz-drop-left { box-shadow: inset 2px 0 0 var(--mzone-teal-500); }
.mz-table th.mz-drop-right { box-shadow: inset -2px 0 0 var(--mzone-teal-500); }
.mz-table th.mz-dragging { opacity: .4; }

/* ── Календарь произвольного периода (mzone-ui.js: initRangeCal) ─────────────
   Один поповер на диапазон: клик «от», клик «до» — и страница сразу уходит на
   period=custom. Заменил пару голых date-input с кнопкой «Показать». */
.mz-cal { position: relative; }
.mz-cal-pop { position: absolute; top: calc(100% + 6px); left: 0; z-index: 60; width: 296px;
  padding: var(--mzone-space-3); border: 1px solid var(--mzone-border);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface);
  box-shadow: var(--mzone-shadow-float); }
.mz-cal-head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--mzone-space-2); margin-bottom: var(--mzone-space-2); }
.mz-cal-title { color: var(--mzone-text); font-size: var(--mzone-font-size-md); font-weight: 600; }
.mz-cal-nav { display: inline-grid; width: 30px; height: 30px; place-items: center;
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-sm);
  background: var(--mzone-surface); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-lg); line-height: 1; }
.mz-cal-nav:hover:not(:disabled) { background: var(--mzone-surface-soft); color: var(--mzone-text); }
.mz-cal-nav:disabled { opacity: 0.35; cursor: default; }
.mz-cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.mz-cal-dow { padding: 4px 0; color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-xs); font-weight: 600; text-align: center; }
.mz-cal-day { min-height: 34px; padding: 0; border: 0; border-radius: var(--mzone-radius-sm);
  background: transparent; color: var(--mzone-text); font-size: var(--mzone-font-size-md);
  font-variant-numeric: tabular-nums; text-align: center; }
.mz-cal-day:hover:not(:disabled) { background: var(--mzone-surface-muted); }
.mz-cal-day:disabled { color: var(--mzone-text-soft); opacity: 0.45; cursor: default; }
/* Модификаторы выбора — ПОСЛЕ базовых правил (правило 8 стайлгайда). */
.mz-cal-day.is-in { border-radius: 0; background: var(--mzone-cyan-100); color: var(--mzone-teal-700); }
.mz-cal-day.is-edge { background: var(--mzone-solid); color: var(--mzone-solid-ink); font-weight: 600; }
.mz-cal-hint { margin-top: var(--mzone-space-2); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-xs); }
/* Тёмная тема: светлая заливка диапазона слепит — берём прозрачную бирюзу. */
:root[data-theme="dark"] .mz-cal-day.is-in { background: rgba(46,214,207,0.18); color: var(--mzone-text); }
@media (max-width: 767px) {
  /* toolbar-sticky на телефоне прокручивается по X и обрезал бы absolute-поповер */
  .mz-cal-pop { position: fixed; top: 18%; right: 12px; left: 12px; width: auto; }
  .mz-cal-day { min-height: 44px; }
  .mz-cal-nav { width: 44px; height: 44px; }
}

/* Плотные строки товарных таблиц (Сводка: «Заканчиваются товары», «Топ товаров»;
   «Данные по товарам»). Убирают пустоту под названием (1г, 1ж, 3а); иконка
   внешней ссылки у названия не рисуется — и так понятно, что кликабельно. */
.table-dense td { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.table-dense .product-meta { margin-top: 0; }
.table-dense .copy-chip { padding-top: 0; padding-bottom: 0; }
.table-dense .wb-link-icon { display: none; }
/* Пилюля статуса («нет себестоимости», «3 дн») не должна распирать строку. */
.table-dense .status { min-height: 20px; font-size: var(--mzone-font-size-xs); }

.cols-menu { position: relative; }
.cols-pop { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; display: none; min-width: 220px;
  max-height: 60vh; overflow-y: auto; padding: 10px 12px; border: 1px solid var(--mzone-border);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface); box-shadow: var(--mzone-shadow-float); }
.cols-menu.open .cols-pop { display: grid; gap: 6px; }
.cols-pop label { display: flex; gap: 8px; align-items: center; font-size: var(--mzone-font-size-sm); white-space: nowrap; cursor: pointer; }

/* Галерея фото в редакторе карточки */
.photo-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--mzone-space-3); }
.photo-gallery figure { margin: 0; }
.photo-gallery img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border: 1px solid var(--mzone-border);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface-muted); }
.photo-gallery figcaption { margin-top: 4px; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); text-align: center; }

.product-thumb img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
a.product-title.wb-link { display: inline-block; max-width: 100%; vertical-align: bottom; }
/* То же для названия без ссылки (карточки каталога): у inline-элемента overflow и
   многоточие не работают, и длинное название вылезало за карточку на 375. */
span.product-title { display: inline-block; max-width: 100%; vertical-align: bottom; }
/* В ТАБЛИЦЕ название не держит одну строку: колонка «Товар» росла по самому
   длинному названию до 390 px и выталкивала «Статус», «Сумму», «Прибыль» за
   правый край. Теперь колонка не шире 300 px, а название — до двух строк. */
.table .product-cell { max-width: 300px; }
.table .product-cell .product-title { white-space: normal; overflow-wrap: anywhere; }
/* Строка артикулов под названием тоже переносится: в колонке 300 px чип артикула
   МП вылезал на соседнюю ячейку «Размер» (снимок прода 24.09.2026). */
.table .product-cell .product-meta { white-space: normal; }
/* Две строки с многоточием — только там, где у ссылки нет тап-зоны: на тач-экранах
   ей добавлен паддинг в 0.8rem (правило ниже), overflow режет по краю паддинга, и
   третья строка выглядывала из-под второй. На телефоне название переносится целиком. */
@media (min-width: 1024px) {
  .table .product-cell .product-title { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ТЕМЫ. Светлая — по умолчанию (:root выше). Тёмные включаются атрибутом
   data-theme на <html>; выбор хранится в localStorage и ставится ДО отрисовки
   (см. app_base.html), иначе на каждой загрузке мигает белым.

   Переопределяем только ТОКЕНЫ — компоненты не трогаем. Литералы графиков
   (--profit/--loss/--cab*) тоже здесь: mzone-charts.js читает их и подставляет
   в SVG-атрибуты, где вложенный var() не резолвится.

   Палитра серий тёмных тем проверена на дальтонизм (protan/deutan/tritan):
   бирюза → янтарь → фиолет, худшая пара ΔE2000 = 17.8.
   ═══════════════════════════════════════════════════════════════════════════ */

:root[data-theme="dark"] {
  color-scheme: dark;
  --mzone-text-inverse: #06142b;
  --mzone-shadow-card: 0 1px 2px rgba(0,0,0,0.30), 0 14px 34px rgba(0,0,0,0.28);
  --mzone-shadow-card-hover: 0 2px 6px rgba(0,0,0,0.36), 0 18px 42px rgba(0,0,0,0.40);
  --mzone-shadow-float: 0 18px 42px rgba(0,0,0,0.55);
  --mzone-solid-ink: #04211f;
  /* Тинты статусов: светлые заливки на тёмном фоне слепят, берём прозрачность */
  --mzone-success-bg: rgba(52,211,153,0.14);
  --mzone-danger-bg: rgba(248,113,113,0.14);
  --mzone-warning-bg: rgba(245,185,59,0.14);
  --mzone-info-bg: rgba(92,155,255,0.14);
  --mzone-purple-bg: rgba(155,135,255,0.16);
  --mzone-success: #34d399;
  --mzone-danger: #f87171;
  --mzone-warning: #f5b93b;
  --mzone-info: #5c9bff;
  --mzone-purple: #9b87ff;
  --mzone-wb: #e85bd0;
  --mzone-ozon: #5c9bff;
  --mzone-wb-brand: #f472d0;
  --mzone-wb-brand-dark: #e85bd0;
  /* графики */
  --profit: #34d399;
  --loss: #f87171;
  --gold: #f5b93b;
  --chart-base: #2ed6cf;
  --chart-expense: #f87171;
  --chart-income: #34d399;
}

/* ── Тёмно-синяя (фирменная) ── */
:root[data-theme="dark"] {
  --mzone-bg: #070e1c;
  --mzone-surface: #101d33;
  --mzone-surface-soft: #16263f;
  --mzone-surface-muted: #1c3151;
  --mzone-text: #e8f0fa;
  --mzone-text-muted: #93a7c4;
  --mzone-text-soft: #8397b6;   /* тёмная тема: было 3.77, стало 5.67 */
  --mzone-border: rgba(148,180,224,0.14);
  --mzone-border-soft: rgba(148,180,224,0.10);
  --mzone-topbar-bg: rgba(16,29,51,0.82);
  --mzone-solid: #2ed6cf;
  --mzone-solid-hover: #16b8b5;
  --mzone-overlay: rgba(3,8,18,0.66);
  --chart-grid: rgba(148,180,224,0.12);
  --chart-empty-bar: rgba(148,180,224,0.16);
  --heat-0: #16263f; --heat-1: #1c4a52; --heat-2: #1f7c78;
  --heat-3: #2ab5b1; --heat-4: #7ceae6;
  --heat-ink-lo: #cfe6ea; --heat-ink-hi: #06232b;
  --cab1: #2ed6cf; --cab2: #9b87ff; --cab3: #f5b93b; --cab4: #5c9bff; --cab5: #fb7185;
}

/* Сайдбар изначально тёмно-синий: в тёмных темах подгоняем под фон страницы,
   иначе он выглядит вставкой из другого интерфейса. */
:root[data-theme="dark"] .sidebar {
  background: radial-gradient(circle at 0 0, rgba(46,214,207,0.13), transparent 34%),
              linear-gradient(180deg, #0a1426 0%, #0d1a2e 100%);
  border-right: 1px solid var(--mzone-border-soft);
}

/* Атмосферное свечение фона — то, что делает тёмную тему «дорогой», а не просто
   инвертированной. Только в тёмных темах и только за контентом. */
:root[data-theme="dark"] body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
}
:root[data-theme="dark"] body::before {
  background: radial-gradient(60rem 34rem at 22% -10%, rgba(14,125,122,0.30), transparent 62%),
              radial-gradient(52rem 30rem at 92% -14%, rgba(59,42,122,0.34), transparent 60%);
}
:root[data-theme="dark"] .app { position: relative; z-index: 1; }

/* Тёмно-синие линии графиков (литерал палитры) на тёмном фоне не видны */
:root[data-theme="dark"] .chart-line-primary { stroke: var(--mzone-text); }
:root[data-theme="dark"] .legend-dot.navy { background: var(--mzone-text); }

/* Фото товаров на тёмном фоне не должны «светиться» белой подложкой */
:root[data-theme="dark"] .product-thumb img,
:root[data-theme="dark"] .thumb img { background: var(--mzone-surface-muted); }

/* ── Переключатель тем в шапке ── */
.theme-switch { display: inline-flex; padding: 3px; align-items: center; gap: 2px;
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-pill);
  background: var(--mzone-surface); }
.theme-switch button { display: grid; width: 28px; height: 26px; place-items: center;
  border: 0; border-radius: var(--mzone-radius-pill); background: transparent;
  color: var(--mzone-text-soft); transition: background-color var(--mzone-transition-fast), color var(--mzone-transition-fast); }
.theme-switch button:hover { color: var(--mzone-text); }
.theme-switch button[aria-pressed="true"] { background: var(--mzone-surface-muted); color: var(--mzone-solid); }
.theme-switch svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

/* ═══════════════════════════════════════════════════════════════════════════
   БЛОКИ ДАШБОРДА (перенос из design-demo). Всё на токенах, поэтому одинаково
   живут в светлой, тёмно-синей и фиолетовой темах.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Кольцевые шкалы «здоровье кабинета» */
.gauge-row { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--mzone-space-3); }
.gauge { text-align: center; }
.gauge-wrap { position: relative; width: 100%; max-width: 108px; margin: 0 auto; }
.gauge-pct { position: absolute; inset: 0; display: grid; place-content: center; }
.gauge-pct b { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; }
.gauge-cap { margin-top: var(--mzone-space-2); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-sm); font-weight: 550; }
.gauge-hint { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); margin-top: 1px; }
.gauge-hint.is-bad { color: var(--mzone-danger); }
@media (max-width: 560px) { .gauge-row { grid-template-columns: 1fr; gap: var(--mzone-space-4); } }

/* Кольцо расходов + прямые подписи */
.donut-wrap { display: flex; gap: var(--mzone-space-5); align-items: center; flex-wrap: wrap; }
/* 240 px, а не 190: при 190 легенда вставала рядом с кольцом в узкой колонке,
   и названия статей резались многоточием («Себесто…», «Внутренни…»). Теперь в
   узкой колонке легенда уходит под кольцо и получает всю ширину. */
.donut-legend { flex: 1 1 240px; min-width: 0; }
.donut-row { display: flex; align-items: center; gap: var(--mzone-space-2);
  padding: 5px 0; font-size: var(--mzone-font-size-md); }
.donut-row i { width: 10px; height: 10px; border-radius: 3px; flex: none; background: var(--mzone-teal-600); }
.donut-row .nm { flex: 1; min-width: 0; color: var(--mzone-text-muted);
  overflow-wrap: break-word; }
.donut-row .vl { font-weight: 600; font-variant-numeric: tabular-nums; }
.donut-row .pc { min-width: 44px; text-align: right; color: var(--mzone-text-muted);
  font-variant-numeric: tabular-nums; }

/* Тепловая карта «часы × дни недели» */
.heat { display: grid; grid-template-columns: 34px repeat(24, minmax(0,1fr));
  gap: 3px; align-items: center; }
/* Подписи часов 9.5px: на самом бледном токене их не прочесть. */
.heat-hh { color: var(--mzone-text-muted); font-size: 9.5px; text-align: center; }
.heat-rl { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
/* Клетка несёт и цвет, и число: цвет даёт картину целиком, число — точный ответ,
   не заставляя наводить мышь. Пять ступеней вместо плавной заливки — близкие
   оттенки глаз не различает, а уровни видно сразу.
   Высота фиксированная, а не aspect-ratio 1: на широком экране квадраты
   раздувались до 50 px при цифре 10 px — плитки огромные, числа не прочесть (1д). */
.heat-c { display: grid; height: 30px; place-items: center; border-radius: 5px;
  font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
  line-height: 1; overflow: hidden; }
.heat-c:hover { outline: 1.5px solid var(--mzone-text); outline-offset: 1px; }
.heat-c.l0 { background: var(--heat-0); color: transparent; }
.heat-c.l1 { background: var(--heat-1); color: var(--heat-ink-lo); }
.heat-c.l2 { background: var(--heat-2); color: var(--heat-ink-lo); }
.heat-c.l3 { background: var(--heat-3); color: var(--heat-ink-hi); }
.heat-c.l4 { background: var(--heat-4); color: var(--heat-ink-hi); }
.heat-scale { display: flex; align-items: center; gap: var(--mzone-space-2);
  margin-top: var(--mzone-space-3); color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.heat-legend { display: inline-flex; gap: 3px; }
.heat-legend i { width: 18px; height: 10px; border-radius: 3px; }
.heat-legend i.l0 { background: var(--heat-0); }
.heat-legend i.l1 { background: var(--heat-1); }
.heat-legend i.l2 { background: var(--heat-2); }
.heat-legend i.l3 { background: var(--heat-3); }
.heat-legend i.l4 { background: var(--heat-4); }
@media (max-width: 720px) {
  .heat { grid-template-columns: 28px repeat(24, minmax(0,1fr)); gap: 2px; }
  /* клетка шириной ~10px: цифру такого кегля не вместить, оставляем цвет */
  .heat-c { height: 18px; font-size: 8.5px; font-weight: 550; }
}

/* Список сигналов «требует внимания» */
.signal { display: flex; gap: var(--mzone-space-3); align-items: flex-start;
  padding: var(--mzone-space-3) 0; border-bottom: 1px solid var(--mzone-border-soft); }
.signal:last-child { border-bottom: 0; }
.signal-pill { flex: none; padding: 3px 8px; border-radius: var(--mzone-radius-xs);
  font-size: var(--mzone-font-size-xs); font-weight: 600;
  background: var(--mzone-danger-bg); color: var(--mzone-danger); }
.signal-pill.warn { background: var(--mzone-warning-bg); color: var(--mzone-warning); }
.signal-tx { min-width: 0; font-size: var(--mzone-font-size-md); line-height: 1.4; }
.signal-tx span { display: block; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }

/* Раскладка остатков по складам внутри «Статистики остатков» (запрос владельца 02.08):
   из сводки не понять, где лежит товар, а поставку собирают именно по складам. */
.stock-detail-row > td { border-top: 0; }
.stock-places { display: flex; flex-wrap: wrap; gap: var(--mzone-space-2); margin: 0.5rem 0 0.75rem; }
.stock-place {
  display: flex; align-items: baseline; gap: 0.5rem; padding: 0.375rem 0.75rem;
  background: var(--mzone-surface-muted); border: 1px solid var(--mzone-border);
  border-radius: 0.5rem; font-size: var(--mzone-font-size-sm);
}
.stock-place-name { font-weight: 550; }
.stock-place-qty { font-variant-numeric: tabular-nums; color: var(--mzone-text-muted); }
.stock-place-sizes { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); }

/* Форма правила автоставок: подписи разной длины ставили поля на разную высоту.
   Ячейка — колонка с прижатым к низу полем, ширина колонок одинаковая. */
.rule-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); align-items: end; }
.rule-grid .form-label { display: flex; min-height: 62px; flex-direction: column;
  justify-content: flex-end; gap: var(--mzone-space-2); }
.rule-grid .input, .rule-grid .select { width: 100%; }

/* ─────────────────────────────────────────────────────────────────────────────
   Свой склад: единая панель вкладок + карта склада (план 2026-08-13).
   Панель показывается на всех страницах склада, карта только ПОКАЗЫВАЕТ раскладку
   BinStock (подсчёт остатков не трогаем). Цвета — только токены mzone.
   ───────────────────────────────────────────────────────────────────────────── */
.wh-tabs { overflow-x: auto; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
.wh-tabs .segmented { min-width: max-content; }

.wh-map { display: flex; flex-direction: column; gap: var(--mzone-space-3); min-width: max-content; }
.wh-map-row { display: flex; align-items: stretch; gap: var(--mzone-space-2); }
.wh-map-rowlabel { flex: 0 0 auto; align-self: center; min-width: 4.5rem;
  color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; }
.wh-cell { display: flex; flex: 0 0 auto; width: 7rem; min-height: 3.75rem;
  flex-direction: column; justify-content: center; gap: 2px; padding: var(--mzone-space-2);
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-sm);
  background: var(--mzone-surface); color: var(--mzone-text); text-align: center;
  text-decoration: none; transition: var(--mzone-transition-fast); }
.wh-cell:hover { border-color: var(--mzone-text-soft); box-shadow: 0 2px 8px rgba(6,20,43,0.10); }
.wh-cell.is-selected { outline: 2px solid var(--mzone-solid); outline-offset: 1px; }
/* Код (штрихкод, номер коробки) — ОДИН токен: разорванный по строкам он
   нечитаем и выглядит как поломка вёрстки (сканер типографики поймал это на
   пересчёте коробки 10.09.2026). Таблицы и так прокручиваются вбок своим
   .table-scroll, поэтому переносить код внутри незачем. */
.wh-cell-code { font-family: var(--mzone-font-mono); font-weight: 600;
  white-space: nowrap; }
.wh-cell-qty { font-size: var(--mzone-font-size-xs); color: var(--mzone-text-muted); }
.wh-cell-empty { background: var(--mzone-surface-muted); color: var(--mzone-text-muted); border-style: dashed; }
.wh-cell-ok { background: var(--mzone-success-bg); border-color: var(--mzone-success); }
.wh-cell-low { background: var(--mzone-warning-bg); border-color: var(--mzone-warning); }
.wh-cell-warn { background: var(--mzone-danger-bg); border-color: var(--mzone-danger); }
.wh-cell-ok .wh-cell-qty { color: var(--mzone-success); }
.wh-cell-low .wh-cell-qty { color: var(--mzone-warning); }
.wh-cell-warn .wh-cell-qty { color: var(--mzone-danger); }

.wh-legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--mzone-space-3); }
.wh-legend-item { display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--mzone-font-size-xs); color: var(--mzone-text-muted); }
.wh-legend-swatch { width: 14px; height: 14px; border: 1px solid var(--mzone-border); border-radius: 4px; }
.wh-legend-empty { background: var(--mzone-surface-muted); border-style: dashed; }
.wh-legend-ok { background: var(--mzone-success-bg); border-color: var(--mzone-success); }
.wh-legend-low { background: var(--mzone-warning-bg); border-color: var(--mzone-warning); }
.wh-legend-warn { background: var(--mzone-danger-bg); border-color: var(--mzone-danger); }


/* ── Вкладки разделов склада ─────────────────────────────────────────────
   Ряд стоит намертво: первый элемент content, прилипает под шапкой и не
   меняет ни высоту, ни ширину при переходе между разделами. «Вкладки прыгают,
   каждый раздел как новая история» — жалоба владельца 24.08. Стабильность
   держат три вещи: sticky-позиция, фиксированная высота ряда и счётчик,
   который занимает место даже пустым (visibility, а не display). */
.sklad-subnav { position: sticky; top: var(--mzone-topbar-height); z-index: 20;
  margin: calc(-1 * var(--mzone-space-7)) calc(-1 * var(--mzone-space-7)) var(--mzone-space-6);
  padding: 0 var(--mzone-space-7); min-height: 48px; align-items: stretch;
  background: var(--mzone-topbar-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--mzone-border-soft); }
.sklad-subnav .tab { min-height: 48px; }
/* Порог 1279px совпадает с сужением паддинга .content: иначе на 901–1279px
   отрицательные маргины ряда вылезали за экран на 4px (находка QA-обхода). */
@media (max-width: 1279px) {
  .sklad-subnav { margin-right: calc(-1 * var(--mzone-space-6)); margin-left: calc(-1 * var(--mzone-space-6));
    padding: 0 var(--mzone-space-6); }
}

/* Счётчик на вкладке раздела: три черновика и двенадцать заказов к отбору должны
   быть видны, не заходя внутрь. Ноль не рисуется, но МЕСТО занимает: иначе ряд
   вкладок дёргается по ширине при каждом переходе. */
.tab-count { margin-left: .35rem; padding: .05rem .35rem; border-radius: var(--mzone-radius-pill);
  background: var(--mzone-surface-muted); color: var(--mzone-text-muted); min-width: 1.4em;
  text-align: center; font-size: var(--mzone-font-size-xs); font-weight: 600;
  font-variant-numeric: tabular-nums; }
.tab-count-empty { visibility: hidden; }
.tab.active .tab-count, .tab[aria-selected="true"] .tab-count { background: var(--mzone-cyan-400); color: var(--mzone-navy-950); }

/* ── Выпадающий список с поиском (select[data-search] + mzone-ui.js) ─────
   Кнопка выглядит как обычный .input, панель — как .search-pop. Нативный селект
   спрятан, но живёт в форме: без JS всё работает по-старому. */
.sselect { position: relative; display: inline-block; min-width: 160px; }
.sselect-native { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.sselect-button { display: inline-flex; width: 100%; align-items: center; justify-content: space-between;
  gap: .5rem; cursor: pointer; text-align: left; }
.sselect-button::after { content: ""; flex: none; width: .55em; height: .55em;
  border-right: 2px solid var(--mzone-text-muted); border-bottom: 2px solid var(--mzone-text-muted);
  transform: rotate(45deg) translateY(-2px); }
.sselect-pop { position: absolute; top: calc(100% + 4px); left: 0; z-index: 50; min-width: 100%;
  width: max-content; max-width: min(560px, 92vw); padding: .4rem; border: 1px solid var(--mzone-border);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface);
  box-shadow: var(--mzone-shadow-float); }
.sselect-search { width: 100%; margin-bottom: .35rem; }
.sselect-list { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; }
/* flex: none обязателен: в flex-колонке с max-height десятки опций СЖИМАЛИСЬ
   по высоте до полосок в 14px — «где товары?» (прод, 30.08). Список должен
   скроллиться, а не ужимать строки. */
.sselect-list > .sselect-item { flex: 0 0 auto; }
.sselect-item { display: block; width: 100%; padding: .45rem .6rem; border: 0; border-radius: var(--mzone-radius-sm);
  background: transparent; color: var(--mzone-text); text-align: left; font-size: var(--mzone-font-size-base);
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sselect-item:hover, .sselect-item:focus-visible { background: var(--mzone-surface-muted); }
.sselect-item.active { background: var(--mzone-cyan-100); color: var(--mzone-navy-950); font-weight: 600; }
.sselect-empty { padding: .45rem .6rem; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }

/* Товар в таблицах склада — ссылка в карточку товара (просьба владельца:
   «редактор товара сразу по ссылке, нажимая на товар»). Внешне строка не
   меняется, подчёркивание появляется только под курсором. */
.product-link { color: inherit; text-decoration: none; display: flex; }
/* Фото в карточке товара склада: без сырых пикселей в шаблоне (правило §5/§15). */
.good-photo { float: right; max-width: 120px; margin-left: var(--mzone-space-4);
  border-radius: var(--mzone-radius-sm); }
.product-link:hover .product-title { text-decoration: underline; }
/* Тач-экраны: тонкая ссылка в ячейке таблицы получает тап-зону 44px БЕЗ роста
   строки — паддинг компенсируется отрицательным маргином (находка QA-обхода). */
@media (max-width: 1023px) {
  .table .product-link, .table .wb-link { padding-block: 0.8rem; margin-block: -0.8rem; }
}

/* Модальный справочник склада: dialog поверх всего, карточка своей ширины. */
.sklad-modal { border: 0; padding: 0; background: transparent; max-width: min(920px, 94vw);
  width: 920px; }
.sklad-modal::backdrop { background: rgba(6, 20, 43, 0.45); backdrop-filter: blur(2px); }
.sklad-modal .card { max-height: 84vh; display: flex; flex-direction: column; }
.sklad-modal .table-scroll { overflow-y: auto; }
/* ── Рабочее место сканера ────────────────────────────────────────────────
   Поле сканера должно быть заметно с двух шагов: оператор смотрит на товар, а
   не на экран. Стили жили в локальных <style> двух страниц (приёмка и очередь
   FBS), а ЛИСТ ПОДБОРА — главное рабочее место сборщика — те же классы только
   звал: у него поле рисовалось браузерным по умолчанию, без рамки и без 48px
   под палец (найдено сверкой 10.09.2026). Теперь правило одно на все экраны. */
.scan-box { border: 2px dashed var(--mzone-teal-600); background: var(--mzone-cyan-100);
  border-radius: var(--mzone-radius-md); padding: var(--mzone-space-5); }
.scan-box input { font-size: var(--mzone-font-size-xl); font-weight: 600;
  letter-spacing: .04em; }
.scan-row { display: flex; gap: var(--mzone-space-3); align-items: flex-end; flex-wrap: wrap; }
.scan-row .field { flex: 1 1 260px; margin-bottom: 0; }
/* 48px — минимальная цель под палец: сборщик работает с планшета в перчатке. */
.fbs-scan-input { font-size: 18px; padding: 12px 14px; width: 100%; min-height: 48px;
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md);
  background: var(--mzone-surface); color: var(--mzone-text); }
.fbs-scan-card { min-height: 180px; }
.fbs-big-btn { min-height: 52px; font-size: 16px; padding: 0 20px; }
/* Ответ сканеру цветом: звук планшета в цеху не слышно. */
.fbs-flash-ok { box-shadow: 0 0 0 3px var(--mzone-success); transition: box-shadow .1s; }
.fbs-flash-err { box-shadow: 0 0 0 3px var(--mzone-danger); transition: box-shadow .1s; }
.fbs-mono { font-family: var(--mzone-font-mono); }

/* Панель выбора в приёмке шире триггера: список товаров должен читаться. */
.scan-box .sselect { width: 100%; }
.scan-box .sselect-pop { max-width: min(560px, calc(100vw - 2rem)); }

/* ── Хлебные крошки ───────────────────────────────────────────────────────
   Отвечают на вопрос «где я» и дают путь назад одним кликом. На складе экраны
   вложены на три уровня, и без них возврат к списку — это кнопка «назад»
   браузера, которая после проведения документа возвращает на форму. */
.breadcrumbs { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--mzone-space-2); margin-bottom: var(--mzone-space-4);
  font-size: var(--mzone-font-size-sm); color: var(--mzone-text-muted); }
.breadcrumbs a { color: var(--mzone-text-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--mzone-teal-600); text-decoration: underline; }
.breadcrumbs [aria-current="page"] { color: var(--mzone-text); font-weight: 600; }
.breadcrumbs-sep { color: var(--mzone-border-strong, var(--mzone-border)); }

/* ── Свёрнутое меню на широком экране ─────────────────────────────────────
   Токен ширины был объявлен и не использовался никем — работа брошена на
   середине. У складских таблиц по десять колонок, и 296 пикселей слева стоят
   дорого. Сворачиваем до иконок, а не прячем совсем: пустая полоса слева хуже
   узкой, по значку человек всё ещё узнаёт раздел. */
.app.is-collapsed { grid-template-columns: var(--mzone-sidebar-collapsed-width) minmax(0,1fr); }
.app.is-collapsed .sidebar { width: var(--mzone-sidebar-collapsed-width);
  padding-left: 0.75rem; padding-right: 0.75rem; }
.app.is-collapsed .nav-item-text,
.app.is-collapsed .nav-label,
.app.is-collapsed .nav-mp-summary .badge { display: none; }
.app.is-collapsed .nav-item { justify-content: center; padding: 0; }
.app.is-collapsed .nav-mp .nav-item { padding-left: 0; }
.app.is-collapsed .sidebar-logo img { height: 34px; }
/* Разделитель вместо подписи группы: без него иконки сливаются в один столбик. */
.app.is-collapsed .nav-label { display: block; height: 1px; margin: var(--mzone-space-3) 0;
  overflow: hidden; padding: 0; background: rgba(255,255,255,0.14); text-indent: -999em; }
.app.is-collapsed .nav-mp-summary { justify-content: center; }
.sidebar-toggle { display: none; }
@media (min-width: 1024px) { .sidebar-toggle { display: inline-flex; } }
.sidebar-toggle[aria-pressed="true"] { color: var(--mzone-teal-600); }

/* ── Строка ссылок под вкладками склада ───────────────────────────────────
   У эталона под вкладками склада идёт строка привязки сборки FBS. У нас там же
   то, что ушло из ряда вкладок, когда их стало ровно пять: сборка, календарь,
   прогноз, клиенты. Это НЕ вкладки: они ведут в соседние разделы, и выглядеть
   должны иначе, иначе человек ищет среди них «Приёмки». */
.sklad-links { display: flex; flex-wrap: wrap; gap: var(--mzone-space-2);
  align-items: center; margin-bottom: var(--mzone-space-4); }
.sklad-link { display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px; border: 1px solid var(--mzone-border);
  background: var(--mzone-surface); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-sm); text-decoration: none;
  transition: color var(--mzone-transition), border-color var(--mzone-transition); }
/* Текст берёт teal-700, рамка — teal-600: teal-600 на светлой заливке даёт 2.89
   при пороге 4.5 (замер axe 11.09.2026), а на рамке контраст текста не меряется. */
.sklad-link:hover { border-color: var(--mzone-teal-600); color: var(--mzone-teal-700); }
.sklad-link.active { border-color: var(--mzone-teal-600); color: var(--mzone-teal-700);
  background: var(--mzone-cyan-100); }

/* ── Строка выбора в диалоге ──────────────────────────────────────────────
   Радио с подписью в две строки: название сверху, объяснение снизу. Голый
   список радиокнопок не объясняет разницу, а разница здесь и есть выбор. */
.radio-row { display: flex; align-items: flex-start; gap: var(--mzone-space-3);
  padding: var(--mzone-space-3); border-radius: var(--mzone-radius-sm);
  cursor: pointer; }
.radio-row:hover { background: var(--mzone-surface-alt, var(--mzone-cyan-100)); }
.radio-row input { margin-top: 3px; }

/* ── Полоса прогресса ─────────────────────────────────────────────────────
   Доля в строке таблицы: подобрано из пачки, уложились в срок. Жила копией в
   шаблоне поставки, а нужна ещё и статистике сборки — переехала в систему.
   `.is-bad` красит просроченную долю: цвет здесь единственный способ увидеть
   хвост, не читая числа. */
.pick-bar { height: 8px; border-radius: 4px; min-width: 80px;
  background: var(--mzone-surface-muted); overflow: hidden; }
.pick-bar > span { display: block; height: 100%; background: var(--mzone-success); }
.pick-bar > span.is-bad { background: var(--mzone-danger); }

/* ── Реквизиты документа ──────────────────────────────────────────────────
   Пары «поле — значение» на карточке проведённого документа. У эталона это
   первый блок под шапкой: дата, склад, позиции, комментарий. Колонки тянутся
   по содержимому — фиксированная ширина подписи рвала бы длинные названия. */
.def-list { display: grid; gap: var(--mzone-space-2) var(--mzone-space-6);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.def-list > div { display: flex; flex-direction: column; gap: 2px;
  min-width: 0; word-break: break-word; }

/* ── Полоска над таблицей ─────────────────────────────────────────────────
   «Скопировать всё» и выгрузка живут НАД таблицей, а не в её шапке: шапка
   карточки уже занята названием и подписью, и кнопки там не видно. */
.table-toolbar { display: flex; flex-wrap: wrap; gap: var(--mzone-space-2);
  align-items: center; padding: var(--mzone-space-3) var(--mzone-space-4) 0; }
.table-toolbar .spacer { margin-left: auto; }

/* ── Ответ сканеру ────────────────────────────────────────────────────────
   Сборщик смотрит в экран полсекунды между двумя вещами: ответ должен читаться
   с двух шагов и цветом, а не мелким текстом под полем. Правило жило в локальном
   стиле смены раскладки, а лист подбора писал ответ обычной строкой — два разных
   вида одного и того же на двух соседних рабочих местах. */
.scan-answer { min-height: 58px; display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-radius: var(--mzone-radius-md);
  background: var(--mzone-surface-soft); font-size: var(--mzone-font-size-lg); }
.scan-answer:empty { display: none; }
.scan-answer.is-ok { background: var(--mzone-success-bg, var(--mzone-surface-soft));
  color: var(--mzone-success); }
.scan-answer.is-err { background: var(--mzone-danger-bg, var(--mzone-surface-soft));
  color: var(--mzone-danger); }
.scan-input { font-size: 20px; padding: 14px 16px; }

/* ── Массовый выбор в списке: панель «Выбрано: N» ─────────────────────────
   Жил в локальном <style> очереди FBS, хотя нужен везде, где есть список и
   действие над пачкой. Панель висит внизу экрана, а страница получает отступ под
   неё — иначе она накрывает последние строки таблицы. Галочка под палец: сборщик
   работает с планшета. */
.bulk-pick { width: 22px; height: 22px; cursor: pointer; accent-color: var(--mzone-teal-600); }
.bulk-pick-cell { width: 44px; text-align: center; }
.table tr.is-picked > td { background: var(--mzone-surface-soft); }
.bulkbar { position: fixed; right: 0; bottom: 0; left: var(--mzone-sidebar-width);
  z-index: 45; display: flex; max-height: 45vh; gap: 8px; flex-wrap: wrap;
  align-items: center; overflow-y: auto;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--mzone-border); background: var(--mzone-surface);
  box-shadow: 0 -8px 24px rgba(2, 12, 27, 0.16); }
.bulkbar[hidden] { display: none; }
.bulkbar .btn { min-height: 44px; }
.bulk-count { margin-right: 6px; font-size: 15px; font-weight: 600; }
.app.is-collapsed .bulkbar { left: var(--mzone-sidebar-collapsed-width); }
@media (max-width: 1023px) { .bulkbar { left: 0; } }
/* Прежние имена очереди FBS: экран сборщика трогать ради переименования нельзя. */
.fbs-pick { width: 22px; height: 22px; cursor: pointer; accent-color: var(--mzone-teal-600); }
.fbs-pick-cell { width: 44px; text-align: center; }

/* ── Черта по дням в списке ───────────────────────────────────────────────
   Не украшение: без неё длинный список читается как сплошная стена, и «что было
   сегодня» приходится искать датой в каждой строке. */
.day-divider > td { padding: var(--mzone-space-3) var(--mzone-space-3) var(--mzone-space-2);
  border-bottom: 1px solid var(--mzone-border); background: var(--mzone-surface-soft); }
.day-divider span { font-size: var(--mzone-font-size-sm); font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase; color: var(--mzone-text-muted); }
.table tr.day-divider:hover > td { background: var(--mzone-surface-soft); }

/* ── Номер сущности: IN-3, OUT-7 ──────────────────────────────────────────
   Моноширинный, чтобы номера выстраивались столбиком в списке, и копируемый по
   клику: номер называют вслух и вбивают в поиск шапки. */
.entity-no { display: inline-block; font-family: var(--mzone-font-mono);
  font-weight: 600; font-size: var(--mzone-font-size-sm); letter-spacing: .02em;
  white-space: nowrap; padding: 2px 7px; border-radius: var(--mzone-radius-sm);
  border: 1px solid var(--mzone-border); background: var(--mzone-surface-soft);
  color: var(--mzone-text); }
a.entity-no:hover { border-color: var(--mzone-teal-600); color: var(--mzone-teal-600); }
.entity-no.is-copied { border-color: var(--mzone-success); color: var(--mzone-success); }

/* Поиск одной строкой: поле и кнопка не переносятся друг под друга. */
.nowrap-form { display: flex; flex-wrap: nowrap; gap: var(--mzone-space-2); align-items: center; }
.nowrap-form .input { min-width: 0; flex: 1 1 220px; }
.row-actions { display: flex; gap: var(--mzone-space-2); flex-wrap: wrap; }

/* ── Планшет и телефон: ничего не вылезает за правый край (обход 04.09.2026) ── */
@media (max-width: 1023px) {
  /* Кнопки действий и формы-тулбары переносятся, а не выталкивают правый край
     (на 768 форма импорта себестоимостей уезжала на 60px за экран). */
  .page-actions, .toolbar, .card-header, form.flex { flex-wrap: wrap; }
}
@media (max-width: 767px) {
  /* Правая часть шапки (колокольчик, аватар) не сжимается — сжимается поиск.
     Иначе аватар вылезал за экран, и до профиля было не добраться. */
  .topbar-end { flex: 0 0 auto; }
  /* Вкладки склада растянуты на всю ширину отрицательными полями: на телефоне
     поле контента 16px, а не 24 — иначе полоса вкладок торчит на 8px справа. */
  .sklad-subnav { margin-right: calc(-1 * var(--mzone-space-4)); margin-left: calc(-1 * var(--mzone-space-4)); }
  /* Сегменты («Сегодня · Вчера · 7 дней…», штрафы) прокручиваются, а не давят страницу. */
  .segmented { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
  .segmented-item { flex: 0 0 auto; white-space: nowrap; }
  /* Карточки товаров — одной колонкой; статусы и поля переносятся. */
  .cards-grid, .cards-grid-wide { grid-template-columns: minmax(0, 1fr); }
  .status { white-space: normal; }
  /* Утилита .nowrap сама с !important; дата свежести собрана неразрывными
     пробелами, поэтому одного white-space мало — режем в любом месте. */
  .mp-strip .nowrap { white-space: normal !important; overflow-wrap: anywhere; min-width: 0; max-width: 100%; flex: 1 1 auto; }
  .mp-strip-end { max-width: 100%; }
  .form-input { max-width: 100%; }
  /* Липкая панель остаётся ОДНОЙ строкой: общее «.toolbar { flex-wrap: wrap }»
     выше и утилита .flex-wrap с !important на переключателях складывали её в
     четыре ряда на весь первый экран. */
  .toolbar.toolbar-sticky { flex-wrap: nowrap; }
  .toolbar-sticky .segmented { flex-wrap: nowrap !important; }
}
@media (max-width: 480px) {
  /* Бургер, поиск, тема (две кнопки по 44px), колокольчик и аватар в 375px не
     помещаются: аватар выталкивался за экран, и до профиля было не добраться.
     Переключатель темы на телефоне прячем — тема остаётся сохранённой/системной. */
  .topbar .theme-switch { display: none; }
  /* Выпадающий список комбо начинается не от края экрана (поля страницы и карточки),
     поэтому «100vw − 2rem» ещё торчал на 7px справа. */
  .scan-box .sselect-pop { max-width: calc(100vw - 4.5rem); }
}

/* ── Лист подбора: группы по товарам ─────────────────────────────────────────
   Сборщик работает товарами, а не заданиями: он снимает с полки пять одинаковых
   кепок разом. Строка группы крупная и с фото — вещь на полке ищут глазами, и
   строка без картинки заставляет читать название целиком (образец WB, замечание
   владельца 10.09.2026). Заказы прячутся в details: они нужны при разборе, а не
   при обходе полок. */
.pick-groups { display: flex; flex-direction: column; }
.pick-group { position: relative; border-top: 1px solid var(--mzone-border-soft); }
.pick-group:first-child { border-top: 0; }
/* Кнопки строки живут СНАРУЖИ <summary> (вложенный интерактив ломает клавиатуру
   и экранные читалки), но выглядят частью шапки. Держим их абсолютом в углу
   группы, а шапке даём отступ справа, чтобы текст под них не заезжал. */
.pick-actions { position: absolute; top: 0.875rem; right: 1.125rem; z-index: 1;
  display: flex; gap: var(--mzone-space-2); flex-wrap: wrap; }
/* Отступ справа под кнопки в ТОМ ЖЕ правиле, что и остальные поля. Жил
   отдельной строкой выше, и сокращённое `padding` следующего правила его
   сбрасывало: кнопки ложились поверх адреса полки на всём, что шире 900 px
   (обход 24.09.2026). */
.pick-head { display: flex; align-items: center; gap: var(--mzone-space-4);
  padding: 0.875rem 8rem 0.875rem 1.125rem; cursor: pointer; list-style: none; flex-wrap: wrap; }
/* Вторая кнопка («Упаковано всё»): отступ под обе. Уже 900 px кнопки
   встают в поток (правило ниже), и отступ не нужен. */
@media (min-width: 901px) {
  .pick-group:has(> .pick-actions > :nth-child(2)) > .pick-fold > .pick-head { padding-right: 16rem; }
}
.pick-head::-webkit-details-marker { display: none; }
.pick-head:hover { background: var(--mzone-surface-soft); }
.pick-count { min-width: 62px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--mzone-text); }
.pick-head .product-cell { flex: 1 1 260px; min-width: 0; gap: var(--mzone-space-3); }
.pick-photo { width: 56px; height: 56px; flex: 0 0 56px; border-radius: var(--mzone-radius-sm);
  object-fit: cover; background: var(--mzone-surface-soft); }
.pick-photo-empty { display: flex; align-items: center; justify-content: center;
  color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); text-align: center; }
.pick-title { min-width: 0; }
.pick-chips { margin-top: 4px; }
.pick-progress { min-width: 132px; }
.pick-progress-line { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.pick-progress-line b { color: var(--mzone-text); }
.pick-shelf { min-width: 108px; text-align: right; }
/* Шеврон: строка раскрывается, и это должно быть видно ДО клика. */
.pick-chevron { display: inline-flex; flex: 0 0 auto; color: var(--mzone-text-muted);
  transition: transform var(--mzone-transition); }
.pick-fold[open] > .pick-head .pick-chevron { transform: rotate(180deg); }
.pick-print { flex: 0 0 auto; }
.pick-tasks { padding: 0 1.125rem 0.875rem 5rem; display: flex; flex-direction: column; }
.pick-task { display: flex; align-items: center; gap: var(--mzone-space-3); flex-wrap: wrap;
  padding: 0.5rem 0; border-top: 1px dashed var(--mzone-border-soft); }
.pick-task-no { color: var(--mzone-text-muted); min-width: 24px; }
.pick-task-actions { display: flex; gap: var(--mzone-space-2); flex-wrap: wrap;
  margin-left: auto; }
@media (max-width: 900px) {
  /* На узком экране кнопки встают в обычный поток под строку: в углу они
     наехали бы на название товара. */
  .pick-actions { position: static; padding: 0 1.125rem 0.5rem; }
  .pick-head { padding-right: 1.125rem; }
}
@media (max-width: 720px) {
  /* На телефоне подпись прогресса и полка уезжают под товар, а не жмут его в
     колонку шириной в три буквы. */
  .pick-head { gap: var(--mzone-space-2); }
  .pick-count { min-width: 48px; }
  .pick-progress, .pick-shelf { min-width: 0; text-align: left; }
  .pick-tasks { padding-left: 1.125rem; }
  .pick-task-actions { margin-left: 0; width: 100%; }
}

/* ─── тап-таргеты на телефоне (правило 13 UI-STYLEGUIDE) ──────────────────────
   Обход 18.09.2026 нашёл четыре вида целей мельче 44 px, и все на каждой
   витрине сразу: переключатель темы в шапке (28×26), ссылки складского меню
   (высота 36), сегментные переключатели периода (30) и ссылка площадки в
   верхнем меню (42×24). На мышке они удобны и менять их незачем, поэтому
   растим ТОЛЬКО на узких экранах и там, где палец — единственный инструмент.
   Правило стоит в конце файла сознательно (правило 8: при равной
   специфичности побеждает последнее). */
@media (max-width: 767px) {
  .theme-switch button { min-width: 44px; min-height: 44px; }
  .sklad-link { min-height: 44px; }
  .segmented-item { min-height: 44px; }
  .nav-mp-link, .topbar-nav a { min-height: 44px; display: inline-flex; align-items: center; }
  /* Хлебные крошки: «Склад / Документы» — это навигация, по ней возвращаются,
     а высота строки давала 24 px. Ссылки ВНУТРИ таблиц остаются исключением
     (правило 13): там 44 px на строку раздули бы таблицу вдвое. */
  .breadcrumbs a { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ─── Контроль FBS: график, подсказки плиток, склады, список ─────────────────
   Столбики рисуются флексом, а не картинкой: их двадцать-тридцать, они должны
   сжиматься вместе с экраном и оставаться читаемыми на телефоне. Цвет — путь
   заказа: серый в начале, зелёный в конце, красный у беды. Ось Y и линии
   сетки нужны, чтобы столбик читался числом без наведения — у эталона они есть. */
.fbs-chart { display: grid; grid-template-columns: 2rem minmax(0, 1fr); column-gap: var(--mzone-space-2); }
.fbs-axis { position: relative; height: 160px; }
.fbs-axis-tick { position: absolute; right: 0; transform: translateY(50%); color: var(--mzone-text-muted);
  font-size: 0.625rem; font-variant-numeric: tabular-nums; line-height: 1; }
.fbs-plot { position: relative; height: 160px; }
.fbs-grid-line { position: absolute; right: 0; left: 0; height: 0; border-top: 1px dashed var(--mzone-border-soft); }
.fbs-bars { position: relative; display: flex; align-items: flex-end; gap: 3px; height: 100%; }
.fbs-bar { display: flex; min-width: 0; flex: 1 1 0; height: 100%; }
.fbs-bar-stack { display: flex; width: 100%; height: 100%; flex-direction: column-reverse;
  justify-content: flex-start; border-radius: 3px; overflow: hidden; }
.fbs-bar-part { width: 100%; min-height: 2px; }
.fbs-days { display: flex; gap: 3px; margin-top: 4px; }
/* Подпись начинается под своим столбиком и вылезает вправо, на место пустых
   соседей: ячейка шириной в столбик вмещает два символа, и дата превращалась
   в «2…» (замер на проде 18.09.2026). */
.fbs-bar-day { min-width: 0; flex: 1 1 0; overflow: visible; color: var(--mzone-text-muted);
  font-size: 0.625rem; text-align: left; white-space: nowrap; }
.fbs-dot { display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  vertical-align: -1px; }
.fbs-new { background: #94a3b8; }
.fbs-assembly { background: #6366f1; }
.fbs-transit { background: #0ea5e9; }
.fbs-pickup { background: #f59e0b; }
.fbs-bought { background: #10b981; }
.fbs-cancelled { background: #94a3b8; opacity: 0.55; }
.fbs-problem { background: #ef4444; }
/* Чип этапа в таблице: тот же цвет, что у столбика, но текстом по светлому
   фону — иначе строка таблицы превращается в светофор и читать её нельзя. */
.fbs-chip { border: 1px solid transparent; background: var(--mzone-surface-soft);
  color: var(--mzone-text); }
.fbs-chip.fbs-bought { background: rgba(16,185,129,0.14); color: #047857; }
.fbs-chip.fbs-problem { background: rgba(239,68,68,0.14); color: #b91c1c; }
.fbs-chip.fbs-pickup { background: rgba(245,158,11,0.16); color: #b45309; }
.fbs-chip.fbs-transit { background: rgba(14,165,233,0.14); color: #0369a1; }
.fbs-chip.fbs-assembly { background: rgba(99,102,241,0.14); color: #4338ca; }
.fbs-chip.fbs-cancelled { background: var(--mzone-surface-soft); color: var(--mzone-text-muted); }
/* Подсказка «?» у плитки — details без JS, раскрывается поверх соседей. Текст
   объясняет, ИЗ ЧЕГО посчитано число: у эталона это «О показателе». */
.kpi-card-help { position: relative; }
.kpi-help { position: absolute; top: 0.875rem; right: 0.875rem; }
.kpi-help > summary { display: inline-flex; width: 24px; height: 24px; align-items: center;
  justify-content: center; border: 1px solid var(--mzone-border); border-radius: 50%;
  color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); font-weight: 600;
  cursor: pointer; list-style: none; }
.kpi-help > summary::-webkit-details-marker { display: none; }
.kpi-help > summary:hover, .kpi-help[open] > summary { border-color: var(--mzone-teal-600); color: var(--mzone-teal-700); }
.kpi-help-body { position: absolute; z-index: 30; top: calc(100% + 6px); right: 0;
  width: min(300px, 78vw); padding: var(--mzone-space-3); border: 1px solid var(--mzone-border);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface);
  box-shadow: var(--mzone-shadow-float); color: var(--mzone-text);
  font-size: var(--mzone-font-size-sm); font-weight: 400; line-height: 1.4; text-align: left; }
.kpi-help-body b { display: block; margin-bottom: 0.25rem; }
/* Контроль заказов: три строки, беда подсвечена фоном, как у эталона. */
.fbs-issues { display: grid; gap: var(--mzone-space-3); }
.fbs-issue { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--mzone-space-3); padding: var(--mzone-space-3) var(--mzone-space-4);
  border: 1px solid var(--mzone-border-soft); border-radius: var(--mzone-radius-md); }
.fbs-issue > * { min-width: 0; }
.fbs-issue.is-bad { border-color: transparent; background: var(--mzone-danger-bg); }
.fbs-issue-value { flex: 0 0 auto; font-size: var(--mzone-font-size-xl); font-weight: 700;
  font-variant-numeric: tabular-nums; }
/* Карточка склада продавца: шапка с кнопкой, шесть чисел сеткой, подвал про остаток.
   Число колонок — классом (правило 7 стайлгайда), на узком экране две. */
.fbs-wh { margin-bottom: var(--mzone-space-3); border: 1px solid var(--mzone-border-soft);
  border-radius: var(--mzone-radius-md); }
.fbs-wh.is-active { border-color: var(--mzone-teal-600); }
.fbs-wh-head { display: flex; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; gap: var(--mzone-space-3); padding: var(--mzone-space-3) var(--mzone-space-4);
  border-bottom: 1px solid var(--mzone-border-soft); }
.fbs-wh-head > * { min-width: 0; }
.fbs-wh-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--mzone-space-3) var(--mzone-space-4); padding: var(--mzone-space-3) var(--mzone-space-4); }
.fbs-wh-grid > * { min-width: 0; }
.fbs-wh-value { font-size: var(--mzone-font-size-lg); font-weight: 600;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.fbs-wh-foot { padding: var(--mzone-space-3) var(--mzone-space-4); border-top: 1px solid var(--mzone-border-soft); }
/* Хронология заказа — отдельной строкой таблицы на всю ширину (как раскрытие
   у эталона). Кнопка с data-more показывает строку, шаги лежат сеткой карточек. */
.fbs-more-btn { margin-top: 0.25rem; padding: 0; border: 0; background: none;
  color: var(--mzone-teal-700); font: inherit; font-size: var(--mzone-font-size-xs);
  font-weight: 600; cursor: pointer; }
.fbs-more-btn:hover { text-decoration: underline; }
.fbs-detail > td { background: var(--mzone-surface-soft); white-space: normal; }
.fbs-detail-head { margin-bottom: var(--mzone-space-3); }
.fbs-steps { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--mzone-space-3); }
.fbs-step { min-width: 0; padding: var(--mzone-space-3); border: 1px solid var(--mzone-border-soft);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface); }
.fbs-step-time { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs);
  font-variant-numeric: tabular-nums; }
@media (max-width: 1023px) {
  .fbs-wh-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* На широком экране график стоит рядом с таблицей скорости и должен дотягиваться
   до её низа: иначе под столбиками остаётся пустая половина карточки. */
@media (min-width: 1024px) {
  .fbs-axis, .fbs-plot { height: 260px; }
}
/* Полоска доли не должна раздвигать таблицу: с шириной 100% первая колонка
   выталкивала «Заказов» и «Долю» за экран телефона, и числа приходилось искать
   горизонтальной прокруткой. */
.fbs-speed-bar { max-width: 180px; }
/* Таблица из трёх коротких колонок не обязана быть шириной 720 px. Общий минимум
   спасает широкие таблицы товаров, но здесь он уводил «Заказов» и «Долю» за
   правый край телефона, и главные числа приходилось искать прокруткой вбок. */
.table-compact { min-width: 0 !important; }
/* И поля ячеек уже: четыре колонки «Прибыли по кабинетам» в правой половине PnL
   на 1280 не влезали на 29 px. */
.table-compact th, .table-compact td { padding-right: 0.625rem; padding-left: 0.625rem; }
@media (max-width: 767px) {
  .fbs-axis, .fbs-plot { height: 130px; }
  .fbs-speed-bar { max-width: 96px; }
  /* Десять подписей на телефоне сливаются в «20.0823.0826.08»: показываем
     каждую вторую из них, то есть впятеро реже, чем дней. */
  .fbs-day-alt { visibility: hidden; }
  /* Тап-таргет от 44 px (правило 17): подсказка и раскрытие хронологии. */
  .kpi-help > summary { width: 44px; height: 44px; }
  .fbs-more-btn { display: inline-flex; min-height: 44px; align-items: center; }
  /* «Показать в списке» — самостоятельная ссылка-действие, её и жмут пальцем.
     Ссылки ВНУТРИ предложений (в плитке, в плашке, в подвале карточки) остаются
     исключением по правилу 13: иначе текст порвётся на куски. */
  .fbs-issue a { display: inline-flex; min-height: 44px; align-items: center; }
}
/* === Склад, чат 4 === */
/* Чек-лист готовности поставки площадки: три строки, состояние точкой и словами.
   Точка вместо символа галочки: не зависит от набора глифов шрифта, а системный
   шрифт вперемешку с нашим владелец читает как «кривой сервис». */
.postavki-check { display: grid; gap: 0.25rem; margin: 0; padding: 0; list-style: none;
  font-size: var(--mzone-font-size-sm); line-height: 1.35; }
.postavki-check li { display: flex; min-width: 0; align-items: flex-start; gap: 0.5rem;
  color: var(--mzone-text-muted); }
.postavki-check li > span:last-child { min-width: 0; overflow-wrap: anywhere; }
.postavki-mark { flex: 0 0 8px; width: 8px; height: 8px; margin-top: 0.45em; border-radius: 50%;
  background: var(--mzone-border); }
.postavki-check li.is-ok { color: var(--mzone-text); }
.postavki-check li.is-ok .postavki-mark { background: var(--mzone-success); }
.postavki-check li.is-bad { color: var(--mzone-danger); }
.postavki-check li.is-bad .postavki-mark { background: var(--mzone-danger); }
.postavki-check li.is-unknown .postavki-mark { background: var(--mzone-warning); }
/* Колонка чек-листа переносит текст и не раздувает таблицу до прокрутки на
   каждом экране: ей не больше 260 px. */
.postavki-ready { min-width: 165px; max-width: 200px; white-space: normal; }
/* Колонка документа: селект «Связать» тянется по самому длинному пункту и
   выталкивал «Готовность» за правый край. Ширину задаём мы, не содержимое. */
.postavki-doc { min-width: 150px; max-width: 175px; white-space: normal; }
.postavki-pick { width: 100%; max-width: 150px; }
/* Отгружено, принято, не доехало — одной ячейкой столбиком. Тремя колонками
   три коротких числа занимали треть ширины таблицы. */
.postavki-qty { min-width: 118px; max-width: 150px; text-align: right; white-space: normal;
  font-size: var(--mzone-font-size-sm); line-height: 1.4; }
.postavki-qty b { font-size: var(--mzone-font-size-base); }
/* Лента поставок: раскладка фиксированная, доли колонок заданы нами. При
   обычной (авто) таблица растёт по содержимому: перечисление штрихкодов и
   селект «Связать» уводили «Готовность» с кнопками за правый край. */
.postavki-table { table-layout: fixed; }
/* Перенос по словам, а не по буквам: «Кабинет привязан» рвалось на «Каби
   прив н к клие». Длинные штрихкоды из ячеек ушли в подсказку. */
/* Общая таблица обрезает ячейку многоточием (`.table td`), а здесь содержимое
   переносится: обрезка съедала конец статуса, названия склада и селекта. */
.postavki-table th, .postavki-table td { overflow: visible; text-overflow: clip;
  white-space: normal; overflow-wrap: normal; }
/* Длинный статус («Не запланирована», «Отгрузка разрешена») переносится, а не
   обрезается по краю узкой колонки. Флекс строки не переносит, поэтому здесь
   это обычный строчный блок. */
.postavki-table .status { display: inline-block; height: auto; padding: 2px 0.5rem;
  line-height: 1.3; white-space: normal; }
.postavki-table th:nth-child(1), .postavki-table td:nth-child(1) { width: 19%; }
.postavki-table th:nth-child(2), .postavki-table td:nth-child(2) { width: 13%; }
.postavki-table th:nth-child(3), .postavki-table td:nth-child(3) { width: 16%; }
.postavki-table th:nth-child(4), .postavki-table td:nth-child(4) { width: 15%; }
.postavki-table th:nth-child(5), .postavki-table td:nth-child(5) { width: 17%; }
.postavki-table th:nth-child(6), .postavki-table td:nth-child(6) { width: 20%; }
/* Селект «Связать» тянется по самому длинному пункту: ширину задаёт колонка. */
.postavki-pick { width: 100%; max-width: 100%; }
/* Отгружено, принято, не доехало — одной ячейкой столбиком. Тремя колонками
   три коротких числа занимали треть ширины таблицы. */
.postavki-qty { text-align: right; font-size: var(--mzone-font-size-sm); line-height: 1.4; }
.postavki-qty b { font-size: var(--mzone-font-size-base); }
/* Таблица позиций формы отгрузки: раскладка фиксированная, доли наши. Длинные
   названия товаров растягивали таблицу, и «Доступно» с полем ввода уезжали за
   правый край. Ячейки переносят текст и не обрезаются многоточием. */
.ship-table { table-layout: fixed; }
.ship-table th, .ship-table td { overflow: visible; text-overflow: clip; white-space: normal; }
.ship-c-good { width: 30%; }
.ship-c-code { width: 15%; }
.ship-c-have { width: 15%; }
.ship-c-res  { width: 9%; }
.ship-c-free { width: 11%; }
.ship-c-take { width: 16%; }
.ship-c-left { width: 13%; }
/* Форма отгрузки: «больше, чем доступно» словами, не запретом. */
.ship-warn { display: none; margin-top: 0.25rem; color: var(--mzone-danger);
  font-size: var(--mzone-font-size-xs); white-space: nowrap; }
.ship-warn.is-on { display: block; }

/* === Склад, чат 1 === */
/* Экран «Сегодня» (/sklad/segodnya): плитки-ссылки, список дел, неделя.
   Общие классы (.kpi-card, .card, .table, .btn) не трогаем — здесь только то,
   чего в наборе не было. */
a.kpi-card, a.kpi-card:hover { color: inherit; text-decoration: none; }
a.kpi-card:hover { border-color: var(--mzone-teal-600); }
/* Иконки плиток «Сегодня» из своего набора (правило 16 стайлгайда): пара
   светлое/тёмное, потому что масса и вырезы на тёмной теме меняются местами. */
.kpi-icon[data-icon="warehouse"] { --kpi-icon: url("/static/icons/warehouse.svg"); }
.kpi-icon[data-icon="fbs"] { --kpi-icon: url("/static/icons/fbs-assembly.svg"); }
.kpi-icon[data-icon="docs"] { --kpi-icon: url("/static/icons/day-summary.svg"); }
.kpi-icon[data-icon="invoice"] { --kpi-icon: url("/static/icons/marketplace-report.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="warehouse"] { --kpi-icon: url("/static/icons/warehouse-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="fbs"] { --kpi-icon: url("/static/icons/fbs-assembly-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="docs"] { --kpi-icon: url("/static/icons/day-summary-inverse.svg"); }
:root[data-theme="dark"] .kpi-icon[data-icon="invoice"] { --kpi-icon: url("/static/icons/marketplace-report-inverse.svg"); }
/* Список дел: слова и число слева, кнопка справа; на узком экране кнопка
   переносится под текст (строка с переменным числом элементов обязана
   переноситься, правило 16 обхода 18.09). */
.today-list { display: grid; padding-bottom: var(--mzone-space-2); }
.today-row { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--mzone-space-2) var(--mzone-space-4); padding: var(--mzone-space-3) 1.125rem;
  border-top: 1px solid var(--mzone-border-soft); }
.today-main { min-width: 0; flex: 1 1 320px; }
.today-title { color: var(--mzone-text); font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
.today-count { margin-left: 0.25rem; color: var(--mzone-teal-700); font-variant-numeric: tabular-nums; white-space: nowrap; }
.today-note { color: var(--mzone-danger); font-size: var(--mzone-font-size-sm); font-weight: 600; }
.today-why { margin-top: 2px; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); line-height: 1.4; }
.today-go { display: flex; flex: 0 0 auto; flex-wrap: wrap; gap: var(--mzone-space-2); }
.today-empty { margin: 0; color: var(--mzone-text-muted); }
/* Неделя: семь клеток, сегодня обведён. Сетка классом, не инлайном (правило 7). */
.week-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--mzone-space-2); }
.week-day { min-width: 0; padding: var(--mzone-space-3); border: 1px solid var(--mzone-border-soft);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface-soft); }
.week-day.is-today { background: var(--mzone-surface); box-shadow: inset 0 0 0 2px var(--mzone-teal-600); }
/* Подпись дня переносится, а не режется: «Пн 21.09» плюс метка «сегодня» в семи
   колонках не влезали в строку, и у сегодняшнего дня подпись обрывалась на
   «СЕГОД» (снимок 1280 px, 21.09.2026). */
.week-date { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase; overflow-wrap: anywhere; }
.week-now { display: block; color: var(--mzone-teal-700); }
/* На тёмной теме бирюза `teal-700` даёт 2.98 к фону карточки — ниже нормы WCAG
   AA. Токены выверены для СВЕТЛОЙ темы (правило 14 стайлгайда), поэтому на
   тёмной берём соседний, более светлый: `teal-600` даёт 5.45. Замер 21.09.2026
   на /sklad/segodnya. */
:root[data-theme="dark"] .today-count,
:root[data-theme="dark"] .week-now,
:root[data-theme="dark"] .week-day.is-today .week-date { color: var(--mzone-teal-600); }
.week-day.is-today .week-date { color: var(--mzone-teal-700); }
.week-count { margin-top: var(--mzone-space-1); color: var(--mzone-text); font-size: var(--mzone-font-size-sm); font-weight: 600; font-variant-numeric: tabular-nums; }
.week-doc { display: block; margin-top: 4px; padding: 4px 6px; border-left: 3px solid var(--mzone-teal-600);
  border-radius: var(--mzone-radius-sm); background: var(--mzone-surface); color: var(--mzone-text);
  font-size: var(--mzone-font-size-xs); font-weight: 600; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.week-doc:hover { background: var(--mzone-surface-muted); }
.week-doc.is-draft { border-left-color: var(--mzone-warning); }
.week-more { display: block; margin-top: 4px; color: var(--mzone-text-muted); font-size: var(--mzone-font-size-xs); }
@media (max-width: 1100px) { .week-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 767px) {
  .week-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Тап-таргет от 44 px (правило 17): пальцем жмут кнопки дел, две кнопки
     шапки и чип документа в клетке дня. Чип — самостоятельная ссылка-действие,
     а не ссылка внутри предложения, поэтому исключение правила 13 на него не
     распространяется. Кнопки шапки поднимаем ТОЛЬКО на этом экране: общий
     `.btn` держит и десктоп, где компактная кнопка удобнее. */
  .today-go .btn, .segodnya-actions .btn { min-height: 44px; }
  .today-go { width: 100%; }
  .week-doc { display: flex; min-height: 44px; align-items: center; }
}
@media (max-width: 479px) { .week-grid { grid-template-columns: minmax(0, 1fr); } .week-day.is-empty { padding: var(--mzone-space-2) var(--mzone-space-3); } }
/* === Склад, чат 2 =========================================================
   Карточка документа одна на все виды: полоска шагов приёмки, строка
   «Следующий шаг», история «кто и когда», меню «Создать документ» в списке.
   Программа plans/2026-09-18-sklad-programma-pyat-chatov.md, правило 3. */

/* Шапка карточки: состояние, клиент, даты, кто создал — в одну строку с
   переносом. Разделитель рисуем между текстовыми кусками, а не после бейджей
   состояния: точка после пилюли читается как опечатка. */
.doc-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.doc-meta > span { min-width: 0; }
.doc-meta > span:not(.status) + span:not(.status)::before {
  margin-right: 0.5rem; color: var(--mzone-text-soft); content: "·"; }

/* Три шага приёмки. Сетка классом (правило 7 стайлгайда): на телефоне шаги
   встают в столбик сами, без инлайновых grid-template-columns. */
.doc-steps { display: grid; margin: 0; padding: 0; gap: var(--mzone-space-3);
  grid-template-columns: repeat(3, minmax(0, 1fr)); list-style: none; }
.doc-step { display: flex; min-width: 0; gap: var(--mzone-space-3);
  padding: var(--mzone-space-3); border: 1px solid var(--mzone-border-soft);
  border-radius: var(--mzone-radius-md); background: var(--mzone-surface-soft); }
.doc-step-no { display: inline-flex; width: 26px; height: 26px; flex: 0 0 26px;
  align-items: center; justify-content: center; border: 1px solid var(--mzone-border);
  border-radius: 50%; background: var(--mzone-surface); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-sm); font-weight: 600; font-variant-numeric: tabular-nums; }
.doc-step-body { display: flex; min-width: 0; flex-direction: column; gap: 4px; }
.doc-step-title { color: var(--mzone-text); font-weight: 600; }
.doc-step-state { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm);
  overflow-wrap: anywhere; }
.doc-step-actions { display: flex; flex-wrap: wrap; gap: var(--mzone-space-2); margin-top: 4px; }
/* Модификаторы — ПОСЛЕ базовых правил (правило 8 стайлгайда). */
.doc-step.is-done { border-color: var(--mzone-success); background: var(--mzone-success-bg); }
.doc-step.is-done .doc-step-no { border-color: var(--mzone-success); color: var(--mzone-success); }
.doc-step.is-current { border-color: var(--mzone-teal-600); background: var(--mzone-surface); }
.doc-step.is-current .doc-step-no { border-color: var(--mzone-teal-600); color: var(--mzone-teal-700); }

/* «Следующий шаг: …» — одно действие, а не список возможностей. */
.doc-next { margin: var(--mzone-space-3) 0 0; padding: var(--mzone-space-3);
  border-left: 3px solid var(--mzone-teal-600); border-radius: var(--mzone-radius-sm);
  background: var(--mzone-surface-soft); color: var(--mzone-text); }
.doc-next a { color: var(--mzone-teal-700); font-weight: 550; }

/* История: время слева узкой колонкой, событие справа. Колонка времени
   табличными цифрами — иначе даты не выравниваются по вертикали. */
.doc-history { display: grid; margin: 0; padding: 0; gap: var(--mzone-space-2); list-style: none; }
.doc-history-item { display: grid; align-items: baseline; gap: var(--mzone-space-2) var(--mzone-space-4);
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }
.doc-history-when { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.doc-history-what { min-width: 0; overflow-wrap: anywhere; }
.doc-history-item.is-warn .doc-history-what { color: var(--mzone-warning); }

/* «Создать документ»: одна кнопка с выбором вида. details/summary — раскрытие
   без JS, как у подсказок плиток. */
.create-menu { position: relative; }
.create-menu > summary { list-style: none; cursor: pointer; }
.create-menu > summary::-webkit-details-marker { display: none; }
.create-menu-body { position: absolute; z-index: 30; top: calc(100% + var(--mzone-space-2)); right: 0;
  display: flex; min-width: 15rem; flex-direction: column; padding: var(--mzone-space-2);
  border: 1px solid var(--mzone-border); border-radius: var(--mzone-radius-md);
  background: var(--mzone-surface); box-shadow: var(--mzone-shadow-float); }
.create-menu-item { padding: 0.5rem 0.625rem; border-radius: var(--mzone-radius-sm);
  color: var(--mzone-text); font-size: var(--mzone-font-size-md); text-decoration: none; }
.create-menu-item:hover { background: var(--mzone-surface-soft); color: var(--mzone-teal-700); }
/* Кнопки в том же меню: у <button> свои умолчания браузера, поэтому сбрасываем
   их до вида пункта списка. */
button.create-menu-item { width: 100%; border: 0; background: none; cursor: pointer;
  font: inherit; text-align: left; }
.create-menu-danger { color: var(--mzone-danger); }
.create-menu-danger:hover { background: var(--mzone-danger-soft, var(--mzone-surface-soft));
  color: var(--mzone-danger); }
/* Меню действий живёт в липкой панели выбора у нижнего края: раскрывается
   ВВЕРХ, иначе список действий уезжает за экран. */
.bulk-menu > .create-menu-body { top: auto; bottom: calc(100% + var(--mzone-space-2)); }

/* Строка списка — вход в документ целиком (владелец 21.09.2026). Курсор и
   подсветка говорят об этом до клика; сам переход делает mzone-ui.js. */
.row-link { cursor: pointer; }
.row-link:hover { background: var(--mzone-surface-soft); }

/* Кнопки строки списка: переносятся, а не тянут таблицу вбок. Неразрывный ряд
   из трёх кнопок делал таблицу шире экрана даже на 1280 px, и главное действие
   пряталось за горизонтальной прокруткой (правило 16 стайлгайда: строка с
   переменным числом элементов обязана переноситься). */
.doc-actions { display: flex; max-width: 13rem; flex-wrap: wrap; gap: 4px;
  align-items: flex-start; }
.doc-actions form { display: contents; }

@media (max-width: 767px) {
  /* Шаги в столбик: три колонки на 390 px нечитаемы. */
  .doc-steps { grid-template-columns: minmax(0, 1fr); }
  /* Время истории — своей строкой над событием: две колонки на телефоне
     оставляют событию четверть ширины. */
  .doc-history-item { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  /* Тап-таргет от 44 px (правило 17 стайлгайда). */
  .create-menu-item { display: flex; min-height: 44px; align-items: center; }
  .doc-next a { display: inline-flex; min-height: 44px; align-items: center; }
}

/* === Склад, чат 3 === */
/* Остатки: три числа строки, дальше группа «где лежит». Группа выделена
   рамкой, а не вторым рядом заголовков: число ячеек шапки обязано совпадать с
   числом ячеек строки и «Итого» (сторож tests/web/test_boxes_web.py), иначе
   итог встаёт под чужим столбцом и врёт молча. */
/* Класс таблицы .stock-matrix, а не .stock-places: то имя уже занято
   раскладкой по складам на /stock (display: flex), и таблица остатков склада
   становилась флексом. Шапка стояла сама по себе, строки сами по себе, и
   заголовки не попадали на свои колонки ни на одной ширине (обход 24.09.2026). */
.stock-matrix th.th-place, .stock-matrix td.th-place { background: var(--mzone-surface-soft); }
.stock-matrix .place-first { border-left: 1px solid var(--mzone-border); }
.stock-matrix .place-last { border-right: 1px solid var(--mzone-border); }
.stock-matrix thead th.th-place { white-space: normal; min-width: 92px; }
/* От планшета раскладка фиксированная: товар около пятой части ширины, числа
   делят остальное поровну. Заголовки переносятся по словам. На 768 и 1024 под
   таблицу остаётся одинаково ~720 px, поэтому порог 768, а не 1024. Больше
   десяти колонок (второй склад, площадка, «едет на МП») в долях не читаются:
   тогда таблица получает минимум и прокручивается внутри обёртки. */
@media (min-width: 768px) {
  .stock-matrix { table-layout: fixed; }
  .stock-matrix th, .stock-matrix td { padding-right: 0.5rem; padding-left: 0.5rem;
    overflow: visible; text-overflow: clip; white-space: normal; }
  .stock-matrix th:first-child, .stock-matrix td:first-child { width: 22%; padding-left: 1rem; }
  .stock-matrix:has(> thead > tr > th:nth-child(11)) { min-width: 68rem; }
}
/* Штрихкод своей колонкой только от 1280. Ниже он строкой под товаром: на
   ~720 px планшета колонка штрихкода отнимала у шести чисел по 20 px, и
   заголовки делились по слогам («Ожи-дается», «Хране-ние», «пло-щадки»). */
.stock-matrix .stock-code-under { display: none; }
@media (max-width: 1279px) {
  .stock-matrix .stock-code { display: none; }
  .stock-matrix .stock-code-under { display: block; }
}
@media (min-width: 1280px) {
  .stock-matrix th:first-child, .stock-matrix td:first-child { width: 23%; }
  /* Штрихкод площадки в 13 цифр (EAN-13) занимает 126 px: колонке 9rem. Код
     длиннее колонки переносится, а не налезает на соседнее число. */
  .stock-matrix th.stock-code { width: 9rem; }
  .stock-matrix td.stock-code { overflow-wrap: anywhere; }
}
/* Разбивка добора под кнопкой: строки короткие, выровнены под кнопку. */
.fix-breakdown { text-align: right; }
.fix-breakdown > div { white-space: nowrap; }
@media (max-width: 767px) {
  .fix-breakdown { text-align: left; }
}

/* === Склад, чат 5 === */
/* ─── Счета клиентам: путь счёта пятью шагами ────────────────────────────────
   Карточка счёта ведёт человека шагами (что учтено → строки → выставить →
   печать → оплачен), как карточка документа у эталона. Сделанный шаг помечен
   зелёным номером, текущий обведён бирюзой, будущий серый. Шаг это ссылка на
   свой блок ниже по странице; на телефоне полоска переносится в две строки. */
.schet-steps { display: flex; flex-wrap: wrap; gap: var(--mzone-space-2); margin: 0; padding: 0; list-style: none; }
.schet-steps > li { min-width: 0; }
.schet-step { display: inline-flex; min-height: 36px; align-items: center; gap: 0.5rem; padding: 0.25rem 0.75rem 0.25rem 0.375rem;
  border: 1px solid var(--mzone-border-soft); border-radius: var(--mzone-radius-pill); color: var(--mzone-text-muted);
  font-size: var(--mzone-font-size-sm); text-decoration: none; }
.schet-step:hover { color: var(--mzone-text); }
.schet-step-no { display: inline-flex; width: 24px; height: 24px; flex: 0 0 24px; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--mzone-surface-soft); font-weight: 600; font-variant-numeric: tabular-nums; }
.schet-step.is-done { color: var(--mzone-text); }
.schet-step.is-done .schet-step-no { background: var(--mzone-success-bg); color: var(--mzone-success); }
.schet-step.is-current { border-color: var(--mzone-teal-600); color: var(--mzone-text); font-weight: 600; }
.schet-step.is-current .schet-step-no { background: var(--mzone-teal-600); color: var(--mzone-text-inverse); }
.schet-next { margin: var(--mzone-space-3) 0 0; }
/* Пояснение «откуда цифра» у строки бывает длинным (хранение по суткам): пусть
   переносится, а не растягивает таблицу на всю ширину экрана. */
.schet-detail { max-width: 34ch; white-space: normal; }
.schet-total { font-size: var(--mzone-font-size-lg); }
.schet-blockers { margin: 0; padding-left: 1.1rem; }
@media (max-width: 767px) {
  /* Тап-таргет от 44 px (правило 17): шаг это ссылка, по ней жмут пальцем. */
  .schet-step { min-height: 44px; }
}
/* ─── Счета клиентам: клетка «клиент × месяц» ────────────────────────────────
   В клетке три строки: состояние словом, сумма, приписка. Ссылкой сделана вся
   клетка, а не одно слово: по ней открывают счёт, и целиться в слово «оплачен»
   пальцем неудобно.

   Ширины мерены, а не подобраны на глаз (память mzone-table-ellipsis-trap).
   Семь колонок (клиент и шесть месяцев) читаются только от 1280 px, где под
   таблицу остаётся 926: тогда раскладка фиксированная, доли в процентах, и
   правые месяцы никуда не уезжают. Ниже 1280 доли дают по сто пикселей на
   месяц, и слова рвутся по буквам («Не выста влен»), поэтому там таблица
   растёт по содержимому и прокручивается внутри своей обёртки. Страница вбок
   не едет ни на одной ширине. Месяцы идут от свежего к старому, и самые нужные
   два видно без прокрутки. */
.schet-table { min-width: 1100px; }
.schet-table th, .schet-table td { overflow: visible; text-overflow: clip;
  white-space: normal; }
.schet-table td:first-child, .schet-table th:first-child { min-width: 8rem; }
.schet-cell { vertical-align: top; white-space: normal; }
.schet-cell .status { white-space: normal; }
.schet-cell-link { display: grid; min-width: 8.5rem; gap: 0.25rem;
  justify-items: start; color: inherit; text-decoration: none; }
.schet-cell-link:hover .schet-cell-sum { text-decoration: underline; }
.schet-cell-sum { font-weight: 600; font-variant-numeric: tabular-nums; }
@media (min-width: 1280px) {
  .schet-table { min-width: 0; table-layout: fixed; }
  .schet-table th:first-child, .schet-table td:first-child { width: 16%; }
  .schet-table th, .schet-table td { width: 14%; }
  .schet-cell-link { min-width: 0; }
  /* Поля клетки уже: пилюля «Не выставлен» (107 px) не помещалась в 14% с
     полями по 1rem и вылезала на границу клетки (обход 24.09.2026). */
  .schet-table th, .schet-table td { padding-right: 0.5rem; padding-left: 0.5rem; }
  .schet-table th:first-child, .schet-table td:first-child { padding-left: 1rem; }
}

/* === Склад, сведение 21.09.2026 ===
   Кнопки в ячейках ленты поставок: раскладка таблицы фиксированная, и кнопка с
   nowrap («Создать отгрузку», 157 px) вылезала из ячейки в 122 px на соседний
   чек-лист при 768 px. Надпись переносится внутри ячейки, кнопка не выше
   содержимого. */
.postavki-table .btn { white-space: normal; max-width: 100%; height: auto;
  line-height: 1.3; padding-top: 6px; padding-bottom: 6px; text-align: center; }

/* === Фулфилмент: строка обработки товара сохраняется сама (22.09.2026) ======
   Владелец: «после нажатия кнопки сохранить всё слетает, и фильтр, и все
   остальные поля». Строка пишется в базу сама, кнопка остаётся только для
   браузера без JS, а рядом коротко мигает слово о том, что сохранилось. */
.check-inline { display: inline-flex; min-height: 40px; align-items: center; gap: 0.5rem;
  white-space: nowrap; }
.check-inline input { width: 18px; height: 18px; accent-color: var(--mzone-teal-600); }
.js-inline-save .packing-save { display: none; }
.packing-flash { color: var(--mzone-text-muted); font-size: var(--mzone-font-size-sm); }
.packing-flash.is-ok { color: var(--mzone-success); }
.packing-flash.is-bad { color: var(--mzone-danger); }
/* Поле, которое сейчас пишется, и поле с ошибкой — видно не читая подписи. */
.packing-price.is-saving { border-color: var(--mzone-teal-600); }
.packing-price.is-bad { border-color: var(--mzone-danger); }

/* === Таблица товаров в счёте, 23.09.2026 ===
   Семь колонок: товар, штуки и две пары «цена + сумма» (упаковка и сборка FBS).
   Без своей ширины браузер отдавал названию товара три сантиметра и переносил
   его по одному слову, а «Итого» уезжало за правый край: справа обрезано как
   раз то, ради чего счёт открывают. Ниже 1100 px честная горизонтальная
   прокрутка вместо сжатия, выше — доли, где имя товара шире любой цифры. */
/* Неразрывны только СУММЫ в ячейках. Правило на `.table-numeric` било и по
   заголовкам: «Сборка и отгрузка, ₽/шт» в одну строку налезал на «Сборка,
   сумма» на 1440 и 1280 (обход 24.09.2026). Заголовок переносится по словам. */
.schet-goods td.table-numeric { white-space: nowrap; }
.schet-goods th { white-space: normal; }
.schet-goods td:first-child { white-space: normal; }
/* От 768 доли: на 768 и 1024 под таблицу ~720 px, и семи колонкам их хватает,
   если поля ячеек уже. Сумма «12 345,00 ₽» занимает 91 px, жирный итог 95:
   колонкам сумм дано 15%, итогу 17%. Цена за штуку не бывает пятизначной,
   ей хватает 12%. На телефоне цены за штуку и две промежуточные суммы убраны
   (.col-minor), итог строки остаётся на экране. */
@media (min-width: 768px) {
  .schet-goods { table-layout: fixed; }
  .schet-goods th, .schet-goods td { padding-right: 0.5rem; padding-left: 0.5rem;
    overflow: visible; text-overflow: clip; }
  .schet-goods th:first-child, .schet-goods td:first-child { width: 15.5%; padding-left: 1rem; }
  .schet-goods th:nth-child(2) { width: 13.5%; }
  .schet-goods th:nth-child(3), .schet-goods th:nth-child(5) { width: 12%; }
  .schet-goods th:nth-child(4), .schet-goods th:nth-child(6) { width: 15%; }
  .schet-goods th:nth-child(7) { width: 17%; }
}
/* Номер шага в заголовке карточки счёта тем же кружком, что в полоске шагов.
   Символы ①–⑤ в Inter нет, и браузер рисовал их чужим шрифтом мельче текста. */
.schet-step-title { display: flex; align-items: center; gap: 0.5rem; }

/* === Склад, сборка FBS, фулфилмент: обход вёрстки 24.09.2026 ================
   Здесь только правила страниц этих разделов и новые классы. Общие .table,
   .btn, .input, .status и общие медиазапросы не трогаем.

   Все рабочие таблицы разделов считают ширину одинаково: на 768 и на 1024
   под таблицу остаётся ~720 px (на 1024 уже открыто меню), поэтому доли
   колонок включаются от 768. Ячейки переносят текст и не режутся многоточием:
   у общей .table обрезка и рост по содержимому, из-за них правые колонки с
   кнопками уезжали за край (память mzone-table-ellipsis-trap). */

/* Внутренняя ссылка бирюзой. Пурпур WB у нас значит «откроется карточка на
   Wildberries», а ссылка на свой счёт или клиента этим цветом обещала уход на
   площадку. Голая <a> в тексте не отличалась от соседнего слова вовсе. На
   тёмной теме teal-700 даёт 2.98 к фону, поэтому там teal-600 (как у
   .today-count). */
.link { color: var(--mzone-teal-700); font-weight: 550; text-decoration: none; }
.link:hover { text-decoration: underline; }
:root[data-theme="dark"] .link { color: var(--mzone-teal-600); }

/* Утилиты, которые шаблоны звали, а в наборе их не было. */
.items-end { align-items: flex-end !important; }
.pt-0 { padding-top: 0 !important; }
.input.input-sm { padding: 0.3125rem 0.75rem; }
.table tr.table-total > td { border-top: 1px solid var(--mzone-border); background: var(--mzone-surface-soft); }
/* Второстепенная колонка рабочей таблицы: на телефоне её нет, чтобы поле ввода
   и кнопка стояли сразу за товаром, а не за правым краем экрана. */
.phone-only { display: none; }
@media (max-width: 767px) {
  .col-minor { display: none !important; }
  .phone-only { display: inline; }
}

/* Иконки пустых состояний, которые шаблоны звали, а CSS не описывал: вместо
   них рисовалась общая «сводка дня». Пара светлая/тёмная (правило 16). */
.card-empty[data-icon="box"] { --empty-icon: url("/static/icons/own-warehouse.svg"); }
.card-empty[data-icon="check"] { --empty-icon: url("/static/icons/buyout.svg"); }
.card-empty[data-icon="fbs-assembly"] { --empty-icon: url("/static/icons/fbs-assembly.svg"); }
.card-empty[data-icon="settings"] { --empty-icon: url("/static/icons/settings.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="box"] { --empty-icon: url("/static/icons/own-warehouse-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="check"] { --empty-icon: url("/static/icons/buyout-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="fbs-assembly"] { --empty-icon: url("/static/icons/fbs-assembly-inverse.svg"); }
:root[data-theme="dark"] .card-empty[data-icon="settings"] { --empty-icon: url("/static/icons/settings-inverse.svg"); }

/* Нативный fieldset рисовал браузерную рамку «канавкой» посреди формы. */
.sklad-fieldset { min-width: 0; margin: 0 0 var(--mzone-space-4); padding: 0; border: 0; }
.sklad-fieldset > legend { padding: 0; }

/* Кнопка формы в ячейке сетки не тянется на всю колонку. */
.field-action { align-items: flex-start; }
/* Группа кнопок в подвале карточки: подвал сам даёт отступы, свой верхний
   отступ группы сдвигал кнопки ниже подписи рядом. */
.card-footer > .form-actions { margin-top: 0; }

/* Поля ввода в строках таблиц: ширина классом, а не инлайном (правило 2). */
.ff-price-input { max-width: 110px; }
.ff-storage-input { max-width: 10rem; }
.labels-table .lbl-qty { max-width: 6rem; }
.count-table .cnt-fact { max-width: 110px; }
.fbs-search .input { max-width: 260px; }

/* Пары «подпись и значение» с длинным значением: на телефоне значение встаёт
   под подпись, иначе слово подписи вылезало на текст значения. */
@media (max-width: 767px) {
  .sklad-facts .metric-row { flex-direction: column; align-items: flex-start; gap: 2px; }
  .sklad-facts .metric-value { text-align: left; }
}

/* Кабинеты в карточке клиента: строка на кабинет, поле ИНН с кнопкой одной
   строкой. Таблицей в узкой колонке сетки поле сжималось до 46 px, а «Ок»
   падало под него. */
.ff-cabs { display: grid; margin: 0; padding: 0; gap: var(--mzone-space-3); list-style: none; }
.ff-cab { display: flex; flex-wrap: wrap; align-items: center; gap: var(--mzone-space-2);
  padding-bottom: var(--mzone-space-3); border-bottom: 1px solid var(--mzone-border-soft); }
.ff-cab:last-child { padding-bottom: 0; border-bottom: 0; }
.ff-cab-name { flex: 1 1 100%; min-width: 0; }
.ff-cab-inn .input { flex: 0 1 9.5rem; width: 9.5rem; padding-right: 0.75rem; padding-left: 0.75rem; }

/* ── Рабочие таблицы: общее для всех ниже ── */
.work-table th, .work-table td { overflow: visible; text-overflow: clip; white-space: normal; }
/* Пилюля статуса не шире колонки: длинное слово («Запланирована»,
   «Инвентаризация») делится по слогам, а не вылезает на соседнюю колонку.
   Доли колонок подобраны так, чтобы обычные статусы помещались целиком. */
.work-table .status { max-width: 100%; white-space: normal; -webkit-hyphens: auto; hyphens: auto; line-height: 1.25; }
.work-table .product-title { white-space: normal; overflow-wrap: anywhere; -webkit-hyphens: auto; hyphens: auto; }
/* Переносом по слогам делится только длинное слово (от десяти букв), и не
   ближе четырёх букв от края: «ши-рокие мо-дель» в названии товара читался
   рвано, а «водоотталкивающая» без переноса не помещалась в колонку. */
.work-table td, .work-table .status, .work-table .product-title {
  -webkit-hyphenate-limit-before: 4; -webkit-hyphenate-limit-after: 4; hyphenate-limit-chars: 10 4 4; }
/* На телефоне колонка уже в два раза: делим и слова покороче. */
@media (max-width: 767px) {
  .work-table td, .work-table .status, .work-table .product-title {
    -webkit-hyphenate-limit-before: 3; -webkit-hyphenate-limit-after: 3; hyphenate-limit-chars: 6 3 3; }
}
.work-table .product-meta { overflow: visible; white-space: normal; }
/* Кнопка в ячейке переносит надпись, а не тянет колонку («Этикетка Ozon»). */
.work-table td .btn { max-width: 100%; height: auto; padding-top: 0.3125rem; padding-bottom: 0.3125rem;
  white-space: normal; line-height: 1.3; text-align: center; }
.work-table td > .row-actions form, .work-table .doc-actions form { display: contents; }
/* Узкие поля ячеек: так в ~720 px помещаются семь-девять колонок. Длинное
   слово заголовка («Ожидается», «Маркировка») переносится по слогам, а не
   вылезает на соседа.

   Доли заданы ВСЕМ колонкам и в сумме дают 100%: колонка без своей ширины при
   фиксированной раскладке забирает остаток или сжимается до двух пикселей, как
   было с «Документом». Строка-черта по дням (ui.day_row) до 24.09.2026 стояла
   с colspan="99" и сама добавляла таблице такие колонки; теперь шаблон
   передаёт ей точное число колонок. */
@media (min-width: 768px) {
  .work-table { table-layout: fixed; }
  .work-table th, .work-table td { padding-right: 0.5rem; padding-left: 0.5rem; }
  .work-table th:first-child, .work-table td:first-child { padding-left: 1rem; }
  .work-table th:last-child, .work-table td:last-child { padding-right: 1rem; }
  .work-table th, .stock-matrix th, .schet-goods th { -webkit-hyphens: auto; hyphens: auto; }
  /* В шапке делится только длинное слово: иначе браузер добивал строку
     переносом («Сборка и от-грузка»). Короткие слова шапки помещаются в свои
     колонки целиком, это держат доли ниже. */
  .work-table th, .schet-goods th {
    -webkit-hyphenate-limit-before: 4; -webkit-hyphenate-limit-after: 4; hyphenate-limit-chars: 10 4 4; }
  /* Длинное имя клиента или склада в узкой колонке тоже делится по слогам. */
  .work-table td { -webkit-hyphens: auto; hyphens: auto; }
  /* Колонка флажка узкая: полям ячейки хватает четверти рема справа. */
  .work-table th.fbs-pick-cell, .work-table td.fbs-pick-cell,
  .work-table th.bulk-pick-cell, .work-table td.bulk-pick-cell { padding-right: 0.25rem; padding-left: 0.75rem; }
}
/* Телефон: без общего минимума в 720 px, второстепенные колонки убраны классом
   .col-minor, поля ячеек уже, поле ввода не шире своей колонки. */
@media (max-width: 767px) {
  .work-table { min-width: 0 !important; }
  .work-table th, .work-table td { padding-right: 0.375rem; padding-left: 0.375rem; }
  .work-table th:first-child, .work-table td:first-child { padding-left: 0.75rem; }
  .work-table th:last-child, .work-table td:last-child { padding-right: 0.75rem; }
  .work-table td .input { width: 100%; max-width: 100% !important; min-width: 0; }
}

/* Документы склада: флажок, документ, клиент, позиции, состояние, план,
   действия. На телефоне остаются документ, позиции и кнопка: строка и так
   ведёт в карточку, где есть всё остальное, а невыполненные собраны сверху
   своей чертой. */
@media (min-width: 768px) {
  .doc-list .bulk-pick-cell { width: 6%; }
  .doc-list .dl-doc { width: 17%; }
  .doc-list .dl-client { width: 11%; }
  .doc-list .dl-pos { width: 11%; }
  .doc-list .dl-state { width: 21%; }
  .doc-list .dl-plan { width: 14%; }
  .doc-list .dl-act { width: 20%; }
}

/* Очередь сборки FBS: размер под товаром, Честный знак под статусом сборки.
   Штрихкод своей колонкой только от 1280: ниже он строкой под товаром, иначе
   семь колонок в ~720 px планшета не помещаются. Доли подобраны по самым
   широким пилюлям и кнопкам: «просрочено» 117 px, «в поставке» 108,
   «Этикетка Ozon» 136, заголовок «площадки» 68, баркод в 13 цифр с кнопкой
   копирования 156 (замер 24.09.2026). */
.fbs-queue .fq-mark { margin-top: 4px; }
.fbs-queue .fq-code-under { display: none; }
@media (max-width: 1279px) {
  .fbs-queue .fq-code { display: none; }
  .fbs-queue .fq-code-under { display: block; }
}
@media (min-width: 768px) and (max-width: 1279px) {
  .fbs-queue .fbs-pick-cell { width: 6%; }
  .fbs-queue .fq-due { width: 19%; }
  .fbs-queue .fq-good { width: 22%; }
  .fbs-queue .fq-mp { width: 12%; }
  .fbs-queue .fq-state { width: 18%; }
  .fbs-queue .fq-act { width: 23%; }
  .fbs-queue:not(:has(.fbs-pick-cell)) .fq-good { width: 28%; }
}
@media (min-width: 1280px) {
  .fbs-queue .fbs-pick-cell { width: 5%; }
  .fbs-queue .fq-due { width: 15%; }
  .fbs-queue .fq-good { width: 17%; }
  .fbs-queue .fq-code { width: 17%; }
  .fbs-queue .fq-mp { width: 11%; }
  .fbs-queue .fq-state { width: 15%; }
  .fbs-queue .fq-act { width: 20%; }
  .fbs-queue:not(:has(.fbs-pick-cell)) .fq-good { width: 22%; }
}
/* Телефон: задание карточкой, а не строкой таблицы. Товар во всю ширину,
   под ним срок и статус, ниже кнопки. Строкой из трёх колонок кнопки всё
   равно уезжали за экран: пилюля срока, товар со штрихкодом и «Этикетка
   Ozon» вместе шире 356 px. */
@media (max-width: 767px) {
  .fbs-queue, .fbs-queue tbody { display: block; }
  .fbs-queue thead { display: none; }
  .fbs-queue tr { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: "good good" "due state" "act act"; gap: 0.625rem 0.75rem;
    padding: 0.875rem 0.75rem; border-bottom: 1px solid var(--mzone-border-soft); }
  .fbs-queue tr:last-child { border-bottom: 0; }
  .fbs-queue td { display: block; padding: 0 !important; border: 0; }
  .fbs-queue td:only-child { grid-column: 1 / -1; }
  .fbs-queue td.fq-good { grid-area: good; }
  .fbs-queue td.fq-due { grid-area: due; }
  .fbs-queue td.fq-state { grid-area: state; }
  .fbs-queue td.fq-act { grid-area: act; }
}

/* Журнал склада: семь колонок, «кто» под временем, «откуда» под операцией.
   Доли только у ленты из семи и меньше колонок. Коробка, «стало» и «что
   произошло» добавляют до трёх колонок: тогда раскладка обычная, ячейки
   переносятся, а остаток прокручивается внутри обёртки. */
.journal-table th .jr-sub { display: block; margin-top: 2px; color: var(--mzone-text-muted); font-weight: 500; }
@media (max-width: 767px) {
  .journal-table .jr-date { display: none; }
}
@media (min-width: 768px) {
  .journal-table:has(> thead > tr > th:nth-child(8)) { table-layout: auto; }
  .journal-table .jr-when { width: 15%; }
  .journal-table .jr-op { width: 20%; }
  .journal-table .jr-good { width: 29%; }
  .journal-table .jr-zone { width: 13%; }
  /* «+1 234» при приёмке крупной партии: колонке штук 10%. */
  .journal-table .jr-qty { width: 10%; }
  .journal-table .jr-ref { width: 13%; }
  /* Все клиенты: колонка клиента берёт свою долю у товара. Товару остаётся
     18%: уже не влезает штрихкод площадки в 13 цифр под названием. */
  .journal-table .jr-client { width: 11%; }
  .journal-table:has(.jr-client) .jr-good { width: 18%; }
  /* Лишние колонки: доли снимаем, раскладка обычная. */
  .journal-table:has(> thead > tr > th:nth-child(8)) th { width: auto; }
}

/* Упаковка товаров: названия услуг в шапке переносятся по словам. Шапка
   была `th.table-numeric`, а у него nowrap: «Маркировка штрихкодом» одной
   строкой занимала 160 px над флажком в 18, и шесть колонок из двенадцати
   уезжали за край уже на 1440. Клиент переехал под название товара. */
.packing-table th { white-space: normal; }
.packing-table th.pk-svc { text-align: center !important; overflow-wrap: anywhere; }
.packing-table td.pk-svc { text-align: center !important; }
/* Поле своей цены не шире колонки: у .input-inline минимум 10rem, а колонке
   дано 7rem, и поле налезало на первый флажок. */
.packing-table .packing-price { width: 100%; min-width: 0; max-width: 6rem; }
/* «Всем N товарам списка»: элементы одной фразой слева, а не разнесены по
   краям ряда (у .toolbar space-between). */
.packing-bulk { justify-content: flex-start; }
.packing-bulk .input[name="price"] { min-width: 0; max-width: 7rem; }
@media (min-width: 768px) {
  .packing-table .pk-good { width: 22%; }
  .packing-table .pk-own { width: 7rem; }
  .packing-table .pk-cost { width: 16%; }
}
/* До 1440 на флажок остаётся 40–60 px, и название услуги рвалось по слогам в
   пять строк. Здесь шапку флажков ставим вертикально, как в таблицах-матрицах:
   колонка узкая, название читается одной строкой снизу вверх. */
@media (min-width: 768px) and (max-width: 1439px) {
  .packing-table th.pk-svc { padding: 0.5rem 0.25rem; vertical-align: bottom; }
  .packing-table th.pk-svc > span { display: inline-block; max-height: 11rem; overflow-wrap: normal;
    writing-mode: vertical-rl; transform: rotate(180deg); text-align: left; white-space: normal; }
  .packing-table td.pk-svc { padding-right: 0.25rem; padding-left: 0.25rem; }
}

/* Прайс услуг, свои цены клиента и справочник клиентов фулфилмента. */
@media (min-width: 768px) {
  .ff-price-table .fp-name { width: 36%; }
  .ff-price-table .fp-kind, .ff-price-table .fp-unit, .ff-price-table .fp-base { width: 18%; }
  .ff-price-table .fp-price { width: 16%; }
  .ff-price-table .fp-on { width: 12%; }
  .ff-dir-table .fd-name { width: 24%; }
  .ff-dir-table .fd-cabs { width: 20%; }
  .ff-dir-table .fd-pos { width: 11%; }
  .ff-dir-table .fd-total { width: 13%; }
  .ff-dir-table .fd-bill { width: 17%; }
  .ff-dir-table .fd-open { width: 15%; }
}

/* «Последние документы» на экране «Сегодня». */
@media (min-width: 768px) {
  .today-docs .td-no { width: 14%; }
  .today-docs .td-kind { width: 26%; }
  .today-docs .td-client { width: 22%; }
  .today-docs .td-state { width: 22%; }
  .today-docs .td-date { width: 16%; }
}

/* Листы подбора и поставки на вкладках сборки FBS. */
@media (min-width: 768px) {
  .fbs-picks .pl-no { width: 16%; }
  .fbs-picks .pl-state { width: 19%; }
  .fbs-picks .pl-type { width: 8%; }
  .fbs-picks .pl-done { width: 12%; }
  .fbs-picks .pl-miss { width: 13%; }
  .fbs-picks .pl-when { width: 16%; }
  .fbs-picks .pl-go { width: 16%; }
  .fbs-supplies .fs-name { width: 34%; }
  .fbs-supplies .fs-state { width: 16%; }
  .fbs-supplies .fs-count { width: 11%; }
  .fbs-supplies .fs-act { width: 39%; }
}

/* Контроль FBS, список «Где сейчас заказы». Строка хронологии под заказом
   (tr.fbs-detail, colspan="7") ширину колонок не трогает. На телефоне
   остаются заказ, этап и время до передачи. Поставке 12%: в 10% «не
   указана» рвалось без дефиса («указан» и «а» на разных строках), сумме 17%:
   «12 345,00 ₽» шире 15%. */
@media (min-width: 768px) {
  .fbs-orders .fo-good { width: 22%; }
  .fbs-orders .fo-created { width: 10%; }
  .fbs-orders .fo-stage { width: 15%; }
  .fbs-orders .fo-lead { width: 12%; }
  .fbs-orders .fo-office { width: 12%; }
  .fbs-orders .fo-supply { width: 12%; }
  .fbs-orders .fo-sum { width: 17%; }
}
/* Телефон: три колонки долями. Без них название товара рядом с картинкой
   сжималось до шести букв и артикул «ШАР0003» рвался посередине. */
@media (max-width: 767px) {
  .fbs-orders { table-layout: fixed; }
  .fbs-orders .fo-good { width: 46%; }
  .fbs-orders .fo-stage { width: 27%; }
  .fbs-orders .fo-lead { width: 27%; }
}
/* Номер поставки длиннее колонки переносится, а не налезает на сумму. */
.fbs-orders td.fo-supply, .fbs-orders td.fo-office { overflow-wrap: anywhere; }

/* Отбор на сборке: клиент, товар, штрихкод, площадки, числа, кнопка.
   Штрихкод площадки в 13 цифр (EAN-13) занимает 126 px: колонке 20%. В тестовых
   данных штрихкоды 11-значные, и в 17% живой штрихкод налезал на «Площадки».
   Колонке «Собрать» 11%: в 9% заголовок делился на «Со-брать». */
@media (min-width: 768px) {
  .sborka-table .sb-client { width: 13%; }
  .sborka-table .sb-good { width: 19%; }
  .sborka-table .sb-code { width: 20%; }
  .sborka-table .sb-mp { width: 12%; }
  .sborka-table .sb-qty { width: 11%; }
  .sborka-table .sb-have { width: 12%; }
  .sborka-table .sb-go { width: 13%; }
}
/* Код длиннее колонки переносится, а не налезает на соседа. Только при
   фиксированной раскладке: при обычной такой перенос сжал бы колонку. */
@media (min-width: 768px) {
  .sborka-table td.sb-code, .labels-table td.lb-code, .count-table td.ct-code,
  .ship-table td.ship-c-code { overflow-wrap: anywhere; }
}

/* Этикетки: флажок, товар, артикул, штрихкод, остаток, поле «Наклеек». */
.labels-table .lb-pick { width: 44px; }
@media (min-width: 768px) {
  .labels-table .lb-art { width: 15%; }
  .labels-table .lb-code { width: 20%; }
  .labels-table .lb-have { width: 11%; }
  .labels-table .lb-qty { width: 8rem; }
}

/* Инвентаризация: причина стоит под расхождением в той же ячейке. */
.count-table .cnt-reason { margin-top: 0.375rem; }
@media (max-width: 767px) {
  .count-table .ct-good { width: 38%; }
  .count-table .ct-fact { width: 27%; }
  .count-table .ct-diff { width: 35%; }
  .count-table .cnt-fact { padding-right: 0.5rem; padding-left: 0.5rem; }
}
@media (min-width: 768px) {
  .count-table .ct-code { width: 20%; }
  .count-table .ct-have { width: 11%; }
  .count-table .ct-fact { width: 8.5rem; }
  .count-table .ct-diff { width: 25%; }
}

/* Расхождения списания. Числовым колонкам 11%: в 9% заголовки «Списано» и
   «Разница» делились по слогам. */
@media (min-width: 768px) {
  .wo-table .wo-client { width: 15%; }
  .wo-table .wo-num { width: 11%; }
  .wo-table .wo-why { width: 30%; }
}

/* Форма отгрузки: ширина поля классом. На телефоне колонки штрихкода, резерва
   и «останется» скрыты (.col-minor), поля ячеек уже, общего минимума нет. */
.ship-table .ship-qty { max-width: 90px; }
/* «Больше, чем доступно» шире колонки «Отгрузить» (120 px против 99 на
   планшете): одной строкой предупреждение налезало на «Доступно». */
.ship-table .ship-warn { white-space: normal; }
/* От планшета поля ячеек уже, а штрихкоду шире колонка: штрихкод площадки в
   13 цифр занимает 126 px, в 15% он налезал на «Лежит» на 1024 и 768. С
   колонкой резерва колонок семь, и доли у остальных меньше: иначе сумма
   выходила за 100% и браузер ужимал штрихкод обратно. */
@media (min-width: 768px) {
  .ship-table th, .ship-table td { padding-right: 0.5rem; padding-left: 0.5rem; }
  .ship-table th:first-child, .ship-table td:first-child { padding-left: 1rem; }
  .ship-table .ship-c-good { width: 26%; }
  .ship-table .ship-c-code { width: 20%; }
  .ship-table .ship-c-have { width: 12%; }
  .ship-table .ship-c-free { width: 12%; }
  .ship-table .ship-c-take { width: 16%; }
  .ship-table .ship-c-left { width: 14%; }
  .ship-table:has(.ship-c-res) .ship-c-good { width: 21%; }
  .ship-table:has(.ship-c-res) .ship-c-have { width: 12%; }
  .ship-table:has(.ship-c-res) .ship-c-res { width: 9%; }
  .ship-table:has(.ship-c-res) .ship-c-take { width: 13%; }
  .ship-table:has(.ship-c-res) .ship-c-left { width: 13%; }
}
@media (max-width: 767px) {
  .ship-table { min-width: 0 !important; }
  .ship-table th, .ship-table td { padding-right: 0.375rem; padding-left: 0.375rem; }
  .ship-table th:first-child, .ship-table td:first-child { padding-left: 0.75rem; }
  .ship-table .ship-qty { max-width: 100%; }
  /* Свои доли на телефоне: с общими «Доступно» получало 54 px и заголовок
     налезал на «Отгрузить». Разбивка «хранение · к отправке» под остатком
     скрыта: в колонку шириной 64 px она не помещалась ни словом «хранение». */
  .ship-table .ship-c-good { width: 36%; }
  .ship-table .ship-c-have { width: 18%; }
  .ship-table .ship-c-free { width: 22%; }
  .ship-table .ship-c-take { width: 24%; }
  .ship-table .ship-c-have .field-hint { display: none; }
}

/* Поставки площадок: от планшета поля ячеек уже (статус «Отгрузка разрешена»
   и склад «Коледино» не помещались в 1rem полей и налезали на соседа). На
   телефоне второстепенные колонки скрыты, раскладка своя, иначе общий минимум
   в 720 px уводил кнопки за экран. */
@media (min-width: 768px) {
  .postavki-table th, .postavki-table td { padding-right: 0.5rem; padding-left: 0.5rem; }
  .postavki-table th:first-child, .postavki-table td:first-child { padding-left: 1rem; }
}
@media (max-width: 767px) {
  .postavki-table { min-width: 0 !important; table-layout: auto; }
  .postavki-table th, .postavki-table td { padding-right: 0.375rem; padding-left: 0.375rem; }
  .postavki-ready, .postavki-doc { min-width: 0; max-width: none; }
}

/* Навигация склада на телефоне. Пилюли второго ряда сразу под вкладками, а
   не под заголовком: общее правило поднимает только nav.tabs и шапку. Только
   ряд, что стоит прямо за вкладками склада: строка инструментов сборки FBS
   (.sklad-links ниже своих вкладок) остаётся на своём месте. */
@media (max-width: 767px) {
  .content > .sklad-subnav + .sklad-links { order: 1; }
}
