/* Design system v3 "Steel Ledger" (2026-08-12) - replaces v2's warm
   "industrial dispatch board" with a cooler, hairline-forward palette,
   carried over from the approved Steel Ledger mockup into the real site.
   Token NAMES are kept stable (only their values change) so no template/JS
   elsewhere had to change - same discipline v2 itself followed. Light by
   default; dark follows the OS via prefers-color-scheme UNLESS overridden by
   the manual toggle in the topbar (static/theme.js stamps
   data-theme="dark"/"light" on <html>, which wins over the media query in
   both directions). The mockup itself never designed a dark variant (out of
   scope there) - dark mode is a real, already-shipped feature here and is
   not being dropped, so the dark values below are new work: same cool/
   hairline spirit as the light palette, not an automatic inversion.
   --wb/--ozon/--good/--warn/--critical keep v2's already-tuned hues in both
   themes (near-identical to the mockup's own choices on the light side, and
   v2's dark values were already calibrated for contrast against a
   near-black surface) - status/brand colors aren't part of what this
   redesign changes. --chart-qr/--chart-manual also keep v2's values
   unchanged in both themes - already CVD-validated via the dataviz skill's
   validate_palette.js, no reason to re-roll a pair that already passes.
   --stencil (v2) was defined but never referenced anywhere in this file or
   any template - dropped. */
/* Шрифт интерфейса (2026-10-11). Onest — вариативный гротеск с настоящей
   кириллицей, лежит локально в /static/fonts (50 КБ на обе подрезки): до него
   стоял системный стек (-apple-system / Segoe UI / Roboto), из-за которого
   одна и та же страница выглядела по-разному на каждой машине и заметно
   старомодно на Windows. Внешних запросов к Google Fonts страница по-прежнему
   не делает — файл раздаёт свой же /static. */
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/onest-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/onest-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-2: #eef1f6;
  --ink: #121620;
  --ink-muted: #5a6474;
  --ink-faint: #8a93a1;
  --rule: #e3e8ef;
  --rule-strong: #c7cfda;
  --accent: #2763d8;
  --accent-soft: rgba(39, 99, 216, 0.10);
  --accent-deep: #1b4bad;
  --accent-ink: #ffffff;
  --wb: #8a1fc4;
  --wb-soft: rgba(138, 31, 196, 0.10);
  --ozon: #0052d6;
  --ozon-soft: rgba(0, 82, 214, 0.10);
  --yandex: #8a6d00;
  --yandex-soft: rgba(255, 204, 0, 0.16);
  --yandex-ink: #ffffff;
  --good: #1a8a5a;
  --good-soft: rgba(26, 138, 90, 0.13);
  --warn: #b4740c;
  --warn-soft: rgba(180, 116, 12, 0.15);
  --critical: #c23b32;
  --critical-soft: rgba(194, 59, 50, 0.11);
  /* Dedicated categorical pair for the /admin/kpi QR-vs-MANUAL chart
     (2026-08-09) - deliberately NOT --wb/--ozon (those mean "marketplace"
     everywhere else on the site; reusing them here would misread as "this is
     a WB/Ozon split", which it isn't - QR/MANUAL cuts across both). Values
     are the reference dataviz palette's slots 1+2 (blue/orange) - chosen
     specifically because that pair passes the categorical-palette validator
     (CVD deutan/protan/tritan separation + normal-vision floor) in both
     light and dark, unlike an arbitrary pick (verified live with
     dataviz skill's validate_palette.js before adopting these hexes).  */
  --chart-qr: #2a78d6;
  --chart-manual: #eb6834;
  /* Rank-badge colors for the /admin/kpi operator leaderboard (2026-08-22) -
     deliberately NOT --warn (already means "caution" everywhere else on the
     site; reusing it for "1st place" would misread as a warning). Text-only
     (no soft/background variant) - matches the already-minimal
     .kpi-rank-num it decorates, no new chip shape introduced. */
  --rank-gold: #a6790a;
  --rank-silver: #66707f;
  --rank-bronze: #9c5a2e;
  --flap-face: #fdfdfe;
  --flap-bezel: #12141a;
  /* Шкала скруглений (2026-10-11). До неё в файле жили девять разных
     значений вперёд-вперемешку (2,3,4,5,6,7,8,9,10px) — глаз читает такую
     россыпь как неряшливость, даже не умея её назвать. Три ступени: мелочь,
     элементы управления, панели. */
  --r-xs: 6px;
  --r-sm: 8px;
  --r-md: 12px;
  --r-full: 999px;
  --font-mono: ui-monospace, "IBM Plex Mono", "JetBrains Mono", "SF Mono", "Cascadia Mono", Consolas, "Roboto Mono", monospace;

  /* Тени (2026-10-11): короче размытие, явное смещение вниз и один
     «прижимающий» слой — прежние были широкими и ватными, из-за чего панели
     выглядели не приподнятыми, а размазанными. */
  --shadow-1: 0 1px 1px rgba(18, 22, 32, 0.04), 0 1px 3px rgba(18, 22, 32, 0.06);
  --shadow-2: 0 1px 2px rgba(18, 22, 32, 0.05), 0 8px 20px -6px rgba(18, 22, 32, 0.13);
  --shadow-3: 0 2px 6px rgba(18, 22, 32, 0.10), 0 24px 48px -12px rgba(18, 22, 32, 0.26);
  --overlay-hover: rgba(22, 25, 31, 0.035);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0d12;
    --surface: #141922;
    --surface-2: #1b212b;
    --ink: #e8ecf3;
    --ink-muted: #9ba3af;
    --ink-faint: #6b7280;
    --rule: #242b36;
    --rule-strong: #343d4b;
    --accent: #5e92f0;
    --accent-soft: rgba(94, 146, 240, 0.17);
    --accent-deep: #8fb4f7;
    --wb: #ba57ed;
    --wb-soft: rgba(186, 87, 237, 0.16);
    --ozon: #529dff;
    --ozon-soft: rgba(82, 157, 255, 0.16);
    --yandex: #ffd94d;
    --yandex-soft: rgba(255, 217, 77, 0.18);
    --yandex-ink: #1a1400;
    --good: #3ecf8e;
    --good-soft: rgba(62, 207, 142, 0.16);
    --warn: #f0a93a;
    --warn-soft: rgba(240, 169, 58, 0.17);
    --critical: #f2685a;
    --critical-soft: rgba(242, 104, 90, 0.16);
    --chart-qr: #3987e5;
    --chart-manual: #d95926;
    --rank-gold: #e0b23d;
    --rank-silver: #b8c0cc;
    --rank-bronze: #d98f5f;
    --flap-face: #1b1f26;
    --flap-bezel: #000000;
    --shadow-1: 0 1px 1px rgba(0, 0, 0, 0.32), 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 24px -8px rgba(0, 0, 0, 0.6);
    --shadow-3: 0 2px 8px rgba(0, 0, 0, 0.45), 0 28px 56px -14px rgba(0, 0, 0, 0.7);
    --overlay-hover: rgba(231, 234, 239, 0.06);
    --accent-ink: #ffffff;
  }
}

:root[data-theme="dark"] {
  --bg: #0a0d12;
  --surface: #141922;
  --surface-2: #1b212b;
  --ink: #e8ecf3;
  --ink-muted: #9ba3af;
  --ink-faint: #6b7280;
  --rule: #242b36;
  --rule-strong: #343d4b;
  --accent: #5e92f0;
  --accent-soft: rgba(94, 146, 240, 0.17);
  --accent-deep: #8fb4f7;
  --wb: #ba57ed;
  --wb-soft: rgba(186, 87, 237, 0.16);
  --ozon: #529dff;
  --ozon-soft: rgba(82, 157, 255, 0.16);
  --yandex: #ffd94d;
  --yandex-soft: rgba(255, 217, 77, 0.18);
  --yandex-ink: #1a1400;
  --good: #3ecf8e;
  --good-soft: rgba(62, 207, 142, 0.16);
  --warn: #f0a93a;
  --warn-soft: rgba(240, 169, 58, 0.17);
  --critical: #f2685a;
  --critical-soft: rgba(242, 104, 90, 0.16);
  --chart-qr: #3987e5;
  --chart-manual: #d95926;
  --rank-gold: #e0b23d;
  --rank-silver: #b8c0cc;
  --rank-bronze: #d98f5f;
  --flap-face: #1b1f26;
  --flap-bezel: #000000;
  --shadow-1: 0 1px 1px rgba(0, 0, 0, 0.32), 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 24px -8px rgba(0, 0, 0, 0.6);
  --shadow-3: 0 2px 8px rgba(0, 0, 0, 0.45), 0 28px 56px -14px rgba(0, 0, 0, 0.7);
  --overlay-hover: rgba(231, 234, 239, 0.06);
  --accent-ink: #ffffff;
}

:root[data-theme="light"] {
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-2: #eef1f6;
  --ink: #121620;
  --ink-muted: #5a6474;
  --ink-faint: #8a93a1;
  --rule: #e3e8ef;
  --rule-strong: #c7cfda;
  --accent: #2763d8;
  --accent-soft: rgba(39, 99, 216, 0.10);
  --accent-deep: #1b4bad;
  --accent-ink: #ffffff;
  --wb: #8a1fc4;
  --wb-soft: rgba(138, 31, 196, 0.10);
  --ozon: #0052d6;
  --ozon-soft: rgba(0, 82, 214, 0.10);
  --yandex: #8a6d00;
  --yandex-soft: rgba(255, 204, 0, 0.16);
  --yandex-ink: #ffffff;
  --good: #1a8a5a;
  --good-soft: rgba(26, 138, 90, 0.13);
  --warn: #b4740c;
  --warn-soft: rgba(180, 116, 12, 0.15);
  --critical: #c23b32;
  --critical-soft: rgba(194, 59, 50, 0.11);
  --chart-qr: #2a78d6;
  --chart-manual: #eb6834;
  --rank-gold: #a6790a;
  --rank-silver: #66707f;
  --rank-bronze: #9c5a2e;
  --flap-face: #fdfdfe;
  --flap-bezel: #12141a;
  --shadow-1: 0 1px 1px rgba(18, 22, 32, 0.04), 0 1px 3px rgba(18, 22, 32, 0.06);
  --shadow-2: 0 1px 2px rgba(18, 22, 32, 0.05), 0 8px 20px -6px rgba(18, 22, 32, 0.13);
  --shadow-3: 0 2px 6px rgba(18, 22, 32, 0.10), 0 24px 48px -12px rgba(18, 22, 32, 0.26);
  --overlay-hover: rgba(22, 25, 31, 0.035);
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Onest", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  /* Сглаживание и чуть поджатый трекинг: на крупных числах и заголовках
     дефолтный интервал Onest выглядит разреженным. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: -0.006em;
}
h1, h2, h3, .stat-value, .big-number { letter-spacing: -0.021em; }
/* Ссылки в тексте — цветом акцента, а не браузерным синим: в тёмной теме
   #0000EE на графитовом фоне давал контраст 1.9 и ссылки строк на «Требуют
   внимания» было не прочитать (аудит 2026-10-18). Только у <a> без класса —
   кнопки-ссылки и чипы держат свой цвет. */
a:not([class]) { color: var(--accent); }
a:not([class]):visited { color: var(--accent); }
.mono { font-family: var(--font-mono); }
.num { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.muted { color: var(--ink-muted); }

.topbar { background: var(--surface); border-bottom: 1px solid var(--rule); position: sticky; top: 0; z-index: 30; }
.topbar-inner {
  /* Fluid, not a fixed 1440px cap - on a 34"+ ultrawide monitor a hard 1440px
     cap leaves ~1000px of dead space on each side. min() scales the content
     width with the viewport up to a generous ceiling (past which table rows
     and cards would just look sparse, not more useful), while vw keeps a
     margin reserved so it's never truly edge-to-edge even on a 49" screen.
     Steel Ledger reskin (v0.9.77): compact ~48px bar (min-height, not a hard
     height, so it still wraps instead of clipping on narrow screens) to
     match the mockup's dense masthead - was a much taller two-row-feeling
     bar before. */
  max-width: min(2200px, 96vw); margin: 0 auto; padding: 0 24px; min-height: 48px;
  display: flex; align-items: center; gap: 10px 22px; flex-wrap: wrap;
}
.account-menu { margin-left: auto; display: flex; align-items: center; gap: 12px; }

/* Order-count-today / success-% cluster (v0.9.77, mockup's .topbar-stat) -
   real numbers, see render() in main.py. */
.topbar-stat { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--ink-muted); white-space: nowrap; }
.topbar-stat svg { width: 13px; height: 13px; color: var(--ink-faint); flex: none; }
.topbar-stat b { color: var(--ink); font-weight: 750; }
.topbar-sep { width: 1px; height: 16px; background: var(--rule); flex: none; }
.avatar {
  width: 26px; height: 26px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 750;
  color: var(--ink-muted); flex: none;
}

/* Live clock + polling-line status pill in the topbar (2026-08-09, carried
   over from the design-concept mockup's masthead) - real data, not a static
   demo label: line_status is computed once per request in render() from
   whether any active store's last poll errored (db.any_active_store_has_poll_error). */
.clock { font-family: var(--font-mono); font-size: 13px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.line-pip {
  --pip-color: var(--good);
  display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-mono);
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--good); background: var(--good-soft); border: 1px solid var(--good);
  padding: 5px 10px 5px 8px; border-radius: var(--r-full); white-space: nowrap;
}
.line-pip.err { --pip-color: var(--critical); color: var(--critical); background: var(--critical-soft); border-color: var(--critical); }
.line-pip .dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--good); flex: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pip-color) 15%, transparent);
}
.line-pip.err .dot { background: var(--critical); }
/* Expanding-ring pulse (mockup's pulseDot, 2026-08-16) - was a plain opacity
   flicker, which is not what the mockup's status dot actually does (a
   sonar-style ring growing out from the dot and fading). --pip-color lets
   one shared keyframe serve both the ok/err states instead of duplicating
   it per state. */
@media (prefers-reduced-motion: no-preference) {
  .line-pip .dot { animation: line-pip-pulse 2.4s ease-out infinite; }
}
@keyframes line-pip-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pip-color) 40%, transparent); }
  70% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--pip-color) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pip-color) 0%, transparent); }
}

/* Row entrance animation (mockup's rowIn, v0.9.79) - applied to every dense
   list row across the site (.store-row on dashboard/assembly's magазин
   lists, .option-row on /scan's QR/MANUAL lists and every other
   .option-list user: /stores, /users, /settings/prefixes, /settings/
   telegram, etc.) since it's a single shared component, not a per-page
   change. Was missing entirely - rows just appeared with no motion,
   which is what the owner meant by "перенёс просто стиль, но не анимации":
   the mockup's rowIn plays on every row, ours played on none. Rows still
   render correctly without the animation-delay stagger (that part is
   optional, added where cheap to wire - see JS below), this alone already
   restores the "rows settle in" feel. */
@keyframes rowIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: no-preference) {
  .store-row, .option-row { animation: rowIn 280ms cubic-bezier(.22,.85,.3,1) both; }
}
@media (max-width: 640px) {
  .clock { display: none; }
}
/* Бренд (2026-09-10): один знак «ip» на графитовой плашке + слово impaper.
   Убрана приписка «СИСТЕМА СБОРКИ» и градиентный WB/Ozon-шестигранник — по
   прямому запросу владельца оставлено просто «impaper». Плашка знака красится
   в тёмный ink и в светлой, и в тёмной теме (сам SVG уже несёт fill), поэтому
   переопределять её под тему не нужно. */
.brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; font-weight: 750; font-size: 14px; letter-spacing: -0.005em; }
.brand-mark {
  width: 22px; height: 22px; border-radius: var(--r-sm); flex: none; display: block;
  /* В тёмной теме графитовая плашка почти сливается с фоном шапки —
     хайрлайн-обводка возвращает знаку края как у отдельного объекта. */
  box-shadow: 0 0 0 1px var(--rule);
}
.brand-text { font-family: var(--font-mono); font-weight: 700; letter-spacing: 0.01em; white-space: nowrap; }

/* Manual day/night toggle (2026-08-09, per user request - overrides the OS
   preference via data-theme on <html>, see static/theme.js). A single
   button holding both icons; only one is ever visible, driven by whichever
   theme is currently effective (explicit override or, absent one, the OS
   preference) - see theme.js's syncIcon(). */
.theme-toggle {
  background: none; border: 1px solid var(--rule-strong); border-radius: var(--r-full);
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--ink-muted); flex: none; transition: border-color 120ms ease, color 120ms ease;
}
.theme-toggle:hover { border-color: var(--accent); color: var(--ink); }
.theme-toggle svg { width: 17px; height: 17px; }
.theme-toggle .icon-moon { display: none; }
.theme-toggle[data-active-theme="dark"] .icon-sun { display: none; }
.theme-toggle[data-active-theme="dark"] .icon-moon { display: block; }
nav { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.nav-group { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.nav-divider { width: 1px; height: 18px; background: var(--rule); margin: 0 10px; flex: none; }
/* Steel Ledger reskin (v0.9.77): active link gets a soft pill (background +
   accent text), matching the mockup's nav.mainnav a.navlink.active -
   replaces the underline-on-active/hover treatment. */
nav a {
  color: var(--ink-muted); text-decoration: none; font-size: 12.5px; font-weight: 650;
  padding: 7px 11px; border-radius: var(--r-sm); white-space: nowrap;
}
nav a:hover { color: var(--ink); background: var(--surface-2); }
nav a.active { color: var(--accent); background: var(--accent-soft); }

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

@media (max-width: 640px) {
  .topbar-inner { padding: 12px 16px; }
  .account-menu { margin-left: 0; width: 100%; justify-content: space-between; }
  .page { padding: 24px 16px 60px; }
  /* nav wraps onto its own line(s) below this width - the divider between the
     operator/admin link clusters doesn't wrap with them and can be left
     dangling alone at the end of a line with nothing after it (found in UX
     audit 2026-08-07, present on every page). Line-wrapping itself already
     separates the two groups visually at this width, divider not needed. */
  .nav-divider { display: none; }
}

.page { max-width: min(2200px, 96vw); margin: 0 auto; padding: 32px 24px 80px; }
/* 6px used to sit here — on every single page, this is the gap between the
   title block and the very next line of body text, and adjacent block
   margins collapse to the LARGER one, not the sum, so that 6px was the
   entire gap regardless of the next element's own margin. Reads as the
   heading and body text being stuck together. */
.page-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 10px 16px; flex-wrap: wrap; margin-bottom: 24px; }
.page-head-text { min-width: 0; }
.eyebrow {
  display: block; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 6px;
}
h1 { font-size: 27px; font-weight: 800; letter-spacing: -0.012em; margin: 0; }
/* Neither rule above sets its own line-height, so both inherit body's 1.5 -
   fine for paragraph text, but on an 11px eyebrow and a 27px h1 it pads a
   half-leading's worth of empty space above AND below the glyphs, which
   reads as an oversized gap between the two lines even though the eyebrow's
   own margin-bottom is only 6px (2026-08-22, per direct owner request to
   tighten this on /schedule - scoped to .page-head-text rather than a bare
   h1/.eyebrow rule so login.html's/error.html's own unrelated headings,
   which don't sit inside this component, are untouched). */
.page-head-text .eyebrow { line-height: 1.2; }
.page-head-text h1 { line-height: 1.15; }
/* /scan is operated many times a minute under time pressure - the header
   should get out of the way, not announce itself (2026-08-09, per user
   request: shrink it and pull everything else up so more fits on a small
   screen). Scoped to .compact, not the shared rule - every other page keeps
   the normal-sized head. */
.page-head.compact { margin-bottom: 6px; }
.page-head.compact .eyebrow { font-size: 8px; margin-bottom: 0; }
.page-head.compact h1 { font-size: 14px; }
.section-title {
  font-family: var(--font-mono); font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint); margin: 40px 0 16px;
}
.hint { color: var(--ink-muted); font-size: 13.5px; margin: 0 0 20px; }

.empty {
  border: 1px dashed var(--rule); border-radius: var(--r-md); padding: 32px 24px; color: var(--ink-muted); text-align: center;
  background-image: radial-gradient(var(--rule) 1px, transparent 1px);
  background-size: 22px 22px; background-position: center;
}

/* Store cards - rendered inside a .mp-panel-body grid (Dashboard, split by
   marketplace, see .mp-split above) rather than one mixed grid; each panel
   bounds its own height + scroll (see .mp-panel-body), so a large store
   count still can't run the page away (originally fixed 2026-08-08, direct
   complaint: "уходит далеко вниз страницы"). */
@keyframes card-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.store-card {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md); padding: 12px 14px;
  box-shadow: var(--shadow-1); transition: transform 140ms ease, box-shadow 140ms ease;
  animation: card-in 320ms ease backwards;
}
.store-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }
.store-card.store-card-inactive { opacity: 0.55; }
.store-card-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; margin-bottom: 8px; }
.store-card-name {
  font-family: var(--font-mono); font-weight: 700; font-size: 12px; letter-spacing: 0.01em;
  text-transform: uppercase;
}
.store-card-count {
  font-size: 26px; font-weight: 700; line-height: 1; margin-bottom: 10px;
  font-variant-numeric: tabular-nums; font-family: var(--font-mono);
  display: flex; align-items: baseline; gap: 8px;
}
.store-card-count-label {
  font-size: 10px; font-weight: 600; color: var(--ink-faint); font-family: var(--font-mono);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.store-card-meta { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 11.5px; font-family: var(--font-mono); }
@media (prefers-reduced-motion: reduce) {
  .store-card { animation: none; }
  .store-card:hover { transform: none; }
}

.table-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-1); }
/* font-size/padding match the mockup's own generic table rule verbatim
   (2026-08-16, per user request "ты просто сделал похоже" - tables across
   the whole site, not just one page, were denser/smaller in the mockup:
   12px body text and 7-8px vertical padding, not this project's previous
   14px/10px). One shared rule, so every table site-wide (dashboard's
   "Последние заказы", /stores, /users, /admin/log, /problems, /scan's
   history, /assembly's run history) gets the fix at once. */
table { border-collapse: collapse; width: 100%; font-size: 12px; }
th, td { text-align: left; padding: 7px 14px; vertical-align: middle; }
thead th {
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-faint); background: var(--surface-2); border-bottom: 1px solid var(--rule-strong);
  padding: 8px 14px;
}
tbody tr + tr td { border-top: 1px solid var(--rule); }
tr.row-inactive { opacity: 0.55; }

/* Long, mostly-historical tables (activity/poll logs) get a bounded height
   with their own scroll instead of stretching the whole page - a 300-row log
   was pushing some pages to ~14000px tall. Header stays visible while scrolling. */
.table-wrap.long-table { max-height: 560px; overflow-y: auto; }
.table-wrap.long-table thead th { position: sticky; top: 0; z-index: 1; }

/* Below the breakpoint, rows stack into cards and each cell's own column
   name (from data-label) becomes its inline caption — a horizontally
   scrolling table is fine for a wide monitoring log, but unusable on a
   narrow screen for tables people actually act on (edit/toggle/delete). */
@media (max-width: 700px) {
  .table-wrap.cards-on-mobile { border: none; background: none; box-shadow: none; overflow-x: visible; position: relative; }
  .table-wrap.cards-on-mobile table, .table-wrap.cards-on-mobile tbody { display: block; width: 100%; }
  .table-wrap.cards-on-mobile thead { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
  .table-wrap.cards-on-mobile tr {
    display: block; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md);
    padding: 4px 0; margin-bottom: 10px; box-shadow: var(--shadow-1);
  }
  .table-wrap.cards-on-mobile tbody tr + tr td { border-top: none; }
  .table-wrap.cards-on-mobile td {
    /* flex-wrap matters: a cell's contents are flex ITEMS here, so a cell
       holding many chips (the "Магазины" column on /assembly with 20+ stores)
       stretched to ~1750px inside a 324px card instead of wrapping - the one
       place the card layout's own no-horizontal-scroll premise broke.
       justify-content is flex-end rather than space-between so wrapped lines
       stay right-aligned with the cell's text-align; the label keeps its left
       position via margin-right:auto below. */
    display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center;
    gap: 6px 14px; padding: 8px 14px; text-align: right; border-top: 1px solid var(--rule);
  }
  .table-wrap.cards-on-mobile tr td:first-child { border-top: none; }
  .table-wrap.cards-on-mobile td::before {
    content: attr(data-label); text-align: left; font-size: 11px; font-weight: 700;
    letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-muted);
    flex: none; margin-right: auto;
  }
  /* Таблица «ключ — значение» держит подпись строки в <th> внутри <tbody>
     (/lookup, /settings/general). В карточном режиме подпись печатает ::before
     из data-label, поэтому сам <th> надо убрать: иначе она выводится ДВАЖДЫ —
     жирным из <th> и капсом из ::before, — а <td> перестаёт быть :first-child
     и получает лишнюю линию сверху внутри однострочной карточки. */
  .table-wrap.cards-on-mobile tbody th { display: none; }
  .table-wrap.cards-on-mobile tbody th + td { border-top: none; }
  .table-wrap.cards-on-mobile td:empty::before { content: none; }
  .table-wrap.cards-on-mobile td.actions { justify-content: flex-end; }
  .table-wrap.cards-on-mobile td.actions::before { content: none; }
}

/* Dashboard's "Последние заказы" sits in the narrower .right-col next to
   .left-col, and both store names (up to ~40 chars, e.g. "Тетрадь в клетку
   Успех начинается с линии") and article codes (up to ~35 chars) routinely
   don't fit - rows were wrapping onto 2-3 lines instead of the panel just
   sizing itself sensibly (2026-08-24, per user request). Nowrap keeps every
   row one line; ellipsis on the two free-text columns caps how wide a single
   long value can push the table (full value is still on hover via title=).
   Desktop table view only - the <700px card layout already wraps fields
   deliberately (see .table-wrap.cards-on-mobile td above), and each field is
   on its own line there so it doesn't need this. */
@media (min-width: 701px) {
  .recent-orders-table th, .recent-orders-table td { white-space: nowrap; }
  .recent-orders-table td[data-label="Магазин"],
  .recent-orders-table td[data-label="Артикул"] {
    max-width: 220px; overflow: hidden; text-overflow: ellipsis;
  }
}

/* Split-flap departure-board KPI display - the mockup's signature component,
   replacing the old plain .kpi-tile. A shared bezel (dark on both themes)
   with individually-inset "flap" faces reads as one physical instrument, not
   a row of separate cards. Digits roll into place on load via static/flap.js
   (small shared script, see base.html) unless prefers-reduced-motion. */
.flap-board {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; background: var(--flap-bezel); border-radius: var(--r-md); overflow: hidden;
  box-shadow: var(--shadow-2); margin: 22px 0 4px;
}
.flap-unit { background: var(--flap-face); padding: 15px 18px 13px; position: relative; }
/* Fixed 2-col/1-col steps below the fluid auto-fit range (rather than
   letting minmax(150px,...) keep shrinking) - an odd tile count (e.g. 3 KPIs
   on Дашборд) would otherwise leave one bare bezel-colored cell dangling in
   the last row; the last tile spans the remainder instead. */
@media (max-width: 700px) {
  .flap-board { grid-template-columns: repeat(2, 1fr); }
  .flap-board .flap-unit:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
@media (max-width: 420px) {
  .flap-board { grid-template-columns: 1fr; }
}
.flap-unit::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--flap-bezel); opacity: 0.35; }
.flap-label {
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 8px;
}
.flap-digits { display: flex; gap: 3px; align-items: baseline; }
.flap-char {
  font-family: var(--font-mono); font-weight: 700; font-size: 32px; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.flap-char.warn { color: var(--warn); }
.flap-char.crit { color: var(--critical); }
.flap-suffix { font-family: var(--font-mono); font-size: 15px; color: var(--ink-faint); margin-left: 4px; align-self: center; }
.flap-sub { margin: 6px 0 0; font-size: 11px; color: var(--ink-muted); }

/* Option list — a bordered, elevated group of selectable/toggleable rows.
   Shared by the store picker on /assembly and the notification toggles on
   /settings/telegram, so both read as one component, not two bespoke bits
   of markup that happen to look similar. Checked state is a full-row tint
   (not just the tiny checkbox glyph) via :has(), so "what's selected" reads
   at a glance — progressively enhanced, unchecked rows work fine without it
   in the handful of very old browsers that lack :has() support. */
.option-list { display: flex; flex-direction: column; border: 1px solid var(--rule); border-radius: var(--r-md);
  overflow: hidden; background: var(--surface); box-shadow: var(--shadow-1); }
/* justify-content was space-between (v0.9.79 fix) - fine for a row with
   exactly 2 direct children (checkbox-label + actions, the /assembly and
   /settings/telegram case), but /scan's rows have THREE direct children
   (thumbnail, text, actions) with no wrapping element around the first two -
   space-between spreads the leftover width between ALL of them, prying the
   thumbnail away from its own article text once the row is wide (exactly
   what widening .right-col to match the mockup, earlier this same version,
   made obvious - the owner called it out as "оторван от картинки"). Plain
   flex-start + margin-left:auto on .option-row-actions produces the
   identical 2-child layout while keeping any earlier children packed
   together, so this is a strict fix, not a behavior change for the
   checkbox rows. */
.option-row { display: flex; justify-content: flex-start; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 15px 18px; border-bottom: 1px solid var(--rule); transition: background-color 100ms ease; }
/* Chip + printed-date + WB-status badge + "печать" button on the QR/MANUAL
   lists (2026-08-09: adding the printed-date span here made this cluster
   overflow the row on narrow screens, found live in this session's own
   mobile screenshot check - the "печать" button was clipped off past the
   viewport edge). Wraps onto its own line under the row's flex-wrap above
   when it doesn't fit, and wraps internally too if it's still too wide even
   alone on that line. margin-left:auto pushes it to the row's far end (see
   .option-row's justify-content comment above). */
.option-row-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; margin-left: auto; }
/* /stages' sticker number + (WB orders only) the real WB orderId stacked
   underneath in smaller text - both real visible text, Ctrl+F-able
   (2026-08-17, per direct user report - a Telegram alert named an order by
   its WB orderId, this page only ever showed sticker_number, a different
   number, so nothing on the page could be searched for it). */
.option-row-numbers { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.option-row-wb-id { font-size: 10px; opacity: 0.75; }
/* Status chip + cancellation date stacked (2026-08-17, per direct user
   request to show when a cancelled item was cancelled) - column so the date
   sits right under its own chip, not floating loose in the row. */
.option-row-status { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.option-row-status-date { font-size: 10px; opacity: 0.75; white-space: nowrap; }
.option-row:last-child { border-bottom: none; }
.option-row:hover { background: var(--overlay-hover); }
.option-row:has(input:checked) { background: var(--accent-soft); }
.option-row-label { display: flex; align-items: center; gap: 14px; cursor: pointer; min-width: 0; }
.option-row-text { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.option-row-name { font-size: 13.5px; font-weight: 500; }

/* Compact variant for the /scan queue lists (2026-08-09, per user request
   "требуется сделать уже и компактнее"). Those rows had grown to six stacked
   lines - article, store, run, «не отгружено», «не напечатано», the
   marketplace status, and the button - which at 55+ rows made the list
   exhausting to scan. The right-hand cluster (не отгружено / напечатано /
   marketplace status / печать) stays exactly where it was - the user asked
   only for narrower rows and, on being shown a version that moved those
   badges into the text column, said plainly that the right edge was better.
   So this rule tightens spacing only; the real height win came from fixing
   the grid min-content loop below, which had been halving the column width. */
.option-row.compact { padding: 8px 12px; gap: 8px; }
.option-row.compact .option-row-text { gap: 2px; }
.option-row.compact .option-row-name { font-size: 13px; }
/* These few pixels decide whether the whole row is one line or two: the text
   column and the right-hand cluster together came to ~615px inside a 609px
   row, so everything wrapped. Tighter chip padding and gaps buy back ~12px
   and the cluster sits beside the text instead of below it. */
.option-row.compact .option-row-actions { gap: 6px; }
.option-row.compact .chip { padding: 2px 6px; font-size: 10px; }
.option-row.compact .option-row-meta { font-size: 11px; }
/* Row thumbnail on the QR/MANUAL lists (2026-08-11, per user request: "чтобы
   понимать что ожидаем заранее" - see it before scanning, not just after a
   match). Fixed size so a row without a photo (has_photo=false) still takes
   the same height as one with a photo - a mix of the two must not make rows
   visually jump as the list scrolls. object-fit:cover crops rather than
   distorting non-square product photos. */
.option-row-thumb {
  width: 34px; height: 34px; border-radius: var(--r-sm); object-fit: cover; flex: none;
  background: var(--surface-2); border: 1px solid var(--rule);
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}
/* Hover pop (mockup's .thumb-wrap:hover .thumb, 2026-08-16) - this element
   is already the hover target for the floating photo preview tooltip (see
   scan.html), so no extra wrapper is needed for the lift itself. */
@media (hover: hover) {
  .option-row-thumb:hover { transform: scale(1.18); box-shadow: 0 3px 8px rgba(22, 25, 31, 0.18); border-color: var(--accent); }
}
@media (prefers-reduced-motion: reduce) { .option-row-thumb { transition: none; } }
.option-row-thumb-placeholder {
  width: 34px; height: 34px; border-radius: var(--r-sm); flex: none;
  background: var(--surface-2); border: 1px solid var(--rule);
}

/* Store tag and run tag on one line (see rowMpTag) */
.row-meta-line { display: inline-flex; align-items: center; min-width: 0; gap: 6px; flex-wrap: wrap; }
/* Пометка ячейки: сборщик должен считывать её краем глаза, поэтому моно и
   чуть плотнее соседних чипов — но не крупнее, это подсказка, а не статус. */
.scan-cell-tag { font-family: var(--font-mono); letter-spacing: .01em; white-space: nowrap; }
.option-row-desc { font-size: 12.5px; color: var(--ink-muted); }
.option-row-meta { font-size: 12.5px; color: var(--ink-muted); white-space: nowrap; }
/* Age badge on an unresolved row's meta cluster (2026-08-16) - crit variant
   for isStale() (>48h, see scan.html), same red the "не напечатано" chip
   already uses on this row, so "urgent" reads as one consistent colour
   language rather than a second unrelated red. */
.option-row-meta.crit { color: var(--critical); font-weight: 700; }
.option-row-control { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-muted); flex: none; }
.option-row-control select:disabled { opacity: 0.45; cursor: default; }

/* Whole-row printed/unprinted state (2026-08-08, per user request) — used on
   /scan's "Готово к сканированию" and "MANUAL печать" lists, not just the
   small status chip, so the state reads at a glance without hunting for it. */
.option-row.state-printed { background: var(--good-soft); }
.option-row.state-unprinted { background: var(--critical-soft); }
/* Needs work - neither shipped nor printed (2026-08-09). Deliberately louder
   than plain state-unprinted: a left accent bar plus the warn tint, so it
   reads as its own category at a glance rather than a shade of red. The row
   spells both halves out in its own chips ("не отгружено" + "не напечатано")
   - never colour alone. */
.option-row.state-untouched {
  background: var(--warn-soft);
  box-shadow: inset 3px 0 0 var(--warn);
}
/* Stale - needs-work that's ALSO been sitting past isStale()'s 48h (2026-08-16,
   per user request: real 9-10-day-old orders looked identical to fresh ones,
   both plain amber). Same shape as state-untouched (inset accent bar + soft
   tint) but critical, not warn, so "urgent" and "just started" are visually
   two different categories, not two intensities of the same colour a tired
   operator has to compare side by side to tell apart. The gentle pulse on
   the accent bar (not the whole row - that would be exhausting across a long
   list) is the one thing on this page allowed to nag without a click. */
.option-row.state-stale {
  background: var(--critical-soft);
  box-shadow: inset 3px 0 0 var(--critical);
}
@keyframes stale-bar-pulse { 0%, 100% { box-shadow: inset 3px 0 0 var(--critical); } 50% { box-shadow: inset 3px 0 0 var(--warn); } }
@media (prefers-reduced-motion: no-preference) {
  .option-row.state-stale { animation: stale-bar-pulse 2.4s ease-in-out infinite; }
}
/* Generic tone modifiers (2026-08-16, /stages/{name} drill-down - per user
   request "строки таблицы -> цветные строки-списки", same list language
   /scan already uses for its QR/MANUAL rows). Same soft-tint + inset
   accent-bar shape as state-untouched/state-stale above, but named after
   the tone itself (good/warn/crit/neutral) rather than a printed-specific
   state, since this list's rows are colored by marketplace lifecycle
   status (_stage_status_label's cls), not print progress. */
.option-row.tone-good { background: var(--good-soft); box-shadow: inset 3px 0 0 var(--good); }
.option-row.tone-warn { background: var(--warn-soft); box-shadow: inset 3px 0 0 var(--warn); }
/* tone-crit on /stages ONLY ever means a genuinely dead/cancelled outcome -
   every code that maps to "crit" in _STAGE_STATUS_LABELS (main.py) is a
   cancellation or a defect, never "needs attention but still live" (that's
   tone-warn). Per direct user request ("мне очень нравится как на макете
   было сделано подсвечивание товара который отменил покупатель") - ported
   the mockup's .row.state-cancelled treatment verbatim instead of the flat
   tint every other tone uses: a diagonal hazard stripe (this row is DONE,
   not pending work like a plain color-tinted row would suggest) plus a
   struck-through article name, both adapted to this site's own tokens
   (mockup's --sunken/--surface -> our --overlay-hover/--surface). 2026-08-16. */
.option-row.tone-crit {
  background: repeating-linear-gradient(135deg, var(--overlay-hover) 0px, var(--overlay-hover) 7px, var(--surface) 7px, var(--surface) 14px);
  box-shadow: inset 3px 0 0 var(--critical);
}
.option-row.tone-crit .option-row-name { text-decoration: line-through; text-decoration-color: var(--ink-faint); color: var(--ink-muted); }
.option-row.tone-neutral { box-shadow: inset 3px 0 0 var(--rule-strong); }
.option-row.tone-good:hover, .option-row.tone-warn:hover,
.option-row.tone-crit:hover, .option-row.tone-neutral:hover { filter: brightness(0.98); }
.option-row.state-printed:hover,
.option-row.state-unprinted:hover,
.option-row.state-untouched:hover,
.option-row.state-stale:hover { filter: brightness(0.98); }
/* Icon-only print button (mockup's .print-btn, v0.9.83, per user request
   "вместо кнопки печать стикера в макете иконка с анимацией") - was a plain
   text "печать" link. Square, bordered, printer glyph - mockup's exact
   sizing/colours. */
.print-btn {
  width: 26px; height: 26px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm); background: var(--surface);
  display: flex; align-items: center; justify-content: center; color: var(--ink-muted); cursor: pointer; flex: none;
}
.print-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.print-btn svg { width: 13px; height: 13px; }
.print-btn[disabled] { opacity: 0.35; cursor: pointer; }
.print-btn[disabled]:hover { border-color: var(--rule-strong); color: var(--ink-muted); background: var(--surface); }
/* Print success flash (mockup's .print-btn.flash/btnPop) - .flash is
   applied for exactly one render by scan.html's justPrintedId (see there
   for why it can't be a plain setTimeout-removed class - the whole list
   re-renders on every print). */
@keyframes btn-pop { 0% { transform: scale(1); } 45% { transform: scale(1.22); } 100% { transform: scale(1); } }
.print-btn.flash { background: var(--good); border-color: var(--good); color: #fff; }
@media (prefers-reduced-motion: no-preference) {
  .print-btn.flash { animation: btn-pop 320ms ease; }
}

/* flex-wrap (2026-08-11): a long hint next to a button (problems.html's CSV
   download) overflowed the viewport on mobile instead of wrapping - found
   live in a 390px screenshot. gap replaces what justify-content:space-between
   provided for two-item rows, and still works once a row wraps to two lines. */
.option-list-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.option-list-scroll { max-height: 260px; overflow-y: auto; }

/* display:inline-flex перебивает атрибут [hidden] — без этой строки чип,
   спрятанный через `hidden`, всё равно виден (поймано на «сохранено»
   в карточке ячейки 2026-09-13). */
.chip[hidden] { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono);
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  padding: 2px 8px; border-radius: var(--r-full); white-space: nowrap;
  text-decoration: none; /* a chip is occasionally an <a> (run-number chips below) */
}
.chip.small { font-size: 9.5px; padding: 2px 6px; margin-left: 6px; }
.chip.good { background: var(--good-soft); color: var(--good); }
.chip.warn { background: var(--warn-soft); color: var(--warn); }
.chip.crit { background: var(--critical-soft); color: var(--critical); }
.chip.wb { background: var(--wb-soft); color: var(--wb); }
.chip.ozon { background: var(--ozon-soft); color: var(--ozon); }
.chip.yandex { background: var(--yandex-soft); color: var(--yandex); }
.chip.neutral { background: var(--surface-2); color: var(--ink-muted); }
.chip.accent { background: var(--accent-soft); color: var(--accent); }

.period-tabs { display: flex; gap: 6px; margin-bottom: 20px; flex-wrap: wrap; }
.period-tab {
  font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; padding: 7px 14px; border-radius: var(--r-sm); text-decoration: none;
  background: var(--surface); border: 1.5px solid var(--rule-strong); color: var(--ink-muted);
}
.period-tab.active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* /admin/kpi charts (2026-08-09, per user request "добавь ещё графики и
   больше аналитики"; restyled 2026-08-16 to the mockup's own component
   language - see CLAUDE.md v0.9.91). Plain flex/div bars, not SVG or a
   charting library, no new dependency. Colored bars stay purely visual
   (magnitude/proportion); exact numbers always live in adjacent plain-ink
   text or the table right below, never only inside a colored fill - a
   deliberate choice kept from before the restyle, after checking this
   site's existing status trio (--good/--warn/--critical) against the
   dataviz skill's palette validator: that trio (and this site's --ink-faint
   neutral used for "ambiguous") fails CVD adjacent-pair separation for some
   vision types when used as same-chart segments, same documented tradeoff
   the skill's own reference status palette makes ("icon + label pairing is
   the mitigation, so a status color never carries meaning alone") - so
   identity here rides on the always-visible legend text/swatch pairing and
   position, never on hue alone. Each chart now sits in the site's ordinary
   .panel/.panel-head/.eyebrow shell (mockup's own wrapping) instead of the
   bespoke .kpi-chart box this replaced - one less one-off component. */
.kpi-legend { display: flex; flex-wrap: wrap; gap: 14px; padding: 9px 14px; font-size: 12px; color: var(--ink-muted); background: var(--surface-2); border-bottom: 1px solid var(--rule); }
.kpi-legend-item { display: flex; align-items: center; gap: 6px; }
.kpi-legend-sw { width: 9px; height: 9px; border-radius: var(--r-xs); display: inline-block; flex: none; }
.kpi-legend-sw.printed { background: var(--good); }
.kpi-legend-sw.duplicate { background: var(--warn); }
.kpi-legend-sw.notfound { background: var(--critical); }
.kpi-legend-sw.ambiguous { background: var(--ink-faint); }
.kpi-legend-sw.qr { background: var(--chart-qr); }
.kpi-legend-sw.manual { background: var(--chart-manual); }

/* Timeline: one bar per day/hour bucket, single hue (accent) - a single
   series needs no legend, see dataviz skill's accessibility check. Height
   set per-bar via the --h custom property (computed server-side in main.py
   as a percent of the period's busiest bucket), not inline on a property any
   @media breakpoint overrides here - safe, unlike the inline
   grid-template-columns bug this project hit twice before (see CLAUDE.md).
   Mockup's own two-level nesting (.kpi-timeline padding wrapper >
   .kpi-timeline-bars the actual flex row) kept as-is rather than
   flattened, so its 110px bar height/3px gap match the mockup exactly. */
.kpi-timeline { padding: 14px 16px 8px; }
/* Each bar is now a column: value label on top, the fixed-height plot area in
   the middle, an x-axis tick under it (2026-09-07, per owner request "сделай
   подписи прямо на столбцах"). 2px gap between adjacent bars per the dataviz
   spec. The old free-standing 3-anchor .kpi-timeline-labels row is gone -
   ticks now sit under their own bar. */
.kpi-timeline-bars { display: flex; align-items: flex-end; gap: 2px; }
.kpi-bar-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; }
.kpi-bar-col:hover .kpi-bar { opacity: 0.7; }
.kpi-bar-value { font: 650 9.5px/1 var(--font-mono); color: var(--ink-muted); height: 12px; }
.kpi-bar-plot { width: 100%; height: 104px; display: flex; align-items: flex-end; }
.kpi-bar-tick { margin-top: 4px; font: 400 9px/1.2 var(--font-mono); color: var(--ink-faint); white-space: nowrap; height: 12px; }
/* Stacked per-outcome bar (2026-08-17, per direct user report the chart was
   "совершенно не информативен" as a single flat-color total) - each bucket
   is now a flex column-reverse of 4 segments (.kpi-bar-seg), same
   printed/duplicate/notfound/ambiguous colors as .kpi-legend-sw/
   .kpi-stackbar-seg above. Bar height (--h) is still the bucket's share of
   the period's busiest bucket, computed server-side exactly as before;
   each segment's OWN height inside that bar comes from flex-grow set to its
   raw count (see admin_kpi.html) - ratios self-normalize without a second
   percent calculation. Order is fixed (printed always at the bottom,
   ambiguous always at the top) so identity reads from position, not only
   hue - the same CVD mitigation already documented above for the other
   charts on this page (icon/position pairing, never color alone). */
.kpi-bar { width: 100%; height: var(--h, 0%); min-height: 2px; background: var(--rule-strong); border-radius: var(--r-xs) var(--r-xs) 2px 2px; overflow: hidden; display: flex; flex-direction: column-reverse; transition: opacity 0.1s; }
.kpi-bar-seg { width: 100%; flex-grow: var(--f, 0); flex-shrink: 0; min-height: 0; }
.kpi-bar-seg.printed { background: var(--good); }
.kpi-bar-seg.duplicate { background: var(--warn); }
.kpi-bar-seg.notfound { background: var(--critical); }
.kpi-bar-seg.ambiguous { background: var(--ink-faint); }
.kpi-timeline-empty { color: var(--ink-faint); font-size: 13px; padding: 30px 0; text-align: center; }
/* operator×bucket chips inside the timeline "как таблицу" drilldown (2026-09-07) */
.kpi-op-chip { display: inline-flex; align-items: baseline; gap: 4px; margin: 2px 4px 2px 0; padding: 1px 6px; border-radius: var(--r-full); background: var(--surface-2); font-size: 11px; white-space: nowrap; }
.kpi-op-chip-name { color: var(--ink-muted); }
.kpi-op-chip-n { font-weight: 650; }

/* Employee filter (2026-08-17, per direct user request: "добавь фильтр...
   по сотрудникам") - same collapsible shape as .kpi-range-form right above
   it, a checkbox-list form instead of two date inputs. */
.kpi-employee-form { display: none; flex-direction: column; gap: 8px; margin-top: 10px; }
.kpi-employee-form.open { display: flex; }
.kpi-employee-list { display: flex; flex-wrap: wrap; gap: 6px; max-width: 480px; }
.kpi-employee-item {
  display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 5px 10px;
  border: 1px solid var(--rule-strong); border-radius: var(--r-full); background: var(--surface); cursor: pointer;
}
.kpi-employee-item:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); }
.kpi-employee-actions { display: flex; gap: 8px; align-items: center; }

/* Top-articles ranked list (2026-08-17, per direct user request: "ТОП 10
   самых популярных артикулов ... которых больше всего купили") - a simple
   leaderboard bar per row, width relative to the #1 article's own count
   (not a percent-of-total, which would make every bar look nearly empty
   once there are more than a handful of distinct articles). Single hue,
   same "no legend needed for one series" rule as the timeline's old flat
   bars. */
.kpi-rank-row { display: flex; align-items: center; gap: 10px; padding: 7px 14px; border-bottom: 1px solid var(--rule); }
.kpi-rank-row:last-child { border-bottom: none; }
.kpi-rank-num { width: 18px; flex: none; font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); text-align: right; }
/* Leaderboard medal modifiers (2026-08-22) - color + bold weight on the same
   plain numeral, not a separate icon/emoji: the number "1"/"2"/"3" already
   carries the meaning, color only reinforces it (never the sole signal). */
.kpi-rank-num.medal-gold { color: var(--rank-gold); font-weight: 800; }
.kpi-rank-num.medal-silver { color: var(--rank-silver); font-weight: 800; }
.kpi-rank-num.medal-bronze { color: var(--rank-bronze); font-weight: 800; }
.kpi-rank-article { width: 200px; flex: 0 0 200px; font-family: var(--font-mono); font-size: 12px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kpi-rank-track { flex: 1; height: 11px; border-radius: var(--r-xs); background: var(--surface-2); overflow: hidden; min-width: 0; }
/* Real bug, found live (2026-08-22) while screenshotting the new "ТОП
   категорий" panel: .kpi-rank-fill is a <span> (inline by default) - width
   on an inline, non-replaced element is simply ignored by the CSS spec, so
   every fill bar in "ТОП артикулов"/"ТОП операторов" (used since 2026-08-17)
   has silently rendered at 0 width the entire time, not just today's new
   panel. display:block makes the already-correct --w custom property (set
   inline per row, verified via computed style before this fix - the value
   itself was always right) actually take effect. */
.kpi-rank-fill { display: block; height: 100%; background: var(--accent); width: var(--w, 0%); border-radius: var(--r-xs); }
.kpi-rank-count { width: 48px; flex: 0 0 48px; text-align: right; font-family: var(--font-mono); font-size: 12px; font-weight: 650; }
@media (max-width: 640px) {
  .kpi-rank-article { width: 120px; flex-basis: 120px; }
}

/* "Товарные линейки" / "ТОП отмен — товары" per-row bars (2026-08-25,
   reworked 2026-09-07). .kpi-line-progress is the flex wrapper (bar + a
   readout beside it) that column-stacks on mobile. The bar itself is a
   .kpi-splitbar - two coloured segments of this row's OWN total side by
   side (printed vs remaining, or kept vs cancelled) - deliberately NOT
   .kpi-rank-track/.kpi-rank-fill, which mean "this row's size vs the #1
   row"; two different meanings must not share one visual (the original
   complaint this component was built to fix). */
.kpi-line-progress { flex: 1; display: flex; align-items: center; gap: 10px; min-width: 0; }
@media (max-width: 640px) {
  .kpi-line-progress { flex-direction: column; align-items: stretch; gap: 4px; }
}

.kpi-splitbar { flex: 1; display: flex; height: 11px; border-radius: var(--r-xs); overflow: hidden; min-width: 0; background: var(--surface-2); }
.kpi-splitbar-seg { height: 100%; width: var(--w, 0%); }
.kpi-splitbar-seg.printed { background: var(--good); }
.kpi-splitbar-seg.remaining { background: var(--rule-strong); }
.kpi-splitbar-seg.kept { background: var(--rule-strong); }
.kpi-splitbar-seg.cancelled { background: var(--critical); }
.kpi-split-readout { flex: 0 0 auto; text-align: right; font-family: var(--font-mono); font-size: 11.5px; white-space: nowrap; color: var(--ink-muted); }
.kpi-split-readout strong { font-weight: 650; color: var(--ink); }
.kpi-split-readout .accent-num { font-weight: 650; }
.kpi-split-readout .accent-num.good { color: var(--good); }
.kpi-split-readout .accent-num.warn { color: var(--warn); }
.kpi-split-readout .accent-num.crit { color: var(--critical); }
@media (max-width: 640px) {
  /* .kpi-line-progress becomes a column here - a flex:1 child then resolves
     its MAIN-axis basis to 0 and collapses to 0 height. Pin the bar's height
     and let it fill the column width instead. */
  .kpi-splitbar { flex: none; width: 100%; }
  .kpi-split-readout { text-align: left; white-space: normal; }
}

/* Per-operator outcome breakdown - one stacked horizontal bar per operator,
   segment widths as --w percent (computed in main.py from that row's own
   total, so they always sum to 100 for that row regardless of the row's
   absolute scale). No per-row total readout (mockup doesn't have one) - the
   table right below already has an exact "Всего" column, repeating it here
   was the same number twice. */
.kpi-stackbar-row { display: flex; align-items: center; gap: 10px; padding: 8px 14px; border-bottom: 1px solid var(--rule); }
.kpi-stackbar-row:last-child { border-bottom: none; }
.kpi-stackbar-name { width: 118px; flex: 0 0 118px; font-size: 11.5px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kpi-stackbar-track { flex: 1; display: flex; height: 14px; border-radius: var(--r-xs); overflow: hidden; background: var(--surface-2); min-width: 0; }
.kpi-stackbar-seg { height: 100%; width: var(--w, 0%); min-width: 0; }
.kpi-stackbar-seg.printed { background: var(--good); }
.kpi-stackbar-seg.duplicate { background: var(--warn); }
.kpi-stackbar-seg.notfound { background: var(--critical); }
.kpi-stackbar-seg.ambiguous { background: var(--ink-faint); }

/* QR vs MANUAL composition - a single proportion bar (2 segments), exact
   numbers always shown as plain text beside it, not just inside the fill. */
.kpi-propbar-track { display: flex; height: 22px; border-radius: var(--r-sm); overflow: hidden; gap: 2px; background: var(--surface-2); margin: 14px 14px 10px; }
.kpi-propbar-seg { height: 100%; width: var(--w, 0%); min-width: 0; }
.kpi-propbar-seg.qr { background: var(--chart-qr); }
.kpi-propbar-seg.manual { background: var(--chart-manual); }
.kpi-propbar-readout { display: flex; flex-wrap: wrap; gap: 18px; font-size: 13px; padding: 0 14px 14px; }
.kpi-propbar-readout strong { font-family: var(--font-mono); }

/* Top summary row - mockup's .stat-row-wide/.stat-block (2026-08-16),
   replacing this page's own flap-board use. Scoped to /admin/kpi only -
   the dashboard/сборка/run-detail flap-boards elsewhere on the site are
   untouched, this was not a site-wide redesign ask. */
.stat-row-wide { display: flex; flex-wrap: wrap; }
.stat-block { flex: 1; min-width: 170px; padding: 12px 16px; border-right: 1px solid var(--rule); }
.stat-block:last-child { border-right: none; }
.stat-label { font-size: 11.5px; font-weight: 600; color: var(--ink-muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.stat-val { display: block; margin-top: 3px; font-size: 19px; font-weight: 800; letter-spacing: -0.01em; }
@media (max-width: 640px) {
  .kpi-stackbar-name { width: 96px; flex-basis: 96px; font-size: 11px; }
  .stat-block { flex: 1 1 50%; border-right: none; border-bottom: 1px solid var(--rule); }
}

/* Custom date range + comparison picker (2026-08-16, reworked 2026-08-19 per
   direct user request "я хочу просто выбирать дни и сравнивать их... а
   сейчас у тебя вообще не логично сделано" - two separate toggle/forms, each
   its own datetime-local pair, replaced by ONE form: a plain date range plus
   a checkbox that reveals a second date range to compare against) - a
   period-tabs-style toggle that reveals native date inputs instead of a
   bespoke calendar widget (native picker = correct locale/keyboard/mobile
   behavior for free, same reasoning already applied to every other input on
   this site). Collapsed by default so the common case (the 3 presets) stays
   a single click, same "don't clutter the common path" spirit as MANUAL
   печать's own collapsible section on /scan. */
.kpi-range-form { display: none; align-items: center; gap: 8px 14px; flex-wrap: wrap; margin-top: 10px; }
.kpi-range-form.open { display: flex; }
.kpi-range-form label { font-size: 11px; color: var(--ink-muted); display: flex; align-items: center; gap: 6px; }
.kpi-range-block { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* "Было → Стало" comparison numbers (2026-08-19, per direct user request
   "выделить зеленым то число которое положительно... в случае негатива
   также покрасить и выделить") - the number itself carries the color, not
   just the small % chip next to it, so a glance at the value alone already
   reads good/bad. Reused for both the summary stat-row and the per-operator
   table's Δ cells - same two classes, same meaning everywhere on this page. */
.kpi-compare-positive { color: var(--good); }
.kpi-compare-negative { color: var(--critical); }
.kpi-range-block-label { font-size: 11px; font-weight: 600; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.02em; }
.kpi-compare-check { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink); cursor: pointer; }

/* Marketplace logo badge (static/logo-wb.svg, logo-ozon.svg) — a small,
   brand-colored wordmark, not a plain "WB"/"Ozon" text chip. Sized as an
   <img> so it scales as one crisp unit instead of relying on a background
   color plus separately-styled text staying in sync. */
.mp-logo { height: 21px; width: auto; vertical-align: middle; border-radius: var(--r-sm); flex: none; }
.mp-logo.small { height: 16px; margin-left: 6px; }
.mp-logo.xs { height: 13px; margin-left: 0; border-radius: var(--r-xs); }

/* Small marketplace+store tag shown per row in the QR/MANUAL scan lists and
   the scan history table (2026-08-09, per user request: "нужно будет
   добавить небольшую иконку напротив товара откуда (какой маркетплейс) и
   какой магазин"). */
.row-mp-tag { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-muted); white-space: nowrap; }
/* Which прогон a queued item belongs to (2026-08-09, per user request). A
   link, not a plain label: "Сформировать отгрузку (Ozon)" lives on the run's
   own page, so this is the shortest path from "here is the leftover" to "here
   is where I finish it" - and leftovers routinely span many runs at once. */
.row-run-tag {
  margin-left: 8px; font-family: var(--font-mono); font-size: 11px;
  color: var(--ink-faint); text-decoration: none; white-space: nowrap;
}
.row-run-tag:hover { color: var(--ink); text-decoration: underline; }

/* "Отгрузка" column on "История прогонов" (2026-08-17, per direct user
   request) - a run can have several WB supplies (one per store/kind), so
   this is a small stacked list, not a single value; each real link opens
   the actual WB seller-cabinet supply page in a new tab. */
.assembly-supply-list { display: flex; flex-direction: column; gap: 3px; font-size: 12px; }
.assembly-supply-list a { color: var(--accent); text-decoration: none; }
.assembly-supply-list a:hover { text-decoration: underline; }

/* Marketplace highlight — a store's own card/row gets a left accent stripe in
   its marketplace's real brand color (same --wb/--ozon as the logo badge and
   every other WB/Ozon-colored element), so which platform a row belongs to
   reads at a glance while scanning down a list, not just from the badge text. */
.mp-wb { border-left: 4px solid var(--wb); }
.mp-ozon { border-left: 4px solid var(--ozon); }
.mp-yandex { border-left: 4px solid var(--yandex); }
tr.mp-wb td:first-child { box-shadow: inset 4px 0 0 var(--wb); }
tr.mp-ozon td:first-child { box-shadow: inset 4px 0 0 var(--ozon); }
tr.mp-yandex td:first-child { box-shadow: inset 4px 0 0 var(--yandex); }

/* Store cards filled with their marketplace's own tint, not just the border
   stripe above (2026-08-09, per user request: "сами магазины блоки нужно
   заливать таким же цветом как и маркетплейс, чтобы это было наглядное"). */
.store-card.mp-wb { background: var(--wb-soft); }
.store-card.mp-ozon { background: var(--ozon-soft); }
.store-card.mp-yandex { background: var(--yandex-soft); }

/* Compact variant - auto-applied by the template once a marketplace panel
   holds many stores (2026-08-09, per user request: "что делать если у нас
   будет более 20 магазинов... может их сделать минималистичнее"). */
.store-card.compact { padding: 9px 11px; }
.store-card.compact .store-card-top { margin-bottom: 4px; }
.store-card.compact .store-card-name { font-size: 12px; }
.store-card.compact .store-card-count { font-size: 19px; margin-bottom: 5px; gap: 6px; }
.store-card.compact .store-card-count-label { font-size: 10.5px; }
.store-card.compact .store-card-meta { font-size: 11px; }
.store-card-breakdown { font-size: 12px; margin: 2px 0 6px; }
/* QR/MANUAL breakdown was hidden entirely in compact mode - per user
   request 2026-08-09, kept but shrunk instead of dropped, since it's real
   information an operator actually uses. */
.store-card.compact .store-card-breakdown { font-size: 10px; margin: 1px 0 4px; }

/* Marketplace-split layout - Dashboard and /assembly's store picker each
   divide their stores into a Wildberries panel and an Ozon panel instead of
   one mixed list (2026-08-09, per user request: "разделить по типам
   маркетплейсов"). Panels always read as the same size regardless of how
   many stores each actually has ("одинаково должно быть") - align-items:
   stretch plus a fixed (not max-) height on the body achieves that. */
.mp-split { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: stretch; margin-bottom: 28px; }
/* Only one marketplace panel present (e.g. no Ozon stores at all) - a
   single full-width column rather than one panel plus dead grid space. */
.mp-split.single { grid-template-columns: 1fr; }
.mp-panel { border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface);
  overflow: hidden; display: flex; flex-direction: column; }
.mp-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 13px 16px; border-bottom: 1px solid var(--rule); border-top: 3px solid transparent; }
.mp-panel-head.wb { border-top-color: var(--wb); }
.mp-panel-head.ozon { border-top-color: var(--ozon); }
.mp-panel-head.yandex { border-top-color: var(--yandex); }
/* STIKxA5 spans all three marketplaces (2026-08-27) - a neutral accent
   stripe, not a marketplace color, since this panel is a product line, not
   a store. */
.mp-panel-head.stik { border-top-color: var(--accent); }
.mp-panel-name { font-weight: 700; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.mp-panel-stat { font-size: 12px; color: var(--ink-muted); font-family: ui-monospace, Consolas, monospace; white-space: nowrap; }
.mp-panel-body { padding: 12px; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 9px; height: 460px; overflow-y: auto; align-content: start; }
.mp-panel-body.dense { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 7px; }
/* /assembly's picker holds .option-list rows, not a card grid - stacks as a
   single column and lets .option-list supply its own row borders/padding. */
.mp-panel-body.as-list { display: block; padding: 0; }
.mp-panel-body.as-list .option-list { border: none; box-shadow: none; border-radius: 0; background: transparent; }
/* Free-flowing content (download buttons - see assembly_detail.html's
   WB/Ozon download split) instead of a uniform card/row grid - no fixed
   height or scroll clamp, since each side's button list naturally varies
   in length. */
.mp-panel-body.auto { display: block; height: auto; overflow: visible; padding: 16px; }
.mp-panel-empty { padding: 20px 16px; color: var(--ink-muted); font-size: 13px; text-align: center; }
/* A panel-scoped action below its own list (e.g. Ozon's "Проверить «Ожидают
   отгрузки»" on /assembly) - previously sat full-width below BOTH panels,
   which read as belonging to whichever panel happened to be on the left
   (2026-08-09, per user report "кнопка как будто под wildberries"). Lives
   inside the .mp-panel it actually applies to instead. */
.mp-panel-foot { padding: 12px 16px 16px; border-top: 1px solid var(--rule); }
/* Mockup's own .panel-foot/.foot-hint/.stuck-results (2026-08-16, per user
   request) - used on /assembly's "Что войдёт в этот прогон" and Ozon
   panels, values copied verbatim from the mockup, not approximated. */
.panel-foot { padding: 11px 14px; border-top: 1px solid var(--rule); background: var(--surface-2); }
.foot-hint { font-size: 10.5px; color: var(--ink-muted); margin: 7px 0 0; line-height: 1.5; }
.foot-hint:first-child { margin-top: 0; }
.stuck-results { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
@media (prefers-reduced-motion: no-preference) { .stuck-results { animation: rowIn 250ms cubic-bezier(.22,.85,.3,1) both; } }
.stuck-result-row {
  display: flex; align-items: center; gap: 8px; font-size: 11.5px; font-weight: 500; color: var(--ink-muted);
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 7px 10px;
}
.stuck-result-row input { accent-color: var(--warn); flex: none; width: 15px; height: 15px; cursor: pointer; }
.stuck-result-row b { color: var(--ink); font-weight: 700; }
@media (max-width: 980px) {
  .mp-split { grid-template-columns: 1fr; }
  .mp-panel-body { height: auto; max-height: 460px; }
  .mp-panel-body.auto { max-height: none; }
}

/* ===================================================================
   Steel Ledger structural reskin round 2 (v0.9.77, 2026-08-15)

   v0.9.76 ported a handful of ISOLATED mockup components onto the existing
   template shape (admin-dropdown, route-track funnel, hero-bar, backlog-note,
   match-badges, thumb-preview) - the owner looked at the result next to the
   mockup and said it still reads as a different layout, not just a
   different palette, and they were right: the mockup wraps its three
   "Сводка" numbers in ONE bordered panel with an eyebrow+refresh-button head
   and vertical dividers, the mockup's store lists are a scannable 44px-row
   LIST (own scroll, not a growing card grid), the funnel dots are filled
   colour-coded icon circles with an arrow mid-connector (not empty outline
   circles), and /scan's store/run filters sit inline in the QR list panel's
   own header row, not a separate row above the whole page. This block adds
   the plain, reusable ".panel" wrapper the mockup uses everywhere (a lighter
   sibling of .mp-panel/.card - no marketplace top-border, no fixed grid
   body) plus the components layered on top of it, and restyles the funnel
   dots/connectors that v0.9.76 already wired up structurally.
   =================================================================== */

/* Generic bordered panel (mockup's ".panel") - dashboard's "Сводка" block,
   and anywhere else that wants "one eyebrow+content card" without the
   marketplace-tinted top border .mp-panel always adds. */
.panel { border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface); overflow: hidden; margin-bottom: 20px; }
.panel-head {
  min-height: 38px; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 16px; background: var(--surface-2); border-bottom: 1px solid var(--rule); flex-wrap: wrap;
}
.panel-head .eyebrow { margin: 0; }
.panel-head .btn { padding: 7px 14px; font-size: 12.5px; }
/* Two flex groups inside .panel-head - eyebrow+count on the left, filters/
   actions on the right (2026-08-16, per user request to redesign /problems
   and /stages/{name}: the mockup keeps a table's own filters inline in its
   panel's header row, never a separate bare row of <select> elements
   floating above/below the panel - see the ".panel wrapper" comment above
   for the fuller "already identified, never applied" context). */
.panel-head-left { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* .btn-input defaults to width:100% (correct for a .form-grid field, which
   owns its whole grid cell) - wrong here, where several selects need to
   sit side by side at their own natural width inside one flex row. Found
   live: without this, every filter select in this row stacked into its own
   full-width line instead of flowing inline like the mockup. */
.filters select.btn-input { width: auto; min-width: 140px; }
@media (max-width: 480px) { .filters select.btn-input { width: 100%; } }

/* "Сводка" - three numbers side by side inside ONE panel with dividers,
   not three separate bordered boxes with a gap between them (that read as
   three unrelated tiles rather than one summary, per the owner's mockup
   comparison). */
.stat-row-wide { display: flex; flex-wrap: wrap; }
.stat-block { flex: 1; min-width: 170px; padding: 14px 18px; border-right: 1px solid var(--rule); }
.stat-block:last-child { border-right: none; }
.stat-block .eyebrow { margin-bottom: 4px; }
.stat-block .stat-val { display: block; font-size: 22px; font-weight: 800; letter-spacing: -0.01em; font-family: var(--font-mono); }
.stat-block .stat-val.crit { color: var(--critical); }
.stat-block .stat-suffix { font-size: 13px; color: var(--ink-faint); margin-left: 4px; font-weight: 500; }
/* Small worded comparison line under a stat value (2026-09-07) - replaced a
   bare "-17%" chip the owner found unreadable ("как ты высчитал 17 процентов,
   от чего это?"). Muted by default; good/crit tone only when the direction
   itself matters. */
.stat-delta-note { display: block; margin-top: 3px; font-size: 10.5px; line-height: 1.3; color: var(--ink-muted); }
.stat-delta-note.good { color: var(--good); }
.stat-delta-note.crit { color: var(--critical); }
@media (max-width: 640px) {
  .stat-block { border-right: none; border-bottom: 1px solid var(--rule); }
  .stat-block:last-child { border-bottom: none; }
}

/* Store list as a compact 44px-row scrollable list (mockup's .list-body /
   .store-row) - replaces the store-card grid on the dashboard, whose own
   height grows without bound as stores are added (already fixed once, see
   v0.9.30's card-grid history, by capping the CARD grid's own height - this
   is a different, mockup-driven layout for the same bounded-height goal).
   Kept as a distinct class from .option-list (assembly's checkbox rows) -
   this one is read-only, no input, different row anatomy. */
.list-body { max-height: 380px; overflow-y: auto; }
.store-row {
  display: flex; align-items: center; gap: 11px; height: 44px; padding: 0 14px;
  border-bottom: 1px solid var(--rule); border-left: 3px solid transparent; text-decoration: none; color: inherit;
}
.store-row:last-child { border-bottom: none; }
.store-row:hover { background: var(--surface-2); }
.store-row.mp-wb { border-left-color: var(--wb); }
.store-row.mp-ozon { border-left-color: var(--ozon); }
.store-row.mp-yandex { border-left-color: var(--yandex); }
.store-row.state-crit { background: var(--critical-soft); }
.store-row.state-crit:hover { filter: brightness(0.97); }
/* Selectable variant (mockup's .store-row.selectable, 2026-08-16, per user
   request "ты просто сделал похоже") - /assembly's store checkboxes used a
   visually different component (.option-row/.option-row-label, the same
   generic pattern /settings/telegram uses) instead of reusing THIS row,
   which is what the mockup actually does (renderStoreSelectRow wraps the
   exact same .store-row anatomy in a <label> with a checkbox added, not a
   separate row language). An unchecked store dims - the same "what's
   selected" signal .option-row already gives via :has(), just opacity
   here since .store-row's own background is meaningful (state-crit, the
   marketplace border) and a solid accent tint would fight it. */
.store-row.selectable { cursor: pointer; }
.store-row.selectable:has(input:not(:checked)) { opacity: 0.45; }
.store-row-mark {
  width: 24px; height: 24px; border-radius: var(--r-sm); flex: none; display: flex; align-items: center; justify-content: center;
  padding: 3px;
}
.store-row-mark img { width: 100%; height: 100%; object-fit: contain; }
.store-row-main { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.store-row-name { display: block; font-size: 12px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.store-row-meta { display: block; font-size: 10.5px; color: var(--ink-muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.store-row-tags { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: none; }
.store-row-count { font-size: 15px; font-weight: 800; letter-spacing: -0.01em; font-family: var(--font-mono); }
.store-row-count-label { font-size: 9.5px; font-weight: 650; color: var(--ink-faint); margin-left: 4px; white-space: nowrap; text-transform: uppercase; }

/* Route-track funnel dots get a filled, per-stage icon colour + a real
   arrowhead mid-connector (mockup's .route-dot.tone-*) - v0.9.76 already
   built the connected-dots structure via macros.stage_board(), but every dot
   was the same empty outline regardless of stage, and the connector was a
   bare line. Tone is picked by stage NAME in the macro (data-tone attribute)
   so this stays declarative CSS, no per-page JS. */
.stage-row.route .stage-dot.tone-accent { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.stage-row.route .stage-dot.tone-warn { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
.stage-row.route .stage-dot.tone-good { background: var(--good-soft); border-color: var(--good); color: var(--good); }
.stage-row.route .stage-dot.tone-crit { background: var(--critical-soft); border-color: var(--critical); color: var(--critical); }
.stage-row.route .stage-dot svg { width: 16px; height: 16px; }
.stage-connector { position: relative; }
.stage-connector svg {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 13px; height: 13px; color: var(--ink-faint); background: var(--surface);
}
@media (max-width: 700px) { .stage-connector svg { display: none; } }

/* Page shell (mockup's .body-grid/.left-col/.right-col) - a narrow sticky
   "at a glance" left column plus a flexible right column of dense list
   panels. Used by /assembly and /scan. Dashboard's "Магазины" section was
   left on the old side-by-side .mp-split through v0.9.77 ("deliberately
   kept as-is") - the owner said this specific section still didn't match
   the mockup, and they're right: the mockup stacks Wildberries above Ozon
   in a narrower .left-col.equal column next to "Последние заказы" in
   .right-col, not two full-width panels side by side. Fixed in v0.9.78 -
   .mp-split remains in use elsewhere (/assembly/{id}'s download buttons),
   just not here anymore. */
.body-grid { display: flex; gap: 16px; align-items: flex-start; }
.left-col { width: 340px; flex: none; display: flex; flex-direction: column; gap: 16px; position: sticky; top: 60px; }
/* Equal-width variant (mockup's .left-col.equal) - dashboard's store lists
   need real room for two full-height panels, unlike /assembly's or /scan's
   narrow at-a-glance sidebar, so this drops the fixed 340px/sticky and
   grows like .right-col instead. */
.left-col.equal { width: auto; flex: 1; position: static; }
.right-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.count-pill {
  font-family: ui-monospace, Consolas, monospace; font-size: 11px; font-weight: 700; color: var(--ink);
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 1px 7px;
}
@media (max-width: 1100px) {
  .body-grid { flex-direction: column; align-items: stretch; }
  .left-col { width: 100%; position: static; }
}

/* "Что войдёт в этот прогон" - compact divided rows inside one .panel,
   mockup's .stat-list. */
.stat-list .stat-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--rule); }
.stat-list .stat-row:last-child { border-bottom: none; }
.stat-list .stat-label { font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.stat-list .stat-val { font-size: 18px; font-weight: 800; letter-spacing: -0.01em; font-family: var(--font-mono); }

/* Plain info note inside a .panel (mockup's .explainer) - /assembly's "what
   this button does" text, now wrapped in the left column instead of a bare
   <p class="hint"> above the form. */
.explainer { display: flex; gap: 10px; padding: 13px 15px; font-size: 12.5px; line-height: 1.55; color: var(--ink-muted); }
.explainer svg { width: 16px; height: 16px; flex: none; margin-top: 1px; color: var(--accent); }
.explainer code { font-family: var(--font-mono); background: var(--surface-2); padding: 1px 4px; border-radius: var(--r-xs); font-size: 11px; color: var(--ink); }
.explainer b { color: var(--ink); }

.btn {
  background: var(--accent); color: var(--accent-ink); border: none; border-radius: var(--r-sm);
  padding: 8px 15px; font-size: 12.5px; font-weight: 650; cursor: pointer;
  display: inline-block; text-decoration: none; line-height: 1.3;
  transition: background-color 120ms ease, transform 120ms ease;
}
.btn:hover { background: var(--accent-deep); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.5; cursor: default; transform: none; }

/* Download buttons on /assembly/{id} tinted by marketplace (2026-08-07, per
   user request) — same --wb/--ozon brand colors as .chip.wb/.chip.ozon and
   the store-row accent stripe, so which platform a file belongs to reads at
   a glance in a row of otherwise-identical buttons. */
.btn.wb { background: var(--wb); }
.btn.wb:hover { filter: brightness(0.9); }
.btn.ozon { background: var(--ozon); }
.btn.ozon:hover { filter: brightness(0.9); }
.btn.yandex { background: var(--yandex); color: var(--yandex-ink); }
.btn.yandex:hover { filter: brightness(0.92); }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
}

a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.scan-input:focus-visible { outline: none; }

/* Custom checkbox — the bare OS widget (tiny, square, browser-default blue)
   was the single biggest thing making an otherwise custom-built interface
   read as unfinished. Sized and shaped to match the rest of the system
   (10px radius family, --accent fill), not an OS control dropped into it. */
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0; flex: none;
  width: 19px; height: 19px; border-radius: var(--r-sm);
  border: 1.5px solid var(--rule-strong); background: var(--surface);
  display: inline-grid; place-content: center; cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}
input[type="checkbox"]::before {
  content: ""; width: 11px; height: 11px; background: var(--accent-ink); transform: scale(0);
  transition: transform 100ms ease;
  clip-path: polygon(11% 46%, 0% 62%, 38% 100%, 100% 21%, 84% 6%, 37% 73%);
}
input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
input[type="checkbox"]:checked::before { transform: scale(1); }
input[type="checkbox"]:hover { border-color: var(--accent); }
input[type="checkbox"]:disabled { cursor: default; opacity: 0.5; }

/* Collapsible WB/Ozon tables on /assembly/{id} (2026-08-08, per user request:
   many stores made these tables take up the whole screen). Restyled
   2026-08-17 (Прогон page redesign, per direct user request) to the
   mockup's own <summary class="panel-head"> pattern (a .panel wrapping a
   collapsible section, count-pill + rotating chevron instead of a bare
   .btn-styled summary) - .section-details/.section-summary retired, this
   was their only use site-wide. */
summary.panel-head { cursor: pointer; list-style: none; }
summary.panel-head::-webkit-details-marker { display: none; }
.details-chevron { width: 16px; height: 16px; flex: none; transition: transform 0.15s; color: var(--ink-faint); }
details[open] > summary.panel-head .details-chevron { transform: rotate(180deg); }

/* Прогон page header (2026-08-17) - back-link above the title, chips row
   under it (date range + mode/status, previously a loose inline-styled
   <span> outside .page-head-text). */
.back-link {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 650;
  color: var(--ink-muted); text-decoration: none; margin-bottom: 6px;
}
.back-link svg { width: 13px; height: 13px; }
.back-link:hover { color: var(--ink); }
.run-detail-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 6px 0; }

.download-groups { display: flex; flex-direction: column; gap: 10px; margin: 4px 0 16px; }
.download-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.download-row-label { font-size: 12.5px; font-weight: 600; color: var(--ink-muted); flex: none; }

.actions { display: flex; gap: 12px; }
.inline-form { display: inline; }
.link-btn {
  background: none; border: none; color: var(--accent); font-size: 13px; cursor: pointer; padding: 0; text-decoration: underline;
}
.link-btn.danger { color: var(--critical); }

.card {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md); padding: 22px;
  max-width: 460px; box-shadow: var(--shadow-1);
}
.form { display: flex; flex-direction: column; gap: 14px; }
.form label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; color: var(--ink-muted); }
.form input, .form select {
  font-size: 14px; padding: 8px 10px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink); font-family: inherit;
  transition: border-color 120ms ease;
}
.form input:hover, .form select:hover { border-color: var(--accent); }
.form #ozon-fields, .form #wb-fields { display: flex; flex-direction: column; gap: 14px; }

.login-page {
  min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px;
  position: relative; overflow: hidden; background: var(--bg);
}
.login-page::before {
  /* faint scan-grid dots, in keeping with the product's own subject matter */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(var(--rule) 1px, transparent 1px);
  background-size: 28px 28px;
  opacity: 0.6;
  -webkit-mask-image: radial-gradient(circle at var(--mx, 50%) var(--my, 38%), #000 0%, transparent 62%);
          mask-image: radial-gradient(circle at var(--mx, 50%) var(--my, 38%), #000 0%, transparent 62%);
}
.login-page::after {
  /* soft accent glow that tracks the cursor via --mx/--my, set in login.html */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(480px circle at var(--mx, 50%) var(--my, 38%), var(--accent-soft), transparent 70%);
  opacity: 0.9; transition: opacity 400ms ease;
}
.login-card { width: 100%; position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .login-page::after { transition: none; }
}

td select {
  font-size: 13px; padding: 5px 8px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink); font-family: inherit;
}
.link-btn:disabled { color: var(--ink-muted); cursor: default; text-decoration: none; }

/* Row-edit popovers (reset password, edit store) render in a <dialog> instead
   of an absolutely/inline positioned popover: a <dialog> paints in the
   browser's top layer, so it can never be clipped by an ancestor's overflow
   or covered by unrelated stacking contexts the way a plain positioned div
   inside a table cell can (reported broken in practice on v0.5.1). Full
   dialog.modal/.modal-head/.modal-body/.modal-foot styling now lives with
   the rest of the admin-pages port near the end of this file (mockup's
   #page-stores/#page-users) - was a flat h3+.form+.modal-actions body here,
   restyled to the mockup's header/body/footer sandwich, mechanism unchanged. */

/* .banner (mockup's own component, "Дашборд error/warning strips") - Прогон
   page redesign (2026-08-17, per direct user request to bring the whole
   "Прогон" page and its "Журнал прогона" closer to the mockup). A new,
   separate component from .error-banner/.warn-banner below (those are used
   on 4 other unrelated templates, left untouched) - this one adds real
   good/neutral tones those two never had, needed for the journal's tone
   coding (see main.py's _group_run_error_lines) and for the conditional
   action banners at the top of a run's page. flex row so an inline action
   (a real <form><button>, not just a link - these trigger real mutating
   requests, unlike the mockup's plain demo link) sits naturally at the end. */
.banner {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 9px 12px; border-radius: var(--r-sm);
  font-size: 12.5px; line-height: 1.45; border: 1px solid var(--rule); margin-bottom: 8px;
}
.banner:last-child { margin-bottom: 0; }
.banner.crit { background: var(--critical-soft); color: var(--critical); border-color: var(--critical); }
.banner.warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn); }
.banner.good { background: var(--good-soft); color: var(--good); border-color: var(--good); }
.banner.neutral { background: var(--surface-2); color: var(--ink-muted); }
.banner > span:first-child { flex: 1; min-width: 0; }
.banner form { flex: none; }
.banner .btn, .banner .btn-line { flex: none; white-space: nowrap; }
/* Grouped journal entries (2026-08-17) - the banner itself is a <summary>,
   clickable to reveal every original line underneath (same "count badge +
   collapsible full list" shape as /stages' journal grouping). */
details.banner-details { margin-bottom: 8px; }
details.banner-details:last-child { margin-bottom: 0; }
details.banner-details > summary.banner { cursor: pointer; list-style: none; margin-bottom: 0; }
details.banner-details > summary.banner::-webkit-details-marker { display: none; }
details.banner-details .banner-lines {
  margin: 4px 0 0; padding: 8px 10px; font-family: var(--font-mono); font-size: 11px;
  white-space: pre-wrap; max-height: 220px; overflow-y: auto; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r-sm); opacity: 0.85;
}
.banner-count { font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; flex: none; }
@media (max-width: 560px) {
  /* .banner > span:first-child keeps min-width:0 so a form/button sharing the
     row never overflows - but that also lets it shrink forever instead of
     wrapping (flex only wraps once shrinking is exhausted). On a narrow
     screen with a real <form><button> inside (the 4 conditional action
     banners on /assembly/{id}), that squeezed the message into a single
     narrow ragged column next to the button instead of stacking (found on
     390px during the Прогон redesign, 2026-08-17). Stack instead. */
  .banner { flex-direction: column; align-items: stretch; }
  .banner form, .banner .btn, .banner .btn-line { width: 100%; text-align: center; }
}

.error-banner {
  background: var(--critical-soft); color: var(--critical); border-radius: var(--r-sm);
  padding: 10px 12px; font-size: 13.5px; margin-bottom: 14px;
}
.warn-banner {
  background: var(--warn-soft); color: var(--warn); border-radius: var(--r-sm);
  padding: 10px 12px; font-size: 13.5px; margin-bottom: 14px;
}

/* Scan-station layout v2 (2026-08-09, per user request): the input moved to
   a full-width block at the very top (was sharing a column with the match
   card), the preview/lists sit in a stretched two-column row below it, and
   history moved to a full-width section at the bottom - was previously
   crammed into the same narrow side column as the store/run pickers. */
/* Search bar rebuilt to match the mockup's own .scan-input verbatim
   (2026-08-16, per user request "строка ввода номера... выглядит не так") -
   was a big generic 28px/2px-border/10px-radius box with an absolutely
   positioned icon overlay; the mockup is a compact 56px flex row, icon and
   input inline, 1px hairline border, 5px radius, and the typed text itself
   is accent-coloured/semibold, not plain ink - reads as a purpose-built
   instrument, not a generic form field. */
.scan-input-wrap {
  height: 56px; background: var(--surface); border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  display: flex; align-items: center; padding: 0 18px; gap: 14px; transition: border-color 120ms ease, box-shadow 120ms ease;
  margin-bottom: 20px;
}
.scan-input-wrap:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.scan-icon { width: 20px; height: 20px; color: var(--ink-muted); flex: none; }

.scan-filter-row { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.scan-store-picker { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--ink-muted); min-width: 200px; flex: 1; }
.scan-store-picker select {
  font-size: 14px; padding: 8px 10px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); font-family: inherit;
}

/* /scan's top section now reuses the generic .body-grid/.left-col/.right-col
   shell (v0.9.78) - match card + both progress rings stacked in the narrow
   340px left-col, QR/MANUAL/history panels in the wide right-col, matching
   the mockup's grouping. The previous scan-body-grid/scan-right-col put the
   match card and progress rings in one WIDE top row instead - visually
   nothing like the mockup despite each individual component being
   "restyled". .progress-row's existing minmax(232px, 1fr) auto-fit already
   stacks the two rings vertically for free inside a 340px column (2×232px
   doesn't fit side by side), no extra rule needed here. */

/* Ring panels rebuilt on the mockup's own .ring-panel/.panel-head/
   .ring-panel-body/.progress-svg/.ptrack/.pfill/.rc-num/.rc-frac (v0.9.83,
   per user request quoting the mockup's exact text back at us) - was a
   flat .progress-card row (ring left, label+value right) at a 76px ring;
   the mockup is a proper two-row .panel (head: eyebrow+pace; body: a
   smaller 74px/r=31 ring beside the number), and the label text itself was
   wrong ("Прогресс сборки" vs the mockup's "Прогресс — QR"). */
.ring-panel-body { padding: 14px 16px; display: flex; align-items: center; gap: 16px; }
.progress-svg circle { fill: none; }
.progress-svg .ptrack { stroke: var(--rule); stroke-width: 7; }
.progress-svg .pfill {
  stroke: var(--accent); stroke-width: 7; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 37px 37px;
  transition: stroke-dashoffset 1s cubic-bezier(.22,.85,.3,1), stroke 300ms ease;
}
/* Progress bands (2026-08-10, per user request: "сделай кольцо в нескольких
   цветах соответствуя проценту заполнения") - a real feature beyond the
   mockup's own single-colour ring, kept because removing a previously
   requested feature to chase mockup fidelity would be a regression, not a
   fix. Discrete steps, not a gradient - a shade cannot distinguish 55% from
   62%, but three states land instantly. The percentage always sits inside
   the ring, so colour reinforces a number that is always present, never
   carries the meaning alone. */
.progress-svg .pfill.lvl-low  { stroke: var(--critical); }
.progress-svg .pfill.lvl-mid  { stroke: var(--warn); }
.progress-svg .pfill.lvl-high { stroke: var(--good); }
.progress-svg .pfill.lvl-done { stroke: var(--good); }
.rc-num.lvl-low  { color: var(--critical); }
.rc-num.lvl-mid  { color: var(--warn); }
.rc-num.lvl-high { color: var(--good); }
.rc-num.lvl-done { color: var(--good); }
/* 100% earns one extra cue beyond hue: the track fills in too, so the ring
   reads as a solid disc outline rather than a nearly-closed arc. */
.progress-svg .pfill.lvl-done { stroke-width: 9; }
.ring-panel:has(.lvl-done) .progress-svg .ptrack { stroke: var(--good-soft); }
@media (prefers-reduced-motion: reduce) { .progress-svg .pfill { transition: none; } }
.rc-num { font-size: 27px; font-weight: 800; letter-spacing: -0.01em; }
.rc-frac { font-size: 11.5px; color: var(--ink-muted); margin-top: 3px; }
/* Pace badge (mockup's .pace, "▲ +38/ч") - was entirely missing, 2026-08-16. */
.panel-head .pace { margin-left: auto; font-size: 11px; }
.pace { font-weight: 700; }
.pace.good { color: var(--good); }
/* Ring panel pulse on every scan result (mockup's ringPulseOnce, v0.9.83) -
   was entirely missing; triggered from scan.html's flashMatch() alongside
   the match card's own flash. */
@keyframes ring-pulse-once {
  0% { filter: drop-shadow(0 0 0 rgba(0,0,0,0)); }
  40% { filter: drop-shadow(0 0 5px var(--accent-soft)); }
  100% { filter: drop-shadow(0 0 0 rgba(0,0,0,0)); }
}
@media (prefers-reduced-motion: no-preference) {
  .ring-panel.pulse .progress-svg { animation: ring-pulse-once 700ms ease; }
}

/* .list-block/.list-block-head/.scan-count-*/.list-sort-* removed
   (2026-08-16) - /scan was the only user and is now rebuilt on the shared
   .panel/.panel-head/.eyebrow/.count-pill/.filters components everywhere
   else on the site already uses (see scan.html's own history note on the
   right-col panels). This bordered-container-fused-with-its-own-list shape
   itself is preserved by .panel; only the bespoke class names died. */
.panel .option-list { border: none; box-shadow: none; border-radius: 0; background: transparent; }
.panel .table-wrap { border: none; box-shadow: none; border-radius: 0; }

/* MANUAL печать - closed by default, expands inside its own .panel via
   the grid-template-rows 0fr -> 1fr trick (2026-08-09, per user request:
   "этот список мы не раскрываем чтобы посмотреть или отпечатать"). */
.dropdown-trigger { cursor: pointer; }
.dropdown-chevron { color: var(--ink-muted); transition: transform 220ms ease; flex: none; }
.dropdown.open .dropdown-chevron { transform: rotate(180deg); }
.dropdown-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 280ms ease; }
.dropdown-body > div { overflow: hidden; }
.dropdown.open .dropdown-body { grid-template-rows: 1fr; }
@media (prefers-reduced-motion: reduce) { .dropdown-body { transition: none; } }
.manual-actions { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 10px 16px; border-bottom: 1px dashed var(--rule); }

/* .scan-status's base display:flex/align-items:center is meant for a short,
   single-line status label - reused here for a full sentence containing an
   inline <a> (missing-sticker-hint on scan.html), flex was splitting the
   text and the link into separate boxes instead of flowing as one paragraph
   (found in UX audit 2026-08-07). display:block lets it wrap normally. */
.scan-side-hint { display: block; font-size: 13px; padding: 12px 14px; margin: 0 0 14px; line-height: 1.5; }
.scan-history-wrap { max-height: 460px; overflow-y: auto; }
.scan-history-wrap table { font-size: 13px; }

.scan-input {
  border: none; outline: none; background: transparent; flex: 1; min-width: 0;
  font-size: 17px; color: var(--accent); font-weight: 600;
}
.scan-input::placeholder { color: var(--ink-faint); font-weight: 500; }

.scan-status {
  font-size: 20px; font-weight: 700; padding: 20px 22px; border-radius: var(--r-md); margin-bottom: 20px;
  min-height: 28px; display: flex; align-items: center;
}
/* Neutral = nothing to report yet (idle / cleared input) - the only state
   that's pure filler, so it collapses to nothing instead of taking up a
   permanent 20px-padded banner's worth of space (2026-08-09, per user
   request: "плашку убери, она не особо нужна"). Every real outcome
   (success/warning/error/info) uses a different class and stays visible -
   this only ever hides the idle placeholder text itself. */
.scan-status-neutral { display: none; }
.scan-status-info { background: var(--accent-soft); color: var(--accent); }
.scan-status-warning { background: var(--warn-soft); color: var(--warn); }
.scan-status-error { background: var(--critical-soft); color: var(--critical); }
.scan-status-success { background: var(--good-soft); color: var(--good); }

/* Match card rebuilt on the mockup's own class names verbatim (v0.9.83,
   per user request quoting the mockup's exact text back at us) -
   .match-panel/.match-top/.label/.article-code/.order-num/.match-photo,
   not our own .scan-match-* naming with different sizes/colours. Tone
   still applies via the JS-facing state-success/state-error/state-duplicate
   classes (renaming those would touch scan.html's JS for zero visual gain)
   - only the border treatment changed: 3px left-border colour on an
   otherwise plain white .panel, not a full-card tint (this had
   border-left-width:10px + a *-soft background fill before, much louder
   than the mockup's restrained look). Colour is still never the only
   signal - the tone chip in .match-top and the WB/Ozon status chip both
   spell the state out in text regardless. */
.match-panel {
  display: flex; flex-direction: column; align-items: stretch; background: var(--surface);
  border: 1px solid var(--rule); border-left-width: 3px; border-left-color: var(--rule);
  padding: 14px; margin-bottom: 0; min-height: 0;
  transition: border-left-color 300ms ease;
}
.match-panel.state-success { border-left-color: var(--good); }
.match-panel.state-error { border-left-color: var(--critical); }
.match-panel.state-duplicate { border-left-color: var(--warn); }
.match-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
@keyframes scan-match-flash { 0% { box-shadow: 0 0 0 0 var(--accent-soft); } 100% { box-shadow: 0 0 0 0 rgba(0,0,0,0); } }
@keyframes scan-match-text-pop { 0% { transform: translateY(-3px); opacity: 0.4; } 100% { transform: translateY(0); opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .match-panel.flash { animation: scan-match-flash 700ms ease; }
  .match-panel.flash .article-code,
  .match-panel.flash .order-num { animation: scan-match-text-pop 350ms ease; }
}

/* Photo box (mockup's .match-photo) - dashed border, constant regardless of
   match state (the mockup's box never recolours per tone; the dashed
   stroke itself signals "content goes here"). Fills the column's own
   width via aspect-ratio. Positioned, for .match-badge-pop below. */
.match-photo {
  position: relative; width: 100%; aspect-ratio: 1 / 1; flex: none; margin-top: 12px;
  border-radius: var(--r-md); background: var(--surface-2); border: 1px dashed var(--rule-strong);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.scan-photo { width: 100%; height: 100%; object-fit: contain; }
.scan-photo-placeholder { color: var(--ink-muted); font-size: 13px; text-align: center; padding: 0 12px; }
.match-panel.state-error .scan-photo-placeholder { color: var(--critical); font-size: 40px; }
/* Small tone-coloured icon badge that pops over the photo's corner on a
   match (mockup's .match-badge-pop) - was entirely absent. One-shot: shown
   for 1.5s then fades, triggered from scan.html alongside flashMatch(). */
.match-badge-pop {
  position: absolute; top: 8px; right: 8px; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff; opacity: 0; transform: scale(0.4);
  transition: opacity 250ms ease, transform 250ms cubic-bezier(.34,1.56,.64,1); pointer-events: none;
  box-shadow: var(--shadow-2);
}
.match-badge-pop.show { opacity: 1; transform: scale(1); }
.match-badge-pop svg { width: 17px; height: 17px; }
@media (prefers-reduced-motion: reduce) { .match-badge-pop { transition: opacity 250ms ease; } }

/* Field caption (mockup's own generic .label) and value styles, sizes/
   colours copied verbatim - was 32px/700 black for the article and 16px
   muted for the order number; mockup is a smaller 23px article, and the
   order number is accent-blue like a real reference, not muted grey. */
.label { font-size: 10px; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }
.article-code { font-size: 23px; font-weight: 750; line-height: 1.2; margin-top: 5px; letter-spacing: -0.01em; word-break: break-all; }
.order-num { font-size: 13.5px; color: var(--accent); margin-top: 3px; font-weight: 600; }

/* --- Error pages (404/403/409/500/502...) -----------------------------
   2026-08-09, per user request ("для каждой ошибки по типу 404 или 502
   сделай красивый дизайн"). Before this, a wrong URL returned the raw
   `{"detail":"Not Found"}` JSON and a downed app showed nginx's stock grey
   page - neither looked like the rest of the product.

   Standalone, like .login-page: error.html cannot extend base.html because
   base needs `user`/`line_status`, which are exactly what may be missing
   when something has gone wrong. The same rule applies doubly to the static
   nginx 502/504 page, which has to survive the app being down entirely -
   it therefore inlines its own copy of these few rules rather than linking
   /static/style.css (which nginx does still serve, but a stylesheet that
   loads while the backend is dead is a coincidence, not a guarantee). */
.error-page {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--bg); position: relative; overflow: hidden;
}
.error-page::before {
  /* the same faint scan-grid motif the login page uses */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(var(--rule) 1px, transparent 1px);
  background-size: 28px 28px; opacity: 0.55;
  -webkit-mask-image: radial-gradient(circle at 50% 40%, #000 0%, transparent 65%);
          mask-image: radial-gradient(circle at 50% 40%, #000 0%, transparent 65%);
}
.error-card {
  position: relative; z-index: 1; width: 100%; max-width: 520px; text-align: center;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: 36px 32px 30px; box-shadow: var(--shadow-2);
}
/* Reuses the flap-board look so an error still reads as part of this product
   rather than a generic framework page. */
.error-code {
  display: inline-flex; gap: 6px; margin: 14px 0 18px;
  background: var(--flap-face); padding: 12px 16px; border-radius: var(--r-md);
}
.error-code-char {
  font-family: var(--font-mono); font-weight: 700; font-size: 44px; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em; color: var(--warn);
}
.error-title { font-size: 21px; margin: 0 0 10px; }
.error-text { margin: 0 auto; max-width: 40ch; color: var(--ink-muted); font-size: 14px; line-height: 1.55; }
.error-hint { margin: 12px auto 0; max-width: 44ch; color: var(--ink-faint); font-size: 12.5px; line-height: 1.5; }
.error-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 24px; }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--rule-strong); }
.btn.ghost:hover { background: var(--accent-soft); }
@media (max-width: 480px) {
  .error-card { padding: 28px 20px 24px; }
  .error-code-char { font-size: 34px; }
}

/* "Где сейчас товар" — the live marketplace funnel on a run's page
   (2026-08-10). Five stages side by side, each showing the combined count
   with the per-marketplace split underneath; stages with nothing in them stay
   visible but recede, so the shape of the funnel is readable at a glance and
   does not jump around as items move between stages. */
.stage-board {
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface);
  box-shadow: var(--shadow-1); margin: 0 0 16px; overflow: hidden;
}
.stage-board-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 11px 16px; border-bottom: 1px solid var(--rule);
}
.stage-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.stage {
  display: flex; flex-direction: column; gap: 4px; padding: 12px 16px;
  border-right: 1px solid var(--rule);
}
.stage:last-child { border-right: none; }
.stage-empty { opacity: 0.45; }
.stage-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-muted); font-family: var(--font-mono); }
.stage-total { font-size: 24px; font-weight: 700; line-height: 1.1; }
.stage-split { display: flex; gap: 5px; flex-wrap: wrap; font-size: 11px; }
@media (max-width: 700px) {
  .stage { border-right: none; border-bottom: 1px solid var(--rule); }
  .stage:last-child { border-bottom: none; }
}
/* A two-stage board (the /assembly queue: Wildberries | Ozon) still fits side
   by side at 390px, but the rule above drops the vertical divider there
   because it assumes stacking - so the two groups run together with nothing
   between them. Stack them explicitly instead: each gets the full width and
   the horizontal divider the rule already provides. */
@media (max-width: 700px) {
  .stage-row.pair { grid-template-columns: 1fr; }
}
/* Dashboard KPI tiles reuse .stage — a trailing "/ 27" and a critical count
   are the only two things they need beyond the funnel's own vocabulary. */
.stage-suffix { font-size: 14px; color: var(--ink-faint); margin-left: 5px; font-weight: 500; }
.stage-total.stage-crit { color: var(--critical); }
/* Clickable funnel tiles (2026-08-11, per user request: "чтобы мы могли
   прямо посмотреть, отсортировать по магазину, по типу" - the funnel used
   to be read-only, its explanation only in an invisible hover title). The
   whole tile is now a link to the drill-down list; the small circled "i" is
   a VISIBLE affordance for that same explanation - a title attribute alone
   is undiscoverable, especially on touch. */
a.stage { text-decoration: none; color: inherit; position: relative; cursor: pointer; }
a.stage:hover, a.stage:focus-visible { background: var(--overlay-hover); }
.stage-info {
  position: absolute; top: 10px; right: 12px; width: 15px; height: 15px; border-radius: 50%;
  background: var(--surface-2); color: var(--ink-muted); font-size: 10px; font-style: italic;
  font-family: Georgia, 'Times New Roman', serif; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}

/* Inline hover-for-explanation "i" (2026-09-07, per owner request on
   /admin/kpi: "добавь иконки i чтобы наводить и напиши там... что это за
   расчет"). Same visual language as .stage-info but flows inline next to a
   label instead of pinning to a panel corner. The title attribute carries
   the annotation - matches every other hover-hint on this site. */
.info-i {
  display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
  width: 14px; height: 14px; margin-left: 5px; border-radius: 50%; flex: none;
  background: var(--surface-2); color: var(--ink-muted);
  font: italic 700 9px/1 Georgia, 'Times New Roman', serif; cursor: help;
}
.info-i:hover { background: var(--accent-soft); color: var(--accent); }

/* ===================================================================
   Steel Ledger structural reskin (v0.9.76, 2026-08-15)

   v0.9.75 ported only the mockup's COLOR tokens (:root custom properties)
   into this file - explicitly not touching component structure, on the
   belief the remaining gap was purely visual/temperature. That belief was
   wrong for a handful of real components the mockup has and this site
   didn't: a route-style stage funnel, a hover-zoom photo preview, a top
   hero summary+action bar, match-card store/run badges, a clickable
   backlog note, and a grouped admin nav dropdown. Everything below adds
   those - reusing/restyling the EXISTING functional component under each
   one wherever a live equivalent already existed (stage_board() macro for
   the funnel, .option-row-thumb for the photo, the existing hint paragraph
   for the backlog note) rather than building a parallel system. See
   _macros.html / assembly.html / scan.html / base.html for the matching
   template-side changes.
   =================================================================== */

/* --- Admin nav dropdown (base.html) --------------------------------
   Collapses the 8 flat admin links (previously a bare .nav-group) into one
   grouped, closed-by-default dropdown - matches the mockup's .admin-dropdown.
   The 4 operator links above stay flat; 4 items never needed grouping. */
.admin-menu { position: relative; flex: none; }
.admin-toggle {
  font-size: 12.5px; font-weight: 650; color: var(--ink-muted); background: none; border: none;
  padding: 7px 11px; border-radius: var(--r-sm); cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px; font-family: inherit;
}
.admin-toggle:hover { color: var(--ink); background: var(--surface-2); }
.admin-toggle.active { color: var(--accent); background: var(--accent-soft); }
.admin-toggle svg { width: 12px; height: 12px; flex: none; transition: transform 150ms ease; }
.admin-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.admin-dropdown {
  position: absolute; top: calc(100% + 10px); left: 0; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r-md); box-shadow: var(--shadow-2);
  padding: 6px; min-width: 190px; opacity: 0; transform: translateY(-4px);
  pointer-events: none; transition: opacity 140ms ease, transform 140ms ease; z-index: 40;
}
.admin-dropdown.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.admin-dropdown a {
  display: block; font-size: 13.5px; color: var(--ink); text-decoration: none;
  padding: 8px 10px; border-radius: var(--r-sm); border-bottom: none;
}
.admin-dropdown a:hover { background: var(--overlay-hover); }
.admin-dropdown a.active { color: var(--accent); font-weight: 600; }
.admin-dropdown-sep { height: 1px; background: var(--rule); margin: 5px 4px; }
/* Меню раскрывается ВЛЕВО от кнопки: при `left: 0` его правый край уходил за
   экран и давал горизонтальную прокрутку ВСЕЙ страницы (на 820px — 42px
   лишних, найдено обходом 2026-09-30). Раскрытие влево безопасно на любой
   ширине: кнопка «Админ» всегда в правой половине шапки. */
.admin-dropdown { left: auto; right: 0; max-width: calc(100vw - 24px); }

/* --- Route-track funnel (macros.stage_board(), used by dashboard.html's
   "ГДЕ СЕЙЧАС ТОВАР" and assembly_detail.html's run-page funnel) ------
   Connected dot/line nodes instead of the boxed columns .stage-row still
   uses everywhere else (dashboard's own 3-tile summary, /assembly's queue
   pair - both written inline in their templates, not through this macro,
   so they're untouched). Scoped entirely to the .route modifier. */
.stage-row.route { display: flex; align-items: flex-start; flex-wrap: nowrap; overflow-x: auto; padding: 6px 8px; }
.stage-row.route .stage {
  flex: 1 1 0; min-width: 118px; border-right: none;
  flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 14px 8px;
}
.stage-row.route .stage-dot {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  background: var(--surface-2); border: 2px solid var(--rule-strong);
  display: flex; align-items: center; justify-content: center;
  transition: transform 120ms ease, border-color 120ms ease, background-color 120ms ease;
}
.stage-row.route a.stage:hover .stage-dot { transform: scale(1.1); }
.stage-row.route .stage:not(.stage-empty) .stage-dot { border-color: var(--accent); background: var(--accent-soft); }
.stage-row.route .stage-empty .stage-dot { opacity: 0.5; }
.stage-row.route .stage-split { justify-content: center; }
.stage-connector { flex: 0 0 auto; width: clamp(16px, 4vw, 40px); height: 2px; background: var(--rule-strong); margin-top: 28px; }
@media (max-width: 700px) {
  .stage-row.route { flex-direction: column; overflow-x: visible; padding: 0; }
  .stage-row.route .stage { flex-direction: row; text-align: left; align-items: center; justify-content: flex-start; border-bottom: 1px solid var(--rule); padding: 10px 12px; }
  .stage-row.route .stage:last-child { border-bottom: none; }
  .stage-row.route .stage-split { justify-content: flex-start; }
  .stage-connector { display: none; }
}

/* --- Hero bar (assembly.html: live selection summary + run CTA, top of
   page; also suits any page needing a one-line summary+action strip). --- */
.hero-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  min-height: 54px; padding: 10px 18px; margin: 0 0 20px;
  background: var(--surface); border: 1px solid var(--rule-strong); border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}
.hero-icon { width: 20px; height: 20px; color: var(--ink-muted); flex: none; }
.hero-text { flex: 1; font-size: 13.5px; font-weight: 600; color: var(--ink); min-width: 200px; }
.btn.btn-run { flex: none; white-space: nowrap; background: var(--critical); }
.btn.btn-run:hover { filter: brightness(0.92); }
@media (max-width: 640px) {
  .hero-bar { padding: 10px 14px; }
}

/* --- Backlog note (assembly.html: link to /problems) - the content/link/
   data already lived here as a plain <p class="hint">; this only wraps it
   into a warm-tinted, whole-row-is-a-link card so it reads as "go here"
   rather than one more line of body text next to the store checkboxes. --- */
.backlog-note {
  display: flex; gap: 10px; align-items: flex-start; text-decoration: none; cursor: pointer;
  background: var(--warn-soft); border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: 12px 14px; margin: -8px 0 18px; color: var(--ink); font-size: 13.5px; line-height: 1.5;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.backlog-note:hover { border-color: var(--warn); }
.backlog-note-icon { width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--warn); }
.backlog-note strong { color: inherit; }
.backlog-arrow { display: inline-block; margin-left: 4px; font-weight: 700; color: var(--accent); white-space: nowrap; }

/* --- Match-card badges (scan.html: store + run chips inside .scan-match,
   alongside the existing plain-text status banner - not a replacement). --- */
.match-badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }

/* --- Thumb-preview hover-zoom (scan.html: QR/MANUAL row thumbnails,
   .option-row-thumb) - one shared floating tooltip, real photo (same
   /scan/photo/{id} src the small thumbnail already uses), not the mockup's
   fake category-icon guess. Colours deliberately inverted relative to the
   page's own ink/bg pair in both themes (dark popup on the light theme,
   light popup on the dark theme) - a floating hover card reading as its own
   surface, not a theme mismatch. */
.thumb-preview {
  position: fixed; z-index: 100; background: var(--ink); color: var(--bg);
  border-radius: var(--r-md); padding: 12px 14px; display: flex; align-items: center; gap: 12px;
  box-shadow: var(--shadow-3); opacity: 0; pointer-events: none; transform: translateY(4px);
  transition: opacity 120ms ease, transform 120ms ease; max-width: 380px;
}
.thumb-preview.show { opacity: 1; transform: translateY(0); }
/* Enlarged (2026-08-16, per user request: the old 64px photo was too small
   to actually tell what the product was - the entire point of this hover
   preview). 160px is big enough to read label text/graphics on most
   product photos without the popup itself dominating the screen. */
.thumb-preview-photo { width: 160px; height: 160px; border-radius: var(--r-md); object-fit: contain; background: rgba(128, 128, 128, 0.18); flex: none; }
.thumb-preview-article { font-size: 13px; font-weight: 700; line-height: 1.3; word-break: break-all; }
.thumb-preview-store { font-size: 11px; margin-top: 4px; opacity: 0.72; }
@media (hover: none) { .thumb-preview { display: none; } }
@media (prefers-reduced-motion: reduce) { .thumb-preview { transition: none; } }

/* ================= Admin pages port (mockup's #page-stores/#page-prefixes/
   #page-users/#page-scan-settings/#page-log/#page-status, 2026-08-16) - these
   were explicitly left out of every earlier Steel Ledger round ("lower
   priority, time-permitting"). Reuses the mockup's own component vocabulary
   (.page-toolbar/.panel/.panel-head/.count-pill/.table-wrap.tall/
   .form-grid/.form-field/.btn-input/.modal-head/.modal-body/.modal-foot),
   same discipline as v0.9.77-79: real DOM shape from the mockup, not just
   inherited tokens. .panel/.panel-head/.eyebrow/.count-pill/.explainer/
   .stat-row-wide/.period-tabs already existed (built for /assembly, /scan,
   /admin/kpi in earlier rounds) - reused as-is here, not redefined. ================= */

/* Title + subtitle + one action button, all on one row (mockup's
   .page-toolbar) - nests the site's existing .page-head-text/.eyebrow/h1
   instead of introducing a second heading language, and adds .sub for the
   subtitle line the plain .page-head convention elsewhere doesn't carry. */
.page-toolbar { display: flex; justify-content: space-between; align-items: flex-end; gap: 10px 16px; flex-wrap: wrap; margin-bottom: 24px; }
.page-toolbar .sub { font-size: 13.5px; color: var(--ink-muted); margin-top: 6px; max-width: 640px; line-height: 1.5; }
.page-toolbar .btn { flex: none; }
@media (max-width: 640px) { .page-toolbar { align-items: stretch; } }

/* Tall, self-scrolling table (mockup's .table-wrap.tall) - same treatment as
   the existing .table-wrap.long-table (sticky header, bounded height), just
   the mockup's own name for it on /stores' store list. */
.table-wrap.tall { max-height: 560px; overflow-y: auto; }
.table-wrap.tall thead th { position: sticky; top: 0; z-index: 1; }

/* "График работы" week grid (2026-08-22, replaced a month grid the same day -
   the owner found up-to-31 day-columns with 10+ employees per day unreadable;
   a week at a time with real shift times/absence chips shown inline, no
   click needed just to see who's on, reads cleanly instead). Ported from the
   approved Claude Design mockup: employee list is a left sidebar (navigation
   only - clicking highlights the matching row in the grid, never hides
   anyone), the grid itself is a flat CSS Grid of sibling <div>s rather than
   a <table> (a <table>'s HTML5 parser foster-parents anything that isn't a
   valid table child out of the table, which the mockup route through Jinja
   never risked - but the div-grid shape carried over anyway for the
   independently-useful part: 1fr day columns that fill the panel's full
   width instead of leaving blank space on a wide monitor, tested at
   1920x1080 per direct owner request). */
.emp-list { max-height: 640px; overflow-y: auto; }
.emp-row {
  display: flex; align-items: center; gap: 11px; padding: 9px 14px; border: none;
  border-bottom: 1px solid var(--rule); border-left: 3px solid transparent; cursor: pointer;
  background: none; width: 100%; text-align: left; font: inherit; color: inherit; margin: 0;
}
.emp-row:last-child { border-bottom: none; }
.emp-row:hover { background: var(--surface-2); }
.emp-row.highlighted { background: var(--accent-soft); border-left-color: var(--accent); }
.emp-avatar {
  width: 30px; height: 30px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 750;
  color: var(--ink-muted); flex: none;
}
.emp-row.highlighted .emp-avatar { background: var(--accent); color: #fff; border-color: var(--accent); }
.emp-avatar.sm { width: 26px; height: 26px; font-size: 10px; border-radius: var(--r-sm); }
.emp-main { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.emp-name { font-size: 12.5px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.emp-pos { font-size: 10.5px; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.emp-empty { padding: 22px 16px; font-size: 12.5px; color: var(--ink-muted); text-align: center; }

.week-nav-btns { display: flex; gap: 8px; }
.week-grid-wrap { max-height: 720px; overflow: auto; }
.week-grid { display: grid; grid-template-columns: 240px repeat(7, minmax(130px, 1fr)) 96px; width: 100%; }
.week-th {
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-faint); text-align: center; padding: 7px 4px; background: var(--surface-2);
  border-bottom: 1px solid var(--rule); border-right: 1px solid var(--rule); position: sticky; top: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.week-th.weekend { color: var(--critical); }
.week-th-daynum { display: block; font-size: 13.5px; margin-top: 2px; color: var(--ink); }
.week-corner { position: sticky; left: 0; top: 0; z-index: 3; background: var(--surface-2); border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.week-emp-cell {
  position: sticky; left: 0; z-index: 1; background: var(--surface); border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule); padding: 7px 12px; display: flex; align-items: center; gap: 9px;
}
.week-emp-cell.highlighted { background: var(--accent-soft); }
.week-cell.highlighted { background: rgba(46, 108, 176, 0.05); }
.week-cell {
  min-height: 52px; text-align: center; cursor: pointer; padding: 4px; border-bottom: 1px solid var(--rule);
  border-right: 1px solid var(--rule); display: flex; align-items: center; justify-content: center; background: var(--surface);
}
.week-cell:hover { background: var(--surface-2); }
.week-cell.weekend { background: rgba(22, 25, 31, 0.02); }
.week-cell.weekend:hover { background: var(--surface-2); }
.week-shift-time { font-family: var(--font-mono); font-weight: 700; font-size: 12px; white-space: nowrap; }
.week-empty-plus { color: var(--ink-faint); font-size: 17px; font-weight: 300; opacity: 0; line-height: 1; }
.week-cell:hover .week-empty-plus { opacity: 1; }
.week-total-cell {
  text-align: center; font-family: var(--font-mono); font-weight: 800; font-size: 13px; padding: 7px;
  border-bottom: 1px solid var(--rule); white-space: nowrap; color: var(--ink); display: flex;
  align-items: center; justify-content: center; background: var(--surface);
}
/* Same fix as the old grid's, ported forward: a chip's border must be keyed
   to its own text color (not left to .chip's base rule) so it stays visible
   against a week-cell.weekend background using the same --surface-2-ish
   token some chip variants use for their own fill. */
.week-cell .chip { border: 1px solid currentColor; margin-left: 0; }

/* Grid-shaped form (mockup's .form-grid/.form-field/.btn-input) - a second
   form language alongside the older .form/.form label/.form input,select
   pair (still used unmodified by account_password.html/login.html, out of
   this port's scope). Used inside dialogs (edit store, reset password, add
   user) and inside plain .panel-wrapped admin forms (add store, add prefix,
   add user) alike - the mockup uses the same classes in both contexts. */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.form-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.form-field.full { grid-column: 1 / -1; }
.form-field label { font-size: 11px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-muted); }
.form-hint { font-size: 11.5px; color: var(--ink-faint); margin: 0; line-height: 1.5; }
@media (max-width: 560px) { .form-grid { grid-template-columns: 1fr; } }

.btn-input {
  font-size: 13.5px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); font-family: inherit; padding: 8px 10px; width: 100%;
  transition: border-color 120ms ease;
}
.btn-input:hover { border-color: var(--accent); }
.btn-input:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
select.btn-input { cursor: pointer; }

/* Ghost button (mockup's .btn-ghost) - "cancel" action next to a primary
   .btn, e.g. in a .modal-foot. Distinct from the pre-existing .btn.ghost
   (a bordered, more prominent secondary button used on /assembly) - this is
   the plainer, text-only-ish cancel button the mockup's dialogs use. */
.btn-ghost {
  font-size: 13px; font-weight: 650; color: var(--ink-muted); background: var(--surface);
  border: 1px solid var(--rule-strong); border-radius: var(--r-sm); padding: 8px 15px; cursor: pointer; font-family: inherit;
}
.btn-ghost:hover { color: var(--ink); border-color: var(--ink-faint); }
/* Destructive variant (2026-08-22, "Очистить" on a schedule cell) - same
   treatment as the pre-existing .link-btn.danger, just on the bordered
   ghost button instead of the plain text link. */
.btn-ghost.danger { color: var(--critical); }
.btn-ghost.danger:hover { color: var(--critical); border-color: var(--critical); }

/* Small icon inside a .btn/.btn-ghost/.btn-line label ("Добавить магазин"
   etc.) - .btn itself stays inline-block (unchanged, ~20 other call sites
   depend on that), so the icon aligns via vertical-align rather than a flex
   gap on .btn itself. */
.btn .btn-icon, .btn-ghost .btn-icon, .btn-line .btn-icon { width: 14px; height: 14px; vertical-align: -3px; margin-right: 6px; display: inline-block; }

/* Square icon-only button ("+" add-employee in a .panel-head, 2026-08-22) -
   replaced an inline "Добавить сотрудника" link/anchor-jump; opens a real
   dialog instead. */
.icon-btn {
  width: 28px; height: 28px; border-radius: var(--r-sm); border: 1px solid var(--rule-strong); background: var(--surface);
  color: var(--ink-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  flex: none; padding: 0;
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.icon-btn svg { width: 16px; height: 16px; display: block; }

/* Bordered line-button (mockup's .btn-line) - smaller/quieter than .btn,
   used for the voice-preview action on /settings/scan. */
.btn-line {
  font-size: 12px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm);
  background: var(--surface); padding: 7px 13px; color: var(--ink-muted); font-family: inherit;
  cursor: pointer; font-weight: 600; display: inline-flex; align-items: center; gap: 5px;
  /* Кнопка не должна читаться как ссылка: часть .btn-line — это <a>, и
     подчёркивание из глобального правила для ссылок делало панель действий
     похожей на список гиперссылок девяностых. Текст в одну строку: «Скачать
     форму» ломалось пополам и ряд кнопок становился разновысоким. */
  text-decoration: none; white-space: nowrap;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.btn-line:hover { text-decoration: none; border-color: var(--ink-faint); color: var(--ink); }
.btn-line:hover { border-color: var(--accent); color: var(--ink); }
/* .accent — главное действие экрана: заливка, а не подкрашенная рамка.
   В варианте с --accent-soft главная кнопка весила столько же, сколько
   соседние вторичные, и взгляд не находил, с чего начать. */
.btn-line.accent {
  color: var(--accent-ink); border-color: var(--accent); background: var(--accent);
  box-shadow: var(--shadow-1);
}
.btn-line.accent:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--accent-ink); }
/* Прежний тихий вариант остался для панелей, где акцент не главный. */
.btn-line.accent-soft { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* Dialog restyle (mockup's .modal-head/.modal-body/.modal-foot) - the
   mechanism stays exactly what it was (native <dialog>, top-layer, per the
   project's own established "not <details>/position:absolute" decision) -
   only the internal class names/layout change, from a flat h3+.form+
   .modal-actions body to a header/body/footer sandwich. Used by /stores'
   edit-store dialog and /users' reset-password + add-user dialogs. */
dialog.modal {
  border: 1px solid var(--rule-strong); border-radius: var(--r-md); padding: 0;
  width: min(460px, calc(100vw - 40px)); background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-3);
}
dialog.modal::backdrop { background: rgba(20, 18, 14, 0.45); }
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 15px 18px; border-bottom: 1px solid var(--rule); background: var(--surface-2);
}
.modal-head h2 { font-size: 15.5px; font-weight: 750; margin: 0; }
.modal-close {
  background: none; border: none; color: var(--ink-muted); cursor: pointer; padding: 4px;
  border-radius: var(--r-sm); display: inline-flex; flex: none;
}
.modal-close:hover { background: var(--overlay-hover); color: var(--ink); }
.modal-close svg { width: 16px; height: 16px; display: block; }
.modal-body { padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 10px; padding: 14px 18px;
  border-top: 1px solid var(--rule); background: var(--surface-2);
}
/* Used outside a dialog too (mockup's own #page-prefixes "Добавить префикс"
   panel reuses .modal-foot as a plain bordered footer bar, left-aligned) -
   .form-grid/.form-field/.btn-input/.modal-foot form the general admin-form
   language, dialogs are one of the places it's used, not the only one. */
.modal-foot.start { justify-content: flex-start; }

/* ===================================================================
   /storage — адресное хранение (ПРОТОТИП, 2026-09-08)
   Signature element: the rack is drawn as a real ELEVATION — levels stack
   bottom-up the way a picker reads a physical shelf, each level sits on a
   drawn plank, there is a floor line, and the waist-height "золотая зона"
   (levels 2–3) is shaded. A flat top-down grid would hide exactly the thing
   this module exists to show: that a fast-moving item is on the wrong shelf.
   ------------------------------------------------------------------- */

/* flex-wrap обязателен: пять кнопок в ряд занимают 402px и на телефоне
   вылезали за экран на 44px, давая горизонтальный скролл всей странице
   (найдено обходом 2026-09-22). Сама схема стеллажа за экран выходить может —
   она внутри .rack-elevation-wrap с overflow-x, это осознанное исключение
   для чертежа, — а кнопки должны переноситься. */
.rack-toolbar-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Схема склада во всю ширину + карточка ячейки рядом. Колонки под список
   стеллажей больше нет: она тратила 232px на один стеллаж и три радиокнопки,
   стоя в 250px от картинки, которой управляла (аудит 2026-09-13). Карточка
   выросла с 268 до 400px — это единственное место ввода на странице, и она
   была самой узкой колонкой из трёх. */
.rack-layout { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 14px; align-items: start; }
/* Карточка липнет к верху: раскладка склада — это десятки сохранений подряд,
   и форма не должна уезжать вверх, пока человек листает длинный стеллаж. */
.rack-detail { position: sticky; top: 14px; }

/* --- список стеллажей --- */
/* ---- Шапка схемы: вкладки стеллажей, поиск, режимы ---- */
.rack-stage-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 14px; border-bottom: 1px solid var(--rule);
}
.rack-tabs { display: flex; gap: 5px; flex-wrap: wrap; min-width: 0; }
.rack-tab {
  display: flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 5px 10px 5px 6px; border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  transition: background 120ms ease, border-color 120ms ease;
}
.rack-tab:hover { background: var(--surface-2); }
.rack-tab.active { background: var(--accent-soft); border-color: var(--accent); }
.rack-tab-code {
  display: grid; place-items: center; min-width: 24px; height: 22px; padding: 0 5px;
  border-radius: var(--r-sm); background: var(--surface-2); color: var(--ink);
  font-size: 11px; font-weight: 700;
}
.rack-tab.active .rack-tab-code { background: var(--accent); color: var(--accent-ink); }
/* Цвет задан явно: <button> не наследует цвет страницы, и в тёмной теме
   название стеллажа оказывалось чёрным на почти чёрном (найдено аудитом). */
.rack-tab-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.rack-tab-meta { font-size: 10.5px; color: var(--ink-faint); white-space: nowrap; }
.rack-tab-add {
  width: 30px; height: 32px; cursor: pointer; font-size: 16px; line-height: 1;
  border: 1px dashed var(--rule-strong); border-radius: var(--r-sm);
  background: none; color: var(--ink-faint);
  transition: color 120ms ease, border-color 120ms ease;
}
.rack-tab-add:hover { color: var(--accent); border-color: var(--accent); }

.rack-stage-tools { display: flex; align-items: center; gap: 7px; margin-left: auto; flex-wrap: wrap; }
.rack-search-wrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
.rack-search-result { font-size: 11.5px; white-space: nowrap; }
.rack-search-result.is-hit { color: var(--good); }
.rack-search-result.is-miss { color: var(--critical); }
.rack-search-result[hidden] { display: none; }

.rack-aside { padding-bottom: 6px; }
.rack-list { display: flex; flex-direction: column; padding: 8px; gap: 4px; }
.rack-list-item {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; gap: 2px 9px;
  align-items: center; text-align: left; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: var(--r-sm); padding: 8px 9px;
}
.rack-list-item:hover { background: var(--surface-2); }
.rack-list-item.active { background: var(--accent-soft); border-color: var(--accent); }
.rack-badge {
  grid-row: 1 / 3; width: 30px; height: 30px; border-radius: var(--r-sm); flex: none;
  display: grid; place-items: center; font: 800 14px/1 var(--font-mono);
  background: var(--surface-2); color: var(--ink-muted);
}
.rack-list-item.active .rack-badge { background: var(--accent); color: var(--accent-ink); }
.rack-list-name { font-size: 13px; font-weight: 650; }
.rack-list-meta { font-size: 10.5px; color: var(--ink-faint); white-space: nowrap; }
.rack-fill { grid-column: 2; height: 3px; border-radius: var(--r-xs); background: var(--surface-2); overflow: hidden; }
.rack-fill-bar { display: block; height: 100%; width: var(--w, 0%); background: var(--accent); border-radius: var(--r-xs); }
.rack-list-add {
  margin-top: 4px; padding: 8px; border: 1px dashed var(--rule-strong); border-radius: var(--r-sm);
  background: transparent; color: var(--ink-faint); font-size: 12px; cursor: pointer;
}
/* Переключатель режима — сегментированный контрол сайта (тот же язык, что у
   выбора периода на /admin/finance), а не три голые радиокнопки. */
.rack-modes { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface-2); }
.rack-mode {
  padding: 5px 9px; border: none; border-radius: var(--r-sm);
  background: none; cursor: pointer; font-size: 11.5px; font-weight: 600; white-space: nowrap;
  color: var(--ink-muted); transition: background 130ms ease, color 130ms ease;
}
.rack-mode:hover { color: var(--ink); }
.rack-mode.active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }

/* Полоса заполнения в плашке сводки. Своя, а не `.rack-fill`: та живёт внутри
   grid-строки списка стеллажей и несёт `grid-column: 2`, который вне грида
   ничего не значит и оставляет полосу обрезком. */
/* Плашки сводки: подпись бывает в одну строку («Свободно»), бывает в две
   («Ходовое лежит неудобно»), и числа вставали на разной высоте — ряд читался
   как «разъехался». Резервируем подписи две строки, число всегда на одной
   линии (2026-09-30). */
/* Подпись плашки бывает в одну строку («Свободно») и в две («Заказов сегодня
   (с 19:00)»), и числа вставали на разной высоте — ряд читался рваным.
   Резервируем подписи две строки, в обеих разновидностях разметки: на складе
   и в финансах это .stat-label, на дашборде — .eyebrow. */
.stat-row-wide .stat-label,
.stat-row-wide .stat-block > .eyebrow { min-height: 33px; align-items: flex-start; }
/* На узком экране ряд переносится в сетку 2×N, и там даже двух строк подписи
   не хватает: «Удержано маркетплейсами» занимает три. Внутри ряда блоки
   растянуты на одну высоту, поэтому просто прижимаем число к низу — соседи
   в одной строке сетки снова стоят на одной линии. */
@media (max-width: 700px) {
  .stat-row-wide .stat-block { display: flex; flex-direction: column; }
  .stat-row-wide .stat-block > .stat-val { margin-top: auto; }
}

.rack-stat-bar {
  display: block; height: 4px; margin-top: 8px; border-radius: var(--r-xs);
  background: var(--surface-2); overflow: hidden;
}
.rack-stat-bar i { display: block; height: 100%; background: var(--accent); border-radius: var(--r-xs); }

/* «Что где лежит» — обратный вопрос к картинке стеллажа. */
.rack-placed-table tbody tr { cursor: pointer; }
.rack-placed-table tbody tr:hover { background: var(--overlay-hover); }

/* --- Стеллаж: настоящая элевация (2026-09-09, переработано) ----------------
   Раньше ячейка была фиксированной 52px и стеллаж на 14 ячеек банально
   обрезался краем панели. Теперь ряд - grid из N равных колонок: 14 или 30
   ячеек одинаково укладываются в доступную ширину, а на узком экране
   включается горизонтальная прокрутка вместо нечитаемых 20-пиксельных ячеек.

   Рисуется как настоящий стеллаж: две стойки с перфорацией, полки с торцом и
   держателем номера, пол с контактной тенью. Именно это, а не абстрактная
   сетка, показывает с одного взгляда, что ходовой товар лежит не на той
   полке. */
.rack-stage { min-width: 0; }
.rack-search { width: 158px; }
/* Вкладки стеллажей и инструменты в одну строку помещаются только на широком
   экране. Когда не помещаются — инструменты уходят СВОЕЙ строкой во всю
   ширину, а не жмутся вправо, оставляя дыру слева (это и читалось как
   «интерфейс разъехался», 2026-09-30). */
@media (max-width: 1560px) {
  .rack-stage-tools { margin-left: 0; width: 100%; }
  .rack-search-wrap { flex: 1 1 auto; }
  .rack-search { width: 100%; max-width: 240px; }
}
/* Название стеллажа может быть любой длины — вкладка не должна выдавливать
   инструменты во вторую строку. */
.rack-tab-name { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rack-elevation-wrap { padding: 18px 18px 6px; overflow-x: auto; }

.rack-elevation { display: flex; align-items: stretch; gap: 0; min-width: min-content; }
/* display:flex перебивает [hidden] — без этого показывались бы все стеллажи
   сразу (тот же класс бага, что уже ловили на .rack-detail-rows). */
.rack-elevation[hidden] { display: none; }

/* стойки */
.rack-upright {
  width: 11px; flex: none; border-radius: var(--r-xs);
  background:
    repeating-linear-gradient(180deg, transparent 0 9px,
      color-mix(in srgb, var(--ink-faint) 45%, transparent) 9px 12px),
    linear-gradient(90deg, var(--rule-strong), var(--surface-2) 45%, var(--rule-strong));
  box-shadow: inset 0 0 0 1px var(--rule-strong);
}
.rack-body { flex: 1; min-width: 0; display: flex; flex-direction: column; padding-left: 22px; }

.rack-level { position: relative; padding: 0 7px; }
/* «Золотая зона» — подсказка, а не выделение. Сплошная заливка на две полки
   была самым ярким пятном схемы и перекрикивала настоящее выделение (тонкая
   рамка выбранной ячейки), а в тёмной теме читалась мутной оливковой полосой.
   Осталась левая метка + едва заметный оттенок. */
.rack-level.golden { background: color-mix(in srgb, var(--warn-soft) 45%, transparent); }
.rack-level.golden:first-child { border-radius: var(--r-sm) var(--r-sm) 0 0; }


/* Ячейка отвечает на «что тут лежит» БЕЗ клика: адрес целиком, артикул и
   остаток прямо на ней. Раньше был только номер «05» десятым кеглем, и чтобы
   узнать содержимое стеллажа, требовалось 56 кликов (аудит 2026-09-13). */
.rack-cell {
  position: relative; min-height: 40px; min-width: 0; cursor: pointer;
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--surface);
  display: flex; flex-direction: column; align-items: stretch; justify-content: center;
  gap: 3px; padding: 7px 9px 10px; overflow: hidden; text-align: left;
  transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
}
.rack-cell:hover { box-shadow: var(--shadow-1); border-color: var(--rule-strong); }
/* Строка ячейки: адрес, артикул, остаток — в один ряд. Раньше ячейка была
   карточкой 56px с артикулом во вторую строку; на ленте коробок одна строка
   читается быстрее и даёт место подписи «взять N». */
.rack-cell-need {
  font-size: 10.5px; font-weight: 700; line-height: 1.2; color: var(--warn);
  letter-spacing: 0.01em;
}
.rack-cell.is-short .rack-cell-need { color: var(--critical); }
.rack-cell.is-wanted { border-color: var(--warn); background: var(--warn-soft); }
.rack-cell.is-short { border-color: var(--critical); background: var(--critical-soft); }
.rack-cell-addr {
  font-size: 11.5px; font-weight: 600; line-height: 1.1; color: var(--ink);
  letter-spacing: 0.02em;
}
.rack-cell-article {
  font-size: 10.5px; line-height: 1.25; color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rack-cell-article.is-box-label { color: var(--accent); font-weight: 600; }
/* Начало артикула сжимается многоточием, конец виден всегда. */
.rack-cell-article.is-split { display: flex; min-width: 0; }
.rack-cell-article .ra-h { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rack-cell-article .ra-t { flex: none; white-space: nowrap; }
/* Остаток стоит В РЯД с адресом, а не поверх него: абсолютная позиция
   налезала на адрес, как только ячейка становилась уже ~50px (14 ячеек на
   полке при 1280 — уже пересечение, а конструктор допускает до 40). */
.rack-cell-head { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.rack-cell-head .rack-cell-article { flex: 1 1 auto; min-width: 0; }
.rack-cell-head .rack-cell-qty { margin-left: auto; }
.rack-cell-qty {
  font-size: 12.5px; font-weight: 700; line-height: 1.1; color: var(--ink);
  font-variant-numeric: tabular-nums; flex: none;
}
.rack-cell-stock {
  position: absolute; left: 7px; right: 7px; bottom: 5px; height: 4px; border-radius: var(--r-full);
  background: var(--rule); overflow: hidden;
}
.rack-cell-stock::after {
  content: ""; position: absolute; inset: 0 auto 0 0; width: var(--fill, 0%);
  background: var(--accent); border-radius: var(--r-xs);
}
.rack-cell.filled { background: var(--surface); border-color: var(--rule-strong); box-shadow: var(--shadow-1); }
.rack-cell.filled .rack-cell-addr { color: var(--ink); }
/* Закреплено, но остатка нет — маршрут приведёт сборщика к пустой полке. */
/* Пустая ячейка — это место под товар, а не объект внимания: тише рамка,
   тише текст. Иначе полсотни пустых боксов перекрикивают шесть занятых. */
.rack-cell:not(.filled) { border-style: dashed; border-color: var(--rule); background: none; box-shadow: none; }
.rack-cell:not(.filled) .rack-cell-addr { color: var(--ink-faint); font-weight: 500; opacity: .75; }
.rack-cell.is-empty-stock .rack-cell-qty { color: var(--critical); }
.rack-cell.is-empty-stock .rack-cell-stock::after { background: var(--critical); }
.rack-cell.selected { outline: 2px solid var(--ink); outline-offset: 1px; }
.rack-cell.hit { outline: 2px solid var(--good); outline-offset: 1px; }
.rack-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Подтверждение сохранения прямо на ячейке: человек видит, что записалось
   именно то место, куда он смотрел. */
@keyframes cellSaved {
  0%   { box-shadow: 0 0 0 0 var(--good-soft), 0 0 0 2px var(--good); }
  100% { box-shadow: 0 0 0 14px transparent, 0 0 0 2px transparent; }
}
.rack-cell.just-saved { animation: cellSaved 1.4s ease-out 1; }
/* Поиск: несовпавшие приглушаются, иначе тонкую рамку не найти глазами среди
   полусотни одинаковых прямоугольников. */
.rack-elevation-wrap.is-searching .rack-cell { opacity: 0.32; filter: saturate(0.3); }
.rack-elevation-wrap.is-searching .rack-cell.hit { opacity: 1; filter: none; }

/* полка: доска с торцом и держателем номера, как на настоящем стеллаже */
.rack-shelf-board {
  position: relative; height: 8px; border-radius: var(--r-xs);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface) 65%, var(--ink-faint)) 0 1px,
    color-mix(in srgb, var(--ink-faint) 70%, var(--surface)) 1px 4px,
    var(--rule-strong) 4px 100%);
  box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.5), inset 0 -1px 0 rgba(0, 0, 0, 0.16);
}
/* Держатель номера полки — в собственном жёлобе слева, как на настоящем
   стеллаже, а не поверх стойки. */
.rack-shelf-tag {
  position: absolute; left: -22px; top: 50%; transform: translateY(-50%);
  width: 16px; padding: 2px 0; border-radius: var(--r-xs); text-align: center;
  font: 700 9px/1.4 var(--font-mono); color: var(--ink-muted);
  background: var(--surface-2); border: 1px solid var(--rule);
}
.rack-level.golden .rack-shelf-tag { color: var(--warn); border-color: var(--warn); }

.rack-base { position: relative; margin-top: 9px; }
.rack-base::before {
  content: ""; display: block; height: 3px; border-radius: var(--r-xs);
  background: var(--ink-faint); opacity: 0.55;
}
.rack-base::after {
  content: ""; position: absolute; left: 4%; right: 4%; top: 4px; height: 9px;
  background: radial-gradient(ellipse at center top, rgba(0, 0, 0, 0.22), transparent 72%);
}
.rack-floor-label { display: block; margin-top: 4px; font-size: 9.5px; color: var(--ink-faint); }

/* «Золотая зона» читается полосой и цветом бирки полки — вертикальная надпись
   на этой ширине только налезала на номера. */
.rack-level.golden { box-shadow: inset 3px 0 0 var(--warn); }

/* «Спрос» — насыщенность АКЦЕНТА, не красного (2026-09-13). Красный на этом
   сайте везде значит проблему, а «товар хорошо продаётся» — не проблема; на
   красной схеме бестселлеры читались как авария. Предупреждающая рамка
   остаётся ровно за одним настоящим неудобством: ходовое лежит не на высоте
   пояса — то самое, что считает плашка «Ходовое лежит неудобно». */
/* Режим «Нужно собрать»: всё, за чем сейчас не надо идти, уходит на второй
   план — иначе три занятые ячейки теряются среди полусотни соседних. */
.rack-elevation-wrap[data-mode="need"] .rack-cell:not(.is-wanted) { opacity: 0.45; }
.rack-elevation-wrap[data-mode="need"] .rack-cell:not(.is-wanted) .rack-cell-stock { display: none; }
.rack-elevation-wrap[data-mode="need"] .rack-box:not(.is-wanted) { opacity: 0.72; }
.rack-elevation-wrap[data-mode="heat"] .rack-cell.filled {
  background: color-mix(in srgb, var(--accent) calc(12% + var(--heat) * 68%), var(--surface));
  border-color: color-mix(in srgb, var(--accent) calc(30% + var(--heat) * 60%), var(--rule-strong));
}
.rack-elevation-wrap[data-mode="heat"] .rack-cell.filled .rack-cell-addr,
.rack-elevation-wrap[data-mode="heat"] .rack-cell.filled .rack-cell-article { color: var(--ink); }
.rack-elevation-wrap[data-mode="heat"] .rack-cell.is-awkward {
  outline: 2px solid var(--warn); outline-offset: -2px;
}
.rack-elevation-wrap[data-mode="heat"] .rack-cell-stock { display: none; }
.rack-elevation-wrap[data-mode="free"] .rack-cell-stock { display: none; }
.rack-elevation-wrap[data-mode="free"] .rack-cell.filled { background: var(--surface); border-color: var(--rule); opacity: 0.45; }
.rack-elevation-wrap[data-mode="free"] .rack-cell:not(.filled) { background: var(--good-soft); border-color: var(--good); }

/* Появление — тем же приёмом, что строки и карточки по всему сайту. */
@media (prefers-reduced-motion: no-preference) {
  .rack-elevation:not([hidden]) .rack-level { animation: rowIn 260ms cubic-bezier(.22,.85,.3,1) both; }
  .rack-elevation:not([hidden]) .rack-level:nth-child(2) { animation-delay: 40ms; }
  .rack-elevation:not([hidden]) .rack-level:nth-child(3) { animation-delay: 80ms; }
  .rack-elevation:not([hidden]) .rack-level:nth-child(4) { animation-delay: 120ms; }
  .rack-elevation:not([hidden]) .rack-level:nth-child(n+5) { animation-delay: 160ms; }
}

.rack-legend { display: flex; flex-wrap: wrap; gap: 16px; padding: 12px 16px 16px; font-size: 12px; color: var(--ink-muted); }
.rack-legend-item { display: flex; align-items: center; gap: 6px; }
.rack-legend-sw { width: 14px; height: 12px; border-radius: var(--r-xs); border: 1px solid var(--rule-strong); background: var(--surface); flex: none; }
.rack-legend-sw.filled { background: var(--accent-soft); border-color: var(--accent); }
.rack-legend-sw.golden { background: var(--warn-soft); border-color: var(--warn); }
.rack-legend-sw.awkward { background: var(--surface); border-color: var(--warn); box-shadow: inset 0 0 0 1px var(--warn); }
.rack-legend-sw.free { background: var(--good-soft); border-color: var(--good); }
.rack-legend-sw.wanted { background: var(--warn-soft); border-color: var(--warn); }
.rack-legend-sw.short { background: var(--critical-soft); border-color: var(--critical); }
.rack-legend-sw.heat-lo { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 30%, var(--rule-strong)); }
.rack-legend-sw.heat-hi { background: color-mix(in srgb, var(--accent) 80%, var(--surface)); border-color: var(--accent); }
.rack-legend[hidden] { display: none; }

/* --- карточка ячейки --- */
.rack-detail-empty { padding: 18px 14px; font-size: 12.5px; color: var(--ink-faint); }
.rack-detail-body { padding: 14px; }
.rack-address { margin-bottom: 16px; padding-bottom: 13px; border-bottom: 1px solid var(--rule); }
.rack-address-code {
  font: 800 27px/1 var(--font-mono); color: var(--ink); letter-spacing: 0.02em; white-space: nowrap;
}
.rack-address-sep { color: var(--ink-faint); margin: 0 1px; }
.rack-address-caption {
  margin-top: 6px; font-weight: 600; font-size: 10px; line-height: 1;
  letter-spacing: 0.01em; color: var(--ink-faint); white-space: nowrap;
}
.rack-address-scan { margin-top: 7px; font-size: 11px; color: var(--ink-faint); }
.rack-address-scan b { color: var(--ink-muted); letter-spacing: 0.1em; }
.rack-detail-rows { display: flex; flex-direction: column; gap: 9px; }
.rack-detail-rows[hidden], .rack-detail-body[hidden], .rack-detail-row[hidden] { display: none; }
.rack-detail-row { display: flex; flex-direction: column; gap: 2px; }
.rack-detail-label { font-weight: 600; font-size: 9.5px; line-height: 1; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-faint); }
.rack-detail-article { font-size: 12px; font-weight: 650; word-break: break-all; }
.rack-advice { font-size: 12px; color: var(--warn); line-height: 1.4; }
.rack-detail-actions { display: flex; flex-direction: column; gap: 7px; margin-top: 16px; }
.rack-detail-actions .btn-line { width: 100%; }

@media (max-width: 1180px) {
  /* Карточка ячейки уходит под схему, но остаётся ПЕРЕД таблицей: это орган
     управления, а таблица — справка. */
  .rack-layout { grid-template-columns: minmax(0, 1fr); }
  .rack-detail { position: static; }
}
@media (max-width: 760px) {
  .rack-stage-head { gap: 8px; padding: 9px 12px; }
  .rack-stage-tools { margin-left: 0; width: 100%; }
  .rack-search-wrap { flex: 1 1 100%; }
  .rack-search { width: 100%; }
  .rack-tab-name { display: none; }      /* на узком экране хватает кода и счётчика */
  /* Схема шире экрана — прокрутка должна быть ВИДНА, иначе половина стеллажа
     просто не существует для человека (аудит 2026-09-13: 8 ячеек из 14 без
     единого признака, что есть продолжение). */
  .rack-elevation-wrap {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    background:
      linear-gradient(to right, var(--surface) 30%, transparent) left / 24px 100% no-repeat,
      linear-gradient(to left, var(--surface) 30%, transparent) right / 24px 100% no-repeat,
      radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.16), transparent) left / 12px 100% no-repeat,
      radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.16), transparent) right / 12px 100% no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
  .rack-box-cells { grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); }
  .rack-shelf-row { flex-wrap: wrap; }
  /* Сенсорная цель: 30px по ширине были меньше минимума в полтора раза. */
  .rack-cell { height: 60px; }
  /* Восемь плашек сводки в столбик — это ~700px до первого органа управления.
     Ряд флексовый, поэтому ширину задаёт сама плашка, а не сетка. */
  .stat-row-wide .stat-block { flex: 1 1 calc(50% - 1px); min-width: 0; }
}

/* --- Адресное хранение: конструктор (2026-09-09) --------------------------- */
.btn-line.small { padding: 4px 10px; font-size: 11px; }
.btn-line.danger { color: var(--critical); border-color: var(--critical); }
.btn-line.danger:hover { background: var(--critical-soft); border-color: var(--critical); color: var(--critical); }

.rack-head-actions { display: flex; align-items: center; gap: 8px; }

/* Пустое состояние: страница-конструктор без единого стеллажа должна
   говорить, что делать дальше, а не показывать три пустые колонки. */
.rack-empty { padding: 46px 26px 40px; text-align: center; }
.rack-empty h2 { margin: 18px 0 6px; font-size: 19px; }
.rack-empty p { margin: 0 auto 18px; max-width: 430px; color: var(--ink-muted); font-size: 12.5px; }
.rack-empty-art { display: inline-flex; flex-direction: column; gap: 5px; opacity: 0.5; }
.rack-empty-art span {
  display: block; width: 132px; height: 13px;
  border: 1px solid var(--rule-strong); border-radius: var(--r-xs);
  background: repeating-linear-gradient(90deg, var(--surface) 0 8px, transparent 8px 10px);
}
.rack-empty-art span:nth-child(2) { background-color: var(--warn-soft); border-color: var(--warn); }
.rack-empty-art span:nth-child(3) { background-color: var(--warn-soft); border-color: var(--warn); }

/* Модальное окно конструктора */
.rack-modal { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; padding: 20px; }
.rack-modal[hidden] { display: none; }
.rack-modal-backdrop { position: absolute; inset: 0; background: rgba(11, 15, 20, 0.55); backdrop-filter: blur(2px); }
.rack-modal-card {
  position: relative; width: min(880px, 100%); max-height: calc(100vh - 40px);
  overflow: auto; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
}
.rack-modal-x {
  border: 0; background: none; cursor: pointer; font-size: 20px; line-height: 1;
  color: var(--ink-faint); padding: 0 2px;
}
.rack-modal-x:hover { color: var(--ink); }
.rack-modal-actions {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
  padding-top: 14px; border-top: 1px solid var(--rule);
}
.rack-modal-spacer { flex: 1; }

/* Форма конструктора: слева параметры, справа живой предпросмотр — чтобы
   было видно, что именно создастся, до нажатия кнопки. */
.rack-constructor { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; padding: 18px; }
.rack-constructor-fields { display: flex; flex-direction: column; gap: 14px; }
.rack-constructor-preview { display: flex; flex-direction: column; gap: 8px; }

.rack-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.rack-field.grow { flex: 1; }
.rack-field.short { width: 78px; flex: none; }
.rack-field-row { display: flex; gap: 12px; align-items: flex-end; }
.rack-field-row.tight { gap: 8px; align-items: center; }
.rack-field-label {
  font-weight: 700; font-size: 9.5px; line-height: 1; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-faint);
}
.rack-field-hint { font-size: 11px; color: var(--ink-muted); }
.rack-field-dash { color: var(--ink-faint); }
.rack-field .btn-input, .rack-field select.btn-input { width: 100%; }

.rack-stepper { display: flex; align-items: stretch; border: 1px solid var(--rule-strong); border-radius: var(--r-sm); overflow: hidden; }
.rack-stepper button {
  width: 30px; border: 0; background: var(--surface); color: var(--ink-muted);
  cursor: pointer; font-size: 15px; line-height: 1;
}
.rack-stepper button:hover { background: var(--accent-soft); color: var(--accent); }
.rack-stepper input {
  width: 100%; min-width: 0; border: 0; border-left: 1px solid var(--rule);
  border-right: 1px solid var(--rule); background: transparent; color: var(--ink);
  text-align: center; font: 700 14px/1 var(--font-mono); padding: 7px 0;
  -moz-appearance: textfield;
}
.rack-stepper input::-webkit-outer-spin-button,
.rack-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Мини-элевация в предпросмотре — та же метафора, что и на самой странице:
   уровни снизу вверх, «золотая зона» подсвечена. */
.rack-preview {
  display: flex; flex-direction: column; gap: 3px; padding: 10px 8px;
  border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface-2, var(--surface));
  min-height: 120px; flex: 1; justify-content: flex-end;
}
.rack-preview-level { display: flex; align-items: center; gap: 7px; padding: 2px 4px; border-radius: var(--r-xs); }
.rack-preview-level.golden { background: var(--warn-soft); }
.rack-preview-num { width: 12px; font-size: 9px; color: var(--ink-faint); text-align: right; flex: none; }
.rack-preview-cells i {
  flex: 1; min-width: 2px; height: 13px; border-radius: 1.5px;
  border: 1px solid var(--rule-strong); background: var(--surface);
}
/* Место, которого на этой полке нет: контур сохраняет сетку, чтобы узкая полка
   читалась как «короче», а не как «нарисована крупнее». */
.rack-preview-cells i.ghost { border-style: dashed; border-color: var(--rule); background: none; opacity: .45; }
.rack-preview-summary { font-size: 11.5px; color: var(--ink-muted); }
.rack-preview-summary b { color: var(--ink); font-size: 13px; }

/* Полки разной ширины */
/* Коробка со сборной мелочью — визуально другой тип адреса, а не «ячейка
   с пометкой»: пунктирная рамка и счётчик артикулов в углу, чтобы на картинке
   стеллажа она читалась сразу. */
.rack-cell.is-box { border-style: dashed; }
.rack-cell-box {
  position: absolute; top: 1px; right: 2px;
  font-size: 8px; line-height: 1; font-weight: 700;
  color: var(--accent); pointer-events: none;
}
.rack-kind-switch { display: flex; gap: 6px; flex-wrap: wrap; }
.rack-kind-switch label {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 5px 9px; border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--surface); font-size: 11.5px;
}
.rack-kind-switch input { accent-color: var(--accent); cursor: pointer; }
.rack-kind-switch label:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
/* Построчный редактор позиций ячейки. Список может быть длинным (коробка с
   возвратами), поэтому он скроллится внутри себя, а кнопки и «Сохранить»
   остаются на виду — иначе на двадцати позициях кнопка уезжала бы за экран. */
.cell-items { display: flex; flex-direction: column; gap: 5px; max-height: 260px; overflow-y: auto; }
.cell-item-row { display: grid; grid-template-columns: 1fr 62px 26px; gap: 5px; align-items: center; }
.cell-item-row .ci-article,
.cell-item-row .ci-stock { padding: 6px 8px; font-size: 12px; min-width: 0; }
.cell-item-row .ci-stock { text-align: center; font-variant-numeric: tabular-nums; }
.ci-remove {
  width: 26px; height: 26px; padding: 0; cursor: pointer; line-height: 1;
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink-faint); font-size: 15px;
  transition: color 120ms ease, border-color 120ms ease;
}
.ci-remove:hover { color: var(--critical); border-color: var(--critical); }
.cell-items-actions { display: flex; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
/* Поле «списком» существует всегда (оно и уезжает на сервер), но показывается
   только в режиме вставки. */
.cell-items-raw { display: none; resize: vertical; line-height: 1.45; margin-top: 7px; }
.cell-items-raw.is-visible { display: block; }
.cell-history { margin-top: 7px; width: 100%; padding: 6px 8px; font-size: 12px; }
.cell-history[hidden] { display: none; }

/* Журнал движений */
.rack-imported {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 11px 16px; margin-bottom: 14px; font-size: 12.5px;
  animation: rowIn .2s ease both;
}
.move-table td { vertical-align: middle; }
.move-table .nowrap { white-space: nowrap; }
/* Знак дублируется текстом («+4» / «−7»), цвет только усиливает — на печати и
   при дальтонизме приход всё равно отличим от списания. */
.move-plus { color: var(--good); font-weight: 600; }
.move-minus { color: var(--critical); font-weight: 600; }
.move-note { margin-left: 7px; font-size: 11px; color: var(--ink-muted); }
/* Фильтр и сводка журнала (2026-10-09). Форма собрана из готовых
   .form-field/.btn-input, как «Добавить префикс» на /settings/prefixes —
   свой flex-ряд полей выбивается из языка сайта и прилипает к краям. */
.moves-filter {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--rule);
}
.moves-filter .form-field { margin: 0; min-width: 150px; }
.moves-filter .form-field:first-child { flex: 1 1 240px; }
.moves-filter-actions { display: flex; gap: 8px; margin-left: auto; }
.moves-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  padding: 10px 16px; border-bottom: 1px solid var(--rule);
  background: var(--surface-2);
}
/* Кнопка-раскрытие: шеврон, а не иконка-картинка — значок меняется по
   состоянию, поэтому состояние видно и без цвета. */
.move-table .move-toggle-cell { padding-right: 0; }
.move-toggle {
  border: 0; background: none; cursor: pointer; padding: 2px 4px;
  color: var(--ink-muted); font-size: 12px; line-height: 1;
}
.move-toggle:hover { color: var(--accent); }
.move-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* `[hidden]` у строки таблицы перебивается любым display — в этом проекте это
   самый частый класс бага, поэтому правило явное. */
.move-detail[hidden] { display: none; }
/* Полоса слева привязывает подробности к своей строке: без неё раскрытый блок
   читается как отдельная запись журнала. */
.move-detail > td {
  background: var(--surface-2); padding: 12px 16px 14px;
  box-shadow: inset 3px 0 0 var(--accent);
}
.move-detail-grid {
  display: grid; gap: 10px 18px; font-size: 12px;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}
/* Подпись НАД значением, а не рядом: при подписи сбоку пары ломались по-разному
   в каждой колонке, и сетка читалась как россыпь слов. */
.move-detail-grid > div {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0;
}
.move-detail-grid > div > span { overflow-wrap: anywhere; }
.move-detail-wide { grid-column: 1 / -1; }
.move-detail-key {
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-muted);
}
.move-detail-raw { font-size: 10.5px; color: var(--ink-muted); }
.move-detail-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 11px; }
.moves-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.moves-foot .foot-hint { margin: 0; }
.moves-foot .link-btn[hidden] { display: none; }
@media (max-width: 700px) {
  .moves-filter-actions { margin-left: 0; width: 100%; }
  .moves-filter .form-field { min-width: 0; width: 100%; }
  /* В карточном режиме строка подробностей — такая же карточка, иначе
     colspan-ячейка растягивает таблицу по горизонтали. Селектор повторяет
     `.table-wrap.cards-on-mobile`, иначе общее правило карточек (у него
     специфичность выше) оставило бы ячейку flex-строкой с подписью справа. */
  /* Карточный режим раздаёт `display: block` каждому tr, и это перебивает
     атрибут `hidden` — тот самый класс бага, что ловили на `.chip[hidden]`.
     Правило обязано быть не слабее по специфичности. */
  .table-wrap.cards-on-mobile .move-detail[hidden],
  .table-wrap.cards-on-mobile .move-row[hidden] { display: none; }
  .table-wrap.cards-on-mobile .move-detail > td { display: block; width: 100%; text-align: left; }
  .table-wrap.cards-on-mobile .move-detail > td::before { content: none; }
  /* У кнопки-раскрытия подписи нет, и пустой ::before рисовал бы пустую
     строку в шапке карточки. */
  .table-wrap.cards-on-mobile td.move-toggle-cell::before { content: none; }
  .table-wrap.cards-on-mobile td.move-toggle-cell { justify-content: flex-start; }
  .move-detail-key { min-width: 0; }
}
.rack-varied-toggle {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-size: 12px; color: var(--ink); user-select: none;
}
.rack-varied-toggle input { accent-color: var(--accent); cursor: pointer; }
.rack-level-sizes {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;
  animation: rowIn .18s ease both;
}
.rack-level-size {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 7px; border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: var(--surface);
}
.rack-level-size > span.mono {
  font-size: 11px; font-weight: 600; color: var(--ink-muted); min-width: 9px;
}
.rack-level-size input {
  width: 42px; padding: 2px 4px; text-align: center;
  border: 1px solid var(--rule); border-radius: var(--r-xs);
  background: var(--surface-2); color: var(--ink);
  font-size: 12px; font-variant-numeric: tabular-nums;
}
.rack-level-size-unit { font-size: 10px; color: var(--ink-faint); }

.rack-warn {
  margin: 0; padding: 8px 10px; border-radius: var(--r-sm); font-size: 11.5px;
  background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn);
}
.rack-warn[hidden] { display: none; }

.rack-cell-form { display: flex; flex-direction: column; gap: 11px; margin-top: 14px; }

@media (max-width: 720px) {
  .rack-constructor { grid-template-columns: 1fr; }
  .rack-modal { padding: 10px; }
}

/* --- Наклейки на ячейки (2026-09-09) --------------------------------------
   Лист A4 под обычный офисный принтер: 3x8 = 24 наклейки 70x37 мм (геометрия
   стандартных самоклеящихся листов). Коды — inline SVG, поэтому печать
   векторная и чёткая на любом принтере. */
.lbl-filters { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; padding: 12px 16px; }
.lbl-filters .rack-field { width: 210px; }

/* Наклейки на ячейки — 58×40 мм, под термопринтер (2026-09-30, по прямому
   запросу владельца). Раньше это был лист A4 сеткой 3×8 по 70×37 мм под
   самоклеящуюся бумагу. 58×40 — ровно тот же размер, что у этикеток заказов
   (LABEL_EXTENSION, 58×40), то есть печатается тем же принтером и теми же
   настройками, без смены рулона.
   На ЭКРАНЕ наклейки выкладываются в несколько колонок — это предпросмотр;
   на ПЕЧАТИ каждая занимает свою страницу (см. @media print ниже). */
.lbl-sheet {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(58mm, max-content));
  gap: 6px; justify-content: start; padding: 0 2px;
}

@media screen and (max-width: 820px) {
  .lbl-sheet { justify-content: flex-start; overflow-x: auto; }
}
.lbl {
  width: 58mm; height: 40mm; box-sizing: border-box;
  display: flex; align-items: center; gap: 2.5mm; padding: 2mm 2.5mm;
  border: 1px dashed var(--rule); background: #fff; color: #000;
}
.lbl-qr { width: 19mm; height: 19mm; flex: none; display: grid; place-items: center; }
.lbl-qr svg { width: 100%; height: 100%; }
.lbl-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1.5mm; }
/* Адрес ужимается по ширине наклейки: «01A0304» у коробочного стеллажа на два
   знака длиннее, чем «01-B07», и на 58 мм при прежних 26px уехал бы за край. */
.lbl-address {
  font-size: 21px; font-weight: 800; letter-spacing: 0.02em; color: #000;
  line-height: 1; white-space: nowrap;
}
.lbl-code128 { height: 9mm; }
.lbl-code128 svg { width: 100%; height: 100%; }
/* Цифровой код — то, что реально закодировано в обоих штрихкодах. Мелкий:
   его читают только когда сканер не сработал, а место на 58×40 мм принадлежит
   адресу. */
.lbl-code { font-size: 9px; letter-spacing: 0.18em; color: #444; line-height: 1; }

.lbl-howto { margin: 0; padding: 4px 18px 16px 34px; display: grid; gap: 8px; font-size: 13px; color: var(--ink-muted); }
.lbl-howto b { color: var(--ink); }

/* --- Подбор по адресам (2026-09-09) ---------------------------------------
   Экран, на который смотрят стоя, с тележкой, иногда в перчатке. Отсюда всё
   остальное: адрес и количество набраны крупно (их читают с вытянутой руки),
   поле скана и кнопки - не меньше 44px по высоте, состояние скана говорится
   СЛОВАМИ и цветом сразу, а не только цветом. */
.pick-sources { display: grid; grid-template-columns: minmax(240px, 1fr) 2fr; gap: 14px; padding: 14px 16px; }
.pick-source {
  border: 1px solid var(--rule); border-radius: var(--r-md); padding: 14px;
  background: var(--surface); display: flex; flex-direction: column; min-width: 0;
}
.pick-source-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pick-source-title { font-size: 14px; font-weight: 650; }
.pick-source-nums { margin-top: 8px; font-size: 13px; color: var(--ink-muted); }
.pick-source-nums b { font-size: 22px; color: var(--ink); }
/* Кнопка прижата к низу карточки: рядом стоит карточка со списком прогонов,
   она заметно выше, и без этого «Собрать маршрут» висела бы в пустоте. */
.pick-source > button[type="submit"] { margin-top: auto; }
.pick-run-list { display: grid; gap: 6px; }
.pick-run {
  display: grid; grid-template-columns: 52px 1fr auto auto; align-items: center; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface-2);
}
.pick-run-id { font-weight: 700; }
.pick-run-when, .pick-run-nums { font-size: 12px; color: var(--ink-muted); }

.pick-flow-panel { margin-bottom: 14px; }
.pick-flow {
  margin: 0; padding: 4px 16px 16px 16px; list-style: none; counter-reset: step;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.pick-flow li { counter-increment: step; padding-left: 30px; position: relative; font-size: 12.5px; color: var(--ink-muted); }
.pick-flow li::before {
  content: counter(step); position: absolute; left: 0; top: 0;
  width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font: 700 11px/1 var(--font-mono);
}
.pick-flow b { display: block; color: var(--ink); font-size: 13px; margin-bottom: 3px; }
@media (max-width: 900px) { .pick-flow { grid-template-columns: 1fr; } }

.pick-progress-panel { padding: 14px 16px; margin-bottom: 14px; }
.pick-progress-nums { display: flex; gap: 18px; flex-wrap: wrap; font-size: 13px; color: var(--ink-muted); }
.pick-progress-nums b { font-size: 20px; color: var(--ink); }
.pick-progress-track { margin-top: 10px; height: 8px; border-radius: var(--r-sm); background: var(--surface-2); overflow: hidden; }
.pick-progress-bar { display: block; height: 100%; width: var(--w, 0%); background: var(--good); border-radius: var(--r-sm); transition: width 240ms ease; }

.pick-stage { margin-bottom: 14px; }
.pick-scan { display: flex; align-items: center; gap: 12px; padding: 14px 16px 0; flex-wrap: wrap; }
.pick-scan-input {
  flex: 1; min-width: 260px; height: 48px; font-size: 17px; letter-spacing: 0.06em;
}
.pick-scan-state { font-size: 13px; font-weight: 600; color: var(--ink-faint); min-width: 200px; }
.pick-scan-state.ok { color: var(--good); }
.pick-scan-state.warn { color: var(--warn); }
.pick-scan-state.bad { color: var(--critical); }

.pick-card {
  margin: 14px 16px; padding: 16px; border-radius: var(--r-md);
  border: 2px solid var(--rule); background: var(--surface-2);
  display: grid; grid-template-columns: auto auto 1fr; gap: 22px; align-items: center;
  transition: border-color 140ms ease, background 140ms ease;
}
/* Зелёная рамка появляется ТОЛЬКО после реального скана ячейки - это и есть
   подтверждение «ты стоишь там, где надо». */
.pick-card.ready { border-color: var(--good); background: var(--good-soft); }
.pick-address { font-size: 40px; font-weight: 800; line-height: 1; letter-spacing: 0.02em; }
.pick-address-caption { display: block; margin-top: 6px; font-size: 11px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.08em; }
.pick-card-what { display: flex; align-items: baseline; gap: 6px; }
.pick-qty { font-size: 40px; font-weight: 800; line-height: 1; color: var(--accent); }
.pick-what-label { font-size: 12px; color: var(--ink-muted); }
.pick-card-article { min-width: 0; }
.pick-article { font-size: 15px; font-weight: 650; word-break: break-all; }
.pick-article-meta { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.pick-flag { font-size: 12px; color: var(--warn); }

.pick-confirm { display: flex; align-items: flex-end; gap: 14px; padding: 0 16px 16px; flex-wrap: wrap; }
.pick-stepper { display: flex; align-items: center; gap: 6px; }
.pick-stepper button {
  width: 44px; height: 44px; border-radius: var(--r-sm); border: 1px solid var(--rule-strong);
  background: var(--surface); color: var(--ink); font-size: 20px; cursor: pointer;
}
.pick-stepper input {
  width: 82px; height: 44px; text-align: center; font-size: 20px; font-weight: 700;
  border: 1px solid var(--rule-strong); border-radius: var(--r-sm); background: var(--surface); color: var(--ink);
}
.pick-confirm-btn { height: 44px; padding: 0 22px; font-size: 15px; }
.pick-confirm-btn:disabled { opacity: 0.45; cursor: pointer; }

.pick-route-table tbody tr { cursor: pointer; }
.pick-route-table tbody tr.is-done { opacity: 0.55; }
.pick-route-table tbody tr.is-current { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.pick-code { font-size: 10px; color: var(--ink-faint); letter-spacing: 0.12em; }
.pick-noaddr { color: var(--critical); font-size: 12px; }
.pick-unplaced > summary {
  cursor: pointer; padding: 4px 16px 14px; font-size: 13px; color: var(--accent); width: fit-content;
}

@media (max-width: 900px) {
  .pick-sources { grid-template-columns: 1fr; }
  .pick-card { grid-template-columns: 1fr; gap: 12px; }
  .pick-run { grid-template-columns: 46px 1fr; row-gap: 6px; }
}

@media print {
  .topbar, .no-print, .page-toolbar { display: none !important; }
  .page { padding: 0 !important; margin: 0 !important; max-width: none !important; }
  body { background: #fff; }
  /* Пунктир — только чтобы резать по нему на экране; на бумаге он не нужен. */
  .lbl { border-color: transparent; break-inside: avoid; }
  .lbl-sheet { gap: 0; }
}

/* --- Отзывы (2026-09-10) ----------------------------------------------------
   Карточка отзыва, а не строка таблицы: главное здесь — ТЕКСТ, а он бывает и
   в три строки, и в абзац; в ячейке таблицы он либо обрезается, либо ломает
   ряд. Компоненты вокруг — общесайтовые (.panel, .chip, .stat-block,
   .kpi-rank-*), как и на остальных страницах. */
.rev-delta { display: block; margin-top: 5px; font-size: 11.5px; font-weight: 650; color: var(--ink-faint); }

.rev-top { padding: 6px 0 10px; }
.rev-top-row {
  width: 100%; border: 0; background: none; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
}
.rev-top-row:hover { background: var(--overlay-hover); }
.rev-top-row.is-active { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }

.rev-list { display: grid; gap: 10px; padding: 14px 16px 16px; }
.rev-card {
  border: 1px solid var(--rule); border-radius: var(--r-md); padding: 12px 14px;
  background: var(--surface); min-width: 0;
}
.rev-card[hidden] { display: none; }
/* Неотвеченный негатив — единственное, что требует действия: отмечен полосой,
   а не только чипом, чтобы его было видно при беглом пролистывании. */
.rev-card.is-unanswered { box-shadow: inset 3px 0 0 var(--warn); }
.rev-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rev-stars { letter-spacing: 1px; font-size: 13px; color: var(--rule-strong); }
.rev-stars i { font-style: normal; }
.rev-stars i.on { color: var(--warn); }
.rev-store { font-size: 12px; color: var(--ink-muted); }
.rev-date { font-size: 11.5px; color: var(--ink-faint); margin-left: auto; }
.rev-item { margin-top: 8px; font-size: 12px; color: var(--ink-muted); word-break: break-all; }
.rev-item-name { color: var(--ink-faint); }
.rev-text, .rev-pros, .rev-cons { margin: 8px 0 0; font-size: 13.5px; line-height: 1.5; }
.rev-pros, .rev-cons { font-size: 12.5px; color: var(--ink-muted); }
.rev-cons b { color: var(--critical); }
.rev-pros b { color: var(--good); }

@media (prefers-reduced-motion: no-preference) {
  .rev-card { animation: rowIn 240ms cubic-bezier(.22,.85,.3,1) both; animation-delay: calc(var(--i, 0) * 15ms); }
  .rev-top-row { animation: rowIn 240ms cubic-bezier(.22,.85,.3,1) both; animation-delay: calc(var(--i, 0) * 20ms); }
}
@media (max-width: 640px) {
  .rev-date { margin-left: 0; }
}


/* Экономика по товарам (2026-09-14) */
.sku-only-bad { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-muted); cursor: pointer; }
.sku-only-bad input { accent-color: var(--critical); cursor: pointer; }
.sku-table td { white-space: nowrap; font-variant-numeric: tabular-nums; vertical-align: top; }
.sku-table td:first-child { white-space: normal; }
.sku-article { display: block; word-break: break-all; margin-bottom: 3px; }
/* «Осталось» — главное число строки, поэтому крупнее остальных. */
.sku-net { font-size: 13px; font-weight: 700; }
.sku-share { display: block; font-size: 10.5px; color: var(--ink-faint); white-space: nowrap; }
/* Где продавался товар: маркетплейс цветной точкой + магазин. */
.sku-where { display: block; margin-top: 3px; font-size: 11px; color: var(--ink-muted); }
.sku-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; background: var(--ink-faint); }
.sku-dot-wb { background: var(--wb); }
.sku-dot-ozon { background: var(--ozon); }
.sku-dot-yandex { background: var(--yandex); }
.sku-stores { margin-top: 3px; font-size: 11px; color: var(--ink-muted); }
.sku-stores summary { cursor: pointer; }
.sku-stores-table { margin-top: 4px; border-collapse: collapse; }
.sku-stores-table td { padding: 2px 10px 2px 0; border: 0; font-size: 11px; white-space: nowrap; }
/* Товар в минусе подсвечивается строкой целиком: ради него сюда и заходят. */
.sku-row.is-bad { background: var(--critical-soft); }


/* Предупреждение «предыдущая ещё не наклеена» на /scan (2026-09-21).
   Тон — warn, а не crit: это не ошибка, а подсказка о темпе, и рядом в той же
   карточке уже живёт crit-состояние «не найдено», с которым его нельзя
   спутать на глаз. */
.haste-warn {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  margin-top: 10px; padding: 8px 11px; border-radius: var(--r-sm);
  background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn);
  font-size: 12.5px; line-height: 1.4;
}
.haste-warn span { font-family: var(--font-mono); font-size: 12px; }
/* display:flex перебивает атрибут [hidden] — без этой строки предупреждение
   висело бы на экране постоянно (та же ловушка, что у .chip[hidden]). */
.haste-warn[hidden] { display: none; }

/* Страница разбора «Поиск по заказу» (2026-09-21). Главный ответ — «кто» и
   «когда» — вынесен крупно над таблицей фактов: ради него страницу и
   открывают, искать его глазами в общем списке строк не нужно. */
.lookup-headline {
  display: flex; flex-wrap: wrap; gap: 26px; padding: 14px 16px;
  border-bottom: 1px solid var(--rule); background: var(--surface-2);
}
.lookup-headline .label { display: block; margin-bottom: 3px; }
.lookup-who { font-size: 21px; font-weight: 700; color: var(--ink); }
.lookup-when { font-size: 15px; color: var(--ink-muted); }
.lookup-unknown { font-size: 13px; color: var(--ink-muted); line-height: 1.5; }
/* Сосед, напечатанный в пределах трёх секунд: между такими физически нельзя
   успеть наклеить этикетку, и именно они — кандидаты на перепутывание. */
tr.lookup-close > td { background: var(--warn-soft); }
tr.lookup-close > td:first-child { font-weight: 700; color: var(--warn); }

/* Коробки в предпросмотре — ГРУППЫ в рамке, а не отметка на первой ячейке
   (2026-09-30). Полка физически собрана из коробок, и на картинке это должно
   читаться так же, как на схеме стеллажа. */
.rack-preview-cells {
  display: grid; gap: 2px; flex: 1; min-width: 0;
  grid-template-columns: repeat(var(--widest, 12), minmax(2px, 1fr));
}
.rack-preview-box {
  position: relative; grid-column: span var(--span, 1);
  display: grid; gap: 2px; grid-template-columns: repeat(var(--n, 1), minmax(0, 1fr));
}
/* Рамка — псевдоэлементом с отрицательным отступом: padding сузил бы ячейки
   внутри коробки, и коробка на 3 места нарисовалась бы крупнее коробки на 20. */
.rack-preview-box::after {
  content: ""; position: absolute; inset: -2px; pointer-events: none;
  border: 1px solid color-mix(in srgb, var(--accent) 75%, transparent); border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
/* display:flex/inline-flex перебивает атрибут [hidden] — без этих правил
   элемент остаётся виден, сколько его ни прячь из JS. Та же ловушка, что у
   .chip[hidden], и в этом проекте она повторяется чаще всего: аудит 2026-09-22
   нашёл сразу четыре незакрытых места, все на /storage.
   .btn-line — самое неприятное из них: кнопка «Удалить стеллаж» показывалась
   в диалоге СОЗДАНИЯ нового стеллажа. */
.rack-field[hidden] { display: none; }
.rack-level-sizes[hidden] { display: none; }
.cell-items[hidden] { display: none; }
.btn-line[hidden] { display: none; }

/* Шапка на средних экранах (2026-09-22). Логотип + навигация + правый блок
   требуют ~1583px, и ниже ~1650px строка переносилась: навигация оставалась
   сверху, а часы со статистикой уезжали на вторую строку вправо, оставляя
   пустоту слева — именно это читается как «интерфейс разъехался».
   Убирается по ступеням, от наименее ценного к более ценному. Значения
   ступеней — не круглые числа «на глаз», а замеры: логотип 90px, навигация
   712px, правый блок 737px и три отступа по 22px дают 1583px, а полезная
   ширина при `min(2200px, 96vw)` минус padding — это 1334px на 1440 и 1263px
   на 1366. Каждая ступень снимает ровно столько, сколько не хватает следующей
   типовой ширине ноутбука. */
/* Ступень 1 — слова возле чисел: «465 заказов сегодня» → «465». Что это за
   число, объясняет подсказка при наведении, она была там и раньше. ~200px,
   и этого хватает полосе 1500–1650, где не хватало всего 95px. */
@media (max-width: 1700px) { .topbar-stat-word { display: none; } }
/* Ступень 2 — часы: время есть в системном трее и на экране блокировки, в
   шапке они стоят дороже всего остального в пересчёте на пользу. ~85px. */
@media (max-width: 1500px) { .clock { display: none; } }
/* Числа целиком: на 1400px и уже они начинают вытеснять навигацию, а те же
   цифры есть на дашборде крупно и с расшифровкой. Прежнее правило стояло на
   900px, но шапка ломалась на две строки уже на 1400. */
@media (max-width: 1400px) { .topbar-stat, .topbar-sep { display: none; } }

/* Поле «Отдельный CSV» в строке префикса (2026-09-30): ввод и кнопка в одну
   строку, чтобы таблица не растягивалась по высоте на каждой строке. */
.csv-group-form { display: flex; align-items: center; gap: 6px; }
.csv-group-form .btn-input { width: 150px; padding: 5px 8px; font-size: 12.5px; }


/* --- Лента коробок: схема стеллажа (2026-09-30, v0.9.206) -----------------
   Коробка — не рамка вокруг ряда ячеек, а КАРТОЧКА, подписанная словом: это
   единица, которой человек оперирует физически, и адрес `01B0304` читается
   «коробка 03, ячейка 04». Ширина карточки пропорциональна числу ячеек в ней,
   поэтому коробка на 6 мест и на картинке шире коробки на 3. Ячейка внутри
   подписана номером ВНУТРИ коробки — ровно тем, что напечатано на наклейке. */
.rack-shelf-row { display: flex; gap: 10px; align-items: stretch; padding: 8px 0 7px; }
.rack-box {
  flex: var(--span, 1) 1 0; min-width: 0; box-sizing: border-box;
  padding: 9px 10px 11px; border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--surface-2); box-shadow: none;
  display: flex; flex-direction: column; gap: 6px;
}
/* Стеллаж без коробок: группа одна и рамки у неё нет — схема выглядит ровно
   так, как выглядела до появления коробок. */
.rack-box.plain { padding: 0; border: 0; background: none; box-shadow: none; }
.rack-box.is-wanted { border-color: var(--warn); background: var(--warn-soft); }
.rack-box-head { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.rack-box-name { font-size: 12.5px; font-weight: 600; color: var(--ink); white-space: nowrap; letter-spacing: -0.01em; }
.rack-box-fill { margin-left: auto; font-size: 11px; color: var(--ink-faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
.rack-box-need {
  padding: 1px 7px; border-radius: var(--r-md); background: var(--warn); color: #ffffff;
  font-size: 10px; font-weight: 700; white-space: nowrap;
}
.rack-box-cells {
  display: grid; gap: 4px;
  /* Ширину колонки держим крупной: на 1920 коробка из трёх ячеек
     раскладывала их в один ряд по 25–40px, и артикул ужимался до «PLA…» —
     на широком экране было видно МЕНЬШЕ текста, чем на 1366. */
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
}

/* --- Конструктор стеллажа: отдельная страница (2026-09-30, v0.9.207) ------
   Раскладка коробок — прямое редактирование чипами вместо строки «3,3,5;4,4».
   Чип это карточка: номер, число ячеек и пара кнопок во всю ширину — цель
   нажатия 40×26 вместо прежних 26px, и её видно на ощупь. */
.rack-builder {
  display: grid; gap: 14px; align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.rack-builder-fields, .rack-builder-preview { min-width: 0; }
/* Поля конструктора идут ОДНОЙ колонкой: общий .form-grid раскладывает их в
   две, и «Как адресуем» уезжало вбок, оставляя дыру над кодом стеллажа. */
.rack-form-stack { display: flex; flex-direction: column; gap: 14px; }
.rack-builder-preview .modal-body { display: flex; flex-direction: column; gap: 10px; }
.rack-boxes-field, .rack-builder-foot { grid-column: 1 / -1; }
.rack-builder-foot {
  position: sticky; bottom: 14px; z-index: 5;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; box-shadow: var(--shadow-2);
}
.rack-foot-summary { font-size: 12.5px; color: var(--ink-muted); }
.rack-foot-summary b { color: var(--ink); font-size: 14px; }

/* Схема адресации — выбор карточками: у двух схем разный ФОРМАТ адреса, и
   человек должен увидеть оба до того, как выберет. */
.scheme-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.scheme-card {
  display: grid; grid-template-columns: auto 1fr; gap: 3px 9px; align-items: center;
  padding: 10px 12px; border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--surface); cursor: pointer;
}
.scheme-card:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.scheme-card.is-locked { cursor: not-allowed; opacity: 0.75; }
.scheme-card input { accent-color: var(--accent); cursor: inherit; }
.scheme-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.scheme-sample {
  grid-column: 2; font-size: 15px; font-weight: 700; color: var(--ink-muted); letter-spacing: 0.02em;
}
.scheme-card:has(input:checked) .scheme-sample { color: var(--accent); }
.scheme-note { grid-column: 2; font-size: 11px; color: var(--ink-muted); }

.rack-presets { display: flex; flex-wrap: wrap; gap: 6px; }

.rack-boxes-field { display: flex; flex-direction: column; }
.rack-boxes-field[hidden] { display: none; }
.rack-boxes-total { font-size: 11.5px; color: var(--ink-muted); }
.rack-boxes-spacer { flex: 1; }
.rack-box-editor { display: flex; flex-direction: column; gap: 8px; }
.rack-box-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface-2);
}
.rack-box-row-tag {
  flex: none; min-width: 26px; padding: 4px 0; text-align: center;
  font-size: 11px; font-weight: 700; color: var(--ink-muted);
}
.rack-box-chips { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-width: 0; }
.rack-box-row-sum { flex: none; font-size: 11px; color: var(--ink-faint); white-space: nowrap; }
.rack-box-copy {
  flex: none; padding: 4px 9px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface);
  color: var(--ink-faint); font-size: 10.5px; font-weight: 600;
  transition: color 120ms ease, border-color 120ms ease;
}
.rack-box-copy:hover { color: var(--accent); border-color: var(--accent); }

.rack-box-chip {
  width: 96px; box-sizing: border-box; overflow: hidden;
  border: 1px solid var(--rule-strong); border-radius: var(--r-sm); background: var(--surface);
}
.rack-box-chip-head {
  display: flex; align-items: center; gap: 4px; padding: 3px 5px 0; min-height: 17px;
}
.rack-box-chip-tag { font-size: 9px; font-weight: 700; color: var(--accent); }
.rack-box-chip-lock {
  margin-left: auto; font-size: 8.5px; font-weight: 600; color: var(--warn); white-space: nowrap;
}
.rack-box-del {
  margin-left: auto; width: 16px; height: 16px; padding: 0; cursor: pointer; line-height: 1;
  border: 0; background: none; color: var(--ink-faint); font-size: 13px;
}
.rack-box-del:hover { color: var(--critical); }
.rack-box-chip-value { display: flex; align-items: baseline; justify-content: center; gap: 3px; padding: 0 6px 4px; }
.rack-box-chip-unit { font-size: 9.5px; color: var(--ink-faint); }
.rack-box-size {
  width: 46px; min-width: 0; padding: 0; text-align: center;
  border: 0; background: transparent; color: var(--ink);
  font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums;
  font-family: var(--font-mono); -moz-appearance: textfield;
}
.rack-box-size::-webkit-outer-spin-button,
.rack-box-size::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rack-box-size:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.rack-box-chip-steps { display: flex; border-top: 1px solid var(--rule); }
.rack-box-step {
  flex: 1 1 0; height: 26px; border: 0; cursor: pointer; line-height: 1;
  background: var(--surface-2); color: var(--ink-muted); font-size: 15px;
  transition: background 120ms ease, color 120ms ease;
}
.rack-box-step + .rack-box-step { border-left: 1px solid var(--rule); }
.rack-box-step:hover { background: var(--accent-soft); color: var(--accent); }
.rack-box-step:active { background: var(--accent); color: var(--accent-ink); }
/* Коробка, чей адрес уже напечатан на наклейках: её размер менять нельзя —
   видно сразу, а не после отказа сервера. */
.rack-box-chip.is-printed { border-style: dashed; }
.rack-box-chip.is-printed .rack-box-chip-tag { color: var(--ink-faint); }
.rack-box-chip.is-printed .rack-box-step { opacity: 0.4; }
.rack-box-add {
  width: 96px; min-height: 68px; cursor: pointer; white-space: nowrap;
  border: 1px dashed var(--rule-strong); border-radius: var(--r-sm);
  background: none; color: var(--ink-faint); font-size: 11.5px; font-weight: 600;
  transition: color 120ms ease, border-color 120ms ease;
}
.rack-box-add:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 980px) {
  .rack-builder { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 720px) {
  .rack-box-row { flex-wrap: wrap; }
  .rack-box-row-sum { width: 100%; text-align: right; }
}


/* --- Очередь на сборку: «с полки или изготовить» (2026-09-30, v0.9.206) ----
   Стеллаж стоит рядом с рабочим местом, и первый вопрос смены — сколько из
   очереди закрывает полка. Числа считает тот же build_pick_plan, что и лист
   подбора: разойтись они не могут по построению. */
.queue-panel { padding: 12px 16px 13px; margin-bottom: 14px; }
.queue-head { display: flex; align-items: baseline; gap: 10px; }
.queue-title { font-size: 12.5px; font-weight: 700; color: var(--ink); }
.queue-total { font-size: 17px; font-weight: 700; color: var(--ink); }
.queue-spacer { flex: 1; }
.queue-bar {
  display: flex; height: 10px; margin: 9px 0 8px; border-radius: var(--r-xs);
  overflow: hidden; background: var(--surface-2);
}
.queue-bar i { display: block; height: 100%; }
.queue-bar i.from-shelf { background: var(--good); }
.queue-bar i.to-make { background: var(--warn); }
.queue-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; font-size: 11.5px; color: var(--ink-muted); }
.queue-chip { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); }
.queue-chip::before { content: ""; width: 9px; height: 9px; border-radius: var(--r-xs); }
.queue-chip.shelf::before { background: var(--good); }
.queue-chip.make::before { background: var(--warn); }
.queue-chip.unplaced::before { background: var(--critical); }
.queue-note { margin-left: auto; color: var(--ink-faint); }

/* Потребность в карточке ячейки — первым блоком, до формы. */
.rack-need-box {
  margin-bottom: 14px; padding: 11px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--warn); background: var(--warn-soft);
}
.rack-need-box[hidden] { display: none; }
.rack-need-box.is-short { border-color: var(--critical); background: var(--critical-soft); }
.rack-need-text { font-size: 12px; line-height: 1.45; color: var(--warn); }
.rack-need-box.is-short .rack-need-text { color: var(--critical); }
.hint-dash { color: var(--ink-faint); }

/* Подсказка станции про товар с полки (2026-09-30). display:flex у
   .scan-status перебил бы [hidden] — тот самый класс бага, что чаще всего
   ловится в этом проекте. */
.scan-shelf-hint { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; }
.scan-shelf-hint button { flex: none; width: 32px; height: 32px; border: 0; border-radius: var(--r-sm); background: none; color: inherit; font-size: 18px; line-height: 1; cursor: pointer; }
.scan-shelf-hint button:hover { background: var(--overlay-hover); }
.scan-shelf-hint[hidden] { display: none; }

/* --- KPI: плашки и «Кто на смене» (2026-09-30) ----------------------------
   Чип дельты жил ВНУТРИ подписи и вставал то рядом с ней, то на свою строку,
   из-за чего числа в ряду прыгали. Теперь у него своё место под числом, и
   оно зарезервировано даже там, где дельты нет. */
.stat-delta { display: block; min-height: 18px; margin-top: 6px; }

.shift-grid {
  display: grid; gap: 10px; padding: 12px 14px 14px; justify-content: start;
  grid-template-columns: repeat(auto-fit, minmax(232px, 300px));
}
.shift-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 11px 12px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface);
}
/* Работает прямо сейчас — зелёная рамка: это единственное состояние, ради
   которого блок и открывают. */
.shift-card.is-active { border-color: var(--good); background: var(--good-soft); }
.shift-head { display: flex; align-items: center; gap: 9px; min-width: 0; }
.shift-avatar {
  display: grid; place-items: center; width: 28px; height: 28px; flex: none;
  border-radius: var(--r-sm); background: var(--ink-faint); color: var(--surface);
  font-size: 11px; font-weight: 700;
}
.shift-card.is-active .shift-avatar { background: var(--good); color: #ffffff; }
.shift-who { display: flex; flex-direction: column; min-width: 0; }
.shift-who b { font-size: 12px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shift-who i { font-style: normal; font-size: 10.5px; color: var(--ink-faint); }
.shift-nums { display: flex; align-items: baseline; gap: 6px; }
.shift-nums b { font-size: 20px; font-weight: 700; color: var(--ink); }
.shift-nums span { font-size: 10.5px; color: var(--ink-faint); }
.shift-total { margin-left: auto; font-size: 11px; color: var(--ink-muted); }
.shift-bar { display: block; height: 6px; border-radius: var(--r-xs); background: var(--surface-2); overflow: hidden; }
.shift-bar i { display: block; height: 100%; background: var(--rule-strong); }
.shift-card.is-active .shift-bar i { background: var(--good); }

/* ===== /problems «Требуют внимания» (2026-10-04) ==========================
   Один список вместо четырёх таблиц, плитки сверху — его фильтры. Тона те же,
   что у статус-чипов по всему сайту: crit — стоит денег, warn — наш долг,
   серый — не наша работа. */
.pb-money { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 16px; align-items: center; margin-bottom: 12px; padding: 12px 16px; border: 1px solid var(--critical); border-radius: var(--r-md); background: linear-gradient(90deg, var(--critical-soft), var(--surface) 62%); }
.pb-money.is-calm { border-color: var(--rule); background: var(--surface); }
.pb-money.is-calm .pb-pulse { background: var(--good); }
.pb-money.is-calm .pb-pulse::after { display: none; }
.pb-money-main { display: flex; align-items: center; gap: 12px; min-width: 0; }
.pb-money-main > div { display: grid; gap: 1px; min-width: 0; }
.pb-money-main > div > b { font-size: 14.5px; font-weight: 650; }
.pb-money-sub { font-size: 12.5px; color: var(--ink-muted); }
.pb-money-sub b { color: var(--ink); }
.pb-pulse { position: relative; flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--critical); }
.pb-pulse::after { content: ""; position: absolute; inset: -5px; border: 2px solid var(--critical); border-radius: 50%; opacity: 0; animation: pbPulse 2.4s ease-out infinite; }
@keyframes pbPulse { 0% { transform: scale(.5); opacity: .7; } 70%, 100% { transform: scale(1.3); opacity: 0; } }
.pb-money-more { grid-column: 1 / -1; font-size: 12.5px; }
.pb-money-more summary { width: fit-content; color: var(--accent-deep); cursor: pointer; }
.pb-money-more .prob-ask { padding-top: 8px; }
.pb-money-status { grid-column: 1 / -1; margin: 0; }
.pb-money-status:empty { display: none; }
.pb-board { display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(280px, 1fr); gap: 10px; margin-bottom: 12px; }
.pb-kinds { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); overflow: hidden; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); }
.pb-kind { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0 8px; align-items: baseline; padding: 11px 16px 12px; border: 0; border-right: 1px solid var(--rule); background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; transition: background-color .12s; }
.pb-kind:last-child { border-right: 0; }
.pb-kind:hover { background: var(--overlay-hover); }
.pb-kind::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: transparent; }
.pb-kind.is-on { background: var(--surface-2); }
.pb-kind.is-on::after { background: var(--ink-muted); }
.pb-kind.warn.is-on::after { background: var(--warn); }
.pb-kind.ozon.is-on::after { background: var(--ozon); }
.pb-kind-role { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-muted); }
.pb-kind b { font-size: 28px; font-weight: 600; letter-spacing: -.03em; line-height: 1.15; }
.pb-kind-name { font-size: 13.5px; font-weight: 600; }
.pb-kind-old { grid-column: 1 / -1; font-size: 12px; color: var(--ink-faint); }
.pb-ages { display: grid; gap: 4px; padding: 9px 16px 8px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); }
.pb-ages-t { font-size: 11.5px; color: var(--ink-muted); }
.pb-ages-bars { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.pb-age { display: grid; grid-template-rows: auto 40px auto; align-items: end; justify-items: stretch; gap: 2px; min-width: 0; }
.pb-age b { font-size: 12px; font-weight: 600; }
.pb-age i { display: block; align-self: end; min-height: 2px; border-radius: 3px 3px 0 0; background: var(--warn); transform-origin: bottom; animation: mtRise .5s cubic-bezier(.2, .8, .2, 1) both; }
.pb-age.is-old i { background: var(--critical); }
.pb-age span { overflow: hidden; font-size: 11px; color: var(--ink-muted); text-overflow: ellipsis; white-space: nowrap; }
.pb-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 8px 14px; border-bottom: 1px solid var(--rule); }
.pb-filter select { width: auto; max-width: 240px; }
.pb-h, .pb-s { display: grid; grid-template-columns: 74px minmax(150px, 1.15fr) minmax(150px, 1.1fr) 104px minmax(120px, 1fr) 160px 16px; gap: 0 12px; align-items: center; padding-inline: 14px; }
.pb-h { min-height: 26px; border-bottom: 1px solid var(--rule); font-size: 11.5px; color: var(--ink-muted); }
.pb-group[hidden], .pb-b[hidden] { display: none; }
.pb-group-h { display: flex; align-items: center; gap: 6px; min-height: 24px; padding-inline: 14px; border-bottom: 1px solid var(--rule); background: var(--surface-2); font-size: 11.5px; font-weight: 650; color: var(--ink-muted); }
.pb-group-h i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); }
.pb-group-h.crit i { background: var(--critical); }
.pb-group-h.warn i { background: var(--warn); }
.pb-group-h b { font-weight: 600; color: var(--ink); }
.pb-group-h span { margin-left: 6px; font-weight: 400; }
.pb-b { border-bottom: 1px solid var(--rule); }
.pb-s { min-height: 36px; font-size: 13px; cursor: pointer; list-style: none; transition: background-color .12s; }
.pb-s::-webkit-details-marker { display: none; }
.pb-s:hover { background: var(--overlay-hover); }
.pb-s > span { min-width: 0; }
.pb-age-t { font-size: 12.5px; font-weight: 600; color: var(--ink-muted); white-space: nowrap; }
.pb-age-t.crit { color: var(--critical); }
.pb-age-t.warn { color: var(--warn); }
.pb-what { display: flex; align-items: center; gap: 7px; min-width: 0; }
.pb-what b { overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.pb-what i { flex: none; width: 8px; height: 8px; border-radius: 2px; background: var(--ink-faint); }
.pb-what i.warn { background: var(--warn); }
.pb-what i.ozon { background: var(--ozon); }
.pb-what i.crit { background: var(--critical); }
.pb-store, .pb-refs, .pb-items, .pb-act { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-store em { font-style: normal; }
.pb-items b { font-weight: 600; }
.pb-refs { display: flex; gap: 8px; font-size: 12px; }
.pb-refs a { color: var(--ink-muted); }
.pb-refs a:hover { color: var(--accent); }
.pb-act { font-size: 12.5px; }
.pb-chev { width: 7px; height: 7px; border-right: 1.5px solid var(--ink-faint); border-bottom: 1.5px solid var(--ink-faint); transform: rotate(-45deg); transition: transform .15s; }
.pb-b[open] .pb-chev { transform: rotate(45deg); }
.pb-b[open] .pb-s { background: var(--surface-2); }
.pb-rows { max-height: 340px; overflow-y: auto; background: var(--surface-2); }
.pb-r { display: grid; grid-template-columns: 74px minmax(0, 1fr) 72px 190px 84px; gap: 0 12px; align-items: center; min-height: 32px; padding-inline: 14px; border-top: 1px solid var(--rule); font-size: 12.5px; }
.pb-r .pb-age-t { font-weight: 400; }
.pb-print { text-align: right; font-size: 12px; }
.pb-legend > summary { min-height: 40px; padding: 11px 16px; font-size: 13px; font-weight: 600; color: var(--ink-muted); cursor: pointer; }
.pb-legend[open] > summary { border-bottom: 1px solid var(--rule); }
@media (max-width: 1180px) {
  .pb-h { display: none; }
  .pb-s { grid-template-columns: 74px minmax(0, 1fr) auto 16px; gap: 2px 10px; padding-block: 7px; }
  .pb-what { grid-column: 2; grid-row: 1; }
  .pb-items { grid-column: 3; grid-row: 1; }
  .pb-chev { grid-column: 4; grid-row: 1; }
  .pb-store { grid-column: 2 / -1; grid-row: 2; font-size: 12px; color: var(--ink-muted); }
  .pb-refs { grid-column: 2 / 3; grid-row: 3; }
  .pb-act { grid-column: 3 / -1; grid-row: 3; text-align: right; }
}
@media (max-width: 760px) {
  .pb-board { grid-template-columns: minmax(0, 1fr); }
  .pb-kinds { grid-template-columns: minmax(0, 1fr); }
  .pb-kind { border-right: 0; border-bottom: 1px solid var(--rule); }
  .pb-kind:last-child { border-bottom: 0; }
  .pb-money { grid-template-columns: minmax(0, 1fr); }
  .pb-money .btn-ask { min-height: 44px; }
  .pb-filter select { flex: 1 1 100%; max-width: none; }
  .pb-r { grid-template-columns: minmax(0, 1fr) auto; gap: 2px 10px; padding-block: 6px; }
  .pb-r .pb-age-t { display: none; }
  .pb-print .btn-line { min-height: 38px; }
}
@media (prefers-reduced-motion: reduce) { .pb-pulse::after, .pb-age i { animation: none; } }
.prob-mp { font-size: 11px; font-weight: 600; }
.prob-mp.wb { color: var(--wb); }
.prob-mp.ozon { color: var(--ozon); }
.prob-mp.yandex { color: var(--yandex); }
.prob-article { font-size: 12px; color: var(--ink-muted); }
.prob-ask { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap; }
.prob-ask-store { display: flex; align-items: center; gap: 7px; font-size: 12.5px; cursor: pointer; }
.prob-ask-store input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.prob-ask-spacer, .prob-foot-spacer { flex-grow: 1; }
.prob-nothing { padding: 22px 16px; text-align: center; color: var(--ink-muted); font-size: 13px; }
.prob-nothing[hidden], #prob-more[hidden] { display: none; }
.prob-head-side { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; min-width: 0; }
.prob-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.prob-foot .foot-hint { margin: 0; }
.prob-number { color: var(--ink-muted); text-decoration: none; border-bottom: 1px dotted var(--ink-faint); }
.prob-number:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* --- QR-наклейки НА ТОВАР (2026-10-05) --------------------------------------
   Родня наклеек на ячейку (.lbl), но другое содержимое: главный элемент —
   артикул, он же закодирован в QR. Артикулы длинные (до 35 знаков), поэтому
   им отдана вся ширина под кодом, а не колонка сбоку. */
/* Размер приходит переменными из шаблона (он же задаёт @page), а вся вёрстка
   внутри — в em от собственного кегля наклейки. Поэтому один и тот же макет
   честно ужимается с 58×40 до 30×20: на маленьком рулоне он становится
   мельче, а не ломается и не вылезает за край. */
.glbl {
  width: var(--glbl-w, 58mm); height: var(--glbl-h, 40mm); box-sizing: border-box;
  /* Кегль, размер кода и отступы приходят с сервера на КАЖДУЮ наклейку —
     он один знает длину артикула и подбирает их так, чтобы текст влез
     целиком (`_label_fit`). Доля от стороны наклейки этого знать не может:
     22 знака и 35 знаков требуют разного кегля на одном и том же рулоне, и
     на 30×20 длинный артикул уходил за край. */
  font-size: var(--glbl-fs, 3mm);
  display: flex; align-items: center;
  gap: var(--glbl-gap, 0.8mm); padding: var(--glbl-pad, 1mm);
  border: 1px dashed var(--rule); background: #fff; color: #000;
  /* Страховка: что не влезло — обрезается по границе наклейки, а не наезжает
     на соседнюю. Расчёт выше делает этот случай редким. */
  overflow: hidden;
}
.glbl-qr {
  width: var(--glbl-qr, 24mm); height: var(--glbl-qr, 24mm);
  flex: none; display: grid; place-items: center;
}
.glbl-qr svg { width: 100%; height: 100%; }
.glbl-main { flex: 1; min-width: 0; display: flex; flex-direction: column;
             gap: calc(var(--glbl-fs, 3mm) * 0.2); }
/* Перенос по любому знаку: в артикулах нет пробелов, и без break-all строка
   «STIKxPLENKAMATTxA5xF2x35» уехала бы за край целиком. */
.glbl-article {
  font-size: 1em; font-weight: 700; line-height: 1.1; color: #000;
  word-break: break-all;
}
/* Свой межстрочный: без него адрес наследует 1.5 от body, а расчёт
   места на сервере исходит из 1.2 — и строка не влезала бы. */
.glbl-where { font-size: 0.8em; line-height: 1.2; color: #444; }
/* Вертикальная наклейка (20×30): код сверху, подписи под ним по центру —
   в строку рядом они на 20 мм ширины не помещаются ни тот, ни другой. */
.glbl.is-portrait {
  flex-direction: column; justify-content: center; text-align: center;
}
.glbl.is-portrait .glbl-main { flex: none; width: 100%; align-items: center; }

/* Страница: слева управление тремя шагами, справа предпросмотр ОДНОЙ наклейки
   в натуральную величину. Предпросмотр — главный элемент: формат выбирают
   глазами, а не по числам, и до печати, а не после. */
.glbl-top {
  display: grid; grid-template-columns: minmax(0, 1fr) 420px;
  gap: 14px; align-items: start; margin-bottom: 14px;
}
@media (max-width: 1100px) { .glbl-top { grid-template-columns: 1fr; } }
/* Шаг — своя полоса внутри ОДНОЙ панели, а не отдельная панель: три панели
   подряд читались как три разных дела, хотя это одно дело из трёх полей. */
.glbl-step { padding: 13px 16px 14px; border-bottom: 1px solid var(--rule); }
.glbl-step:last-child { border-bottom: none; }
.glbl-step-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 9px; }
.glbl-step-head .muted { font-size: 11.5px; }
/* Карточки одной высоты и равной ширины: ширина по длине текста давала рваный
   ряд. Рамка у всех 2px, у выбранной меняется только цвет — иначе карточка
   «прыгает» на 1px при выборе. */
.glbl-choices {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px;
}
.glbl-choice {
  height: 62px; box-sizing: border-box; display: flex; flex-direction: column;
  justify-content: center; gap: 3px; padding: 0 13px; font-size: 12px;
  text-decoration: none; color: var(--ink); background: var(--surface);
  border: 2px solid var(--rule); border-radius: var(--r-md);
}
.glbl-choice b { font-size: 13px; }
.glbl-choice > span { color: var(--ink-muted); }
a.glbl-choice:hover { border-color: var(--rule-strong); }
.glbl-choice.is-on { border-color: var(--accent); background: var(--accent-soft, var(--surface-2)); }
.glbl-choice .rack-field-label { margin: 0; }
.glbl-choice .btn-input { width: 100%; }
/* Плитка нарисована в пропорциях своего рулона и в ОДНОМ масштабе на все
   форматы: «58×40 или 30×20» видно до печати, а мелкая не растянута до
   размера соседей. Образец стоит в слоте одной высоты, поэтому плитки ровные
   при разной форме рулона. */
.glbl-sizes { display: flex; flex-wrap: wrap; gap: 8px; }
.glbl-size {
  width: 108px; height: 84px; box-sizing: border-box; flex: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; text-decoration: none; font: inherit;
  background: var(--surface); color: var(--ink);
  border: 2px solid var(--rule); border-radius: var(--r-md);
}
.glbl-size:hover { border-color: var(--rule-strong); }
.glbl-size.is-on { border-color: var(--accent); background: var(--accent-soft, var(--surface-2)); }
.glbl-size-slot { height: 38px; display: flex; align-items: center; }
.glbl-size-box {
  display: block; width: calc(var(--sw) * 0.6px); height: calc(var(--sh) * 0.6px);
  border: 1.5px solid var(--ink-muted); border-radius: var(--r-xs); background: var(--surface-2);
}
.glbl-size.is-on .glbl-size-box { border-color: var(--accent); background: var(--surface); }
.glbl-size-name { font-size: 12.5px; font-weight: 700; }
.glbl-size-own {
  flex: 1; min-width: 300px; height: 84px; box-sizing: border-box;
  display: flex; flex-direction: column; justify-content: center; gap: 7px;
  padding: 0 14px; border: 2px solid var(--rule); border-radius: var(--r-md);
  background: var(--surface);
}
.glbl-size-own .rack-field-label { margin: 0; }
.glbl-size-custom { display: flex; align-items: center; gap: 8px; }
.glbl-size-custom .btn-input { width: 86px; }
.glbl-count { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.glbl-note { font-size: 12.5px; color: var(--ink-muted); }
.glbl-note b { color: var(--ink); }
/* Бумага под наклейкой: серый фон, чтобы белая наклейка читалась как
   отдельный физический предмет, а не как часть панели. */
.glbl-paper {
  padding: 22px; display: grid; place-items: center;
  background: var(--surface-2); overflow: auto;
}
.glbl.is-preview { border-style: solid; box-shadow: 0 2px 10px rgba(0, 0, 0, .14); }
/* Полный список нужен только чтобы сверить состав: на экране свёрнут, в печать
   уходит всегда (правило в шаблоне, рядом с @page). */
.glbl-sheet { display: none; }
.glbl-sheet.is-open { display: flex; flex-wrap: wrap; gap: 6px; }
.glbl-foot {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px; margin-bottom: 14px; font-size: 12.5px;
}
/* Разделитель — рамка слева, а не символ «·» в разметке: точка текстом
   остаётся висеть в конце строки, когда ряд переносится. */
.glbl-fact + .glbl-fact { border-left: 1px solid var(--rule); padding-left: 12px; }
/* На узком экране ряд становится колонкой, и вертикальная черта слева от
   каждого пункта читается как отступ без смысла. */
@media (max-width: 700px) {
  .glbl-fact + .glbl-fact { border-left: none; padding-left: 0; }
}
@media print {
  .glbl { border-color: transparent; break-inside: avoid; }
  .glbl-sheet.is-open { gap: 0; }
}

/* Пустой маршрут подбора: объяснение вместо молчания (2026-10-06). */
.pick-empty-route {
  padding: 14px 16px; margin-bottom: 14px; font-size: 13px;
  border-left: 3px solid var(--accent);
}

/* --- Лист подбора на бумагу (2026-10-07) ---------------------------------
   Сборщик уходит с ним в зал: адрес крупный и моноширинный, чтобы читался с
   вытянутой руки, а две пустые клетки справа — под ручку. */
.pick-sheet { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md); padding: 16px 18px; }
.pick-sheet-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
                   padding-bottom: 10px; margin-bottom: 12px; border-bottom: 2px solid var(--ink); font-size: 13px; }
.pick-sheet-head .mono { margin-left: auto; color: var(--ink-muted); }
.pick-sheet-table { width: 100%; border-collapse: collapse; }
.pick-sheet-table th { font-size: 11px; text-align: left; padding: 5px 8px;
                       border-bottom: 1px solid var(--ink); color: var(--ink-muted); }
.pick-sheet-table td { padding: 7px 8px; border-bottom: 1px solid var(--rule); }
.pick-sheet-addr { font-size: 15px; font-weight: 700; letter-spacing: 0.02em; }
.pick-sheet-article { font-size: 12px; word-break: break-all; }
.pick-sheet-need { font-size: 15px; font-weight: 700; text-align: center; }
/* Клетка под ручку: именно рамка, а не пустое место — иначе на бумаге не
   видно, что сюда надо писать. */
.pick-sheet-box { border: 1px solid var(--ink-muted) !important; height: 26px; }
.pick-sheet-aside { margin-top: 10px; font-size: 12px; }
.pick-sheet-foot { margin-top: 18px; padding-top: 10px; border-top: 1px solid var(--rule);
                   font-size: 12px; color: var(--ink-muted); }
.pick-sheet-empty { font-size: 13px; color: var(--ink-muted); }
@media print {
  .pick-sheet { border: none; border-radius: 0; padding: 0; background: #fff; color: #000; }
  .pick-sheet-table td, .pick-sheet-table th { border-color: #000 !important; }
  .pick-sheet-box { border-color: #000 !important; }
}

/* Сегментированная группа кнопок (2026-10-11). Панель действий страницы
   набиралась одинаковыми пилюлями подряд, и шесть равных по весу кнопок
   читались как список без начала и конца. Группа соединяет родственные
   действия одной рамкой: внутри неё кнопки теряют свои границы и скругления,
   остаются только разделители. Снаружи группа ведёт себя как одна кнопка. */
.btn-group { display: inline-flex; align-items: stretch; border: 1px solid var(--rule-strong); border-radius: var(--r-sm); background: var(--surface); overflow: hidden; }
.btn-group .inline-form { display: inline-flex; align-items: stretch; gap: 0; }
.btn-group .btn-line,
.btn-group .btn-input { border: 0; border-radius: 0; box-shadow: none; background: transparent; }
.btn-group > * + *,
.btn-group .inline-form > * + *,
.btn-group .inline-form + .inline-form { border-left: 1px solid var(--rule); }
.btn-group .btn-line:hover { background: var(--overlay-hover); }
.btn-group .btn-input { padding-inline: 10px; }
@media (max-width: 700px) {
  /* На узком экране группа разворачивается в обычные кнопки: сцепленная
     строка из трёх элементов не помещается и начинает давить таблицу. */
  .btn-group { flex-wrap: wrap; border: 0; border-radius: 0; gap: 7px; background: none; }
  .btn-group .btn-line, .btn-group .btn-input { border: 1px solid var(--rule-strong); border-radius: var(--r-sm); }
  .btn-group > * + *, .btn-group .inline-form > * + *, .btn-group .inline-form + .inline-form { border-left: 0; }
}

/* --- Дашборд: сводка и воронка (2026-10-11) ------------------------------
   Страница была информативна, но читалась как таблица чисел: шесть равных
   плашек без иерархии и пять этапов воронки, между которыми стояли пустые
   шевроны. Правка не меняет ни одного числа — только то, как они поданы. */

/* Главные числа крупнее: 22px для показателя, вокруг которого строится
   смена, — это размер подписи, а не цифры. */
.stat-block .stat-val { font-size: 27px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.1; }
.stat-block .stat-suffix { font-size: 15px; font-weight: 500; color: var(--ink-faint); margin-left: 4px; }
.stat-block .eyebrow { letter-spacing: 0.1em; }

/* Три дня одной метрики: число слева, столбики справа. */
.stat-trend-block { flex: 1.5 1 290px; }
/* Столбики стоят ВПЛОТНУЮ к числу, а не у правого края блока: иначе на
   широком экране между «сегодня» и его же динамикой зияет пустое поле и
   связь между ними теряется. */
/* Число выравнивается по той же линии, что числа соседних блоков —
   иначе «сегодня» висит ниже всех и ряд выглядит сломанным. График
   прижимается к низу сам. */
.stat-trend { display: flex; align-items: flex-start; gap: 20px; margin-top: 2px; }
.stat-trend-figure { min-width: 0; flex: none; }
/* Столбики занимают всю оставшуюся ширину блока: при трёх фиксированных
   колонках по 26px справа оставалось полполосы пустоты. */
.stat-trend-bars { display: flex; align-items: flex-end; gap: 12px; height: 46px; flex: 1 1 auto; align-self: flex-end; }
.stat-trend-bar {
  position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  flex: 1 1 0; min-width: 18px; max-width: 150px; height: 100%;
}
.stat-trend-num {
  position: absolute; left: 50%; top: -15px; transform: translateX(-50%);
  font-size: 11px; font-weight: 600; color: var(--ink-muted); white-space: nowrap;
}
.stat-trend-bar.is-today .stat-trend-num { color: var(--ink); }
.stat-trend-fill {
  display: block; width: 100%; min-height: 3px; border-radius: 3px 3px 0 0;
  background: var(--rule-strong);
  /* Рост снизу: столбик «приезжает» на своё значение, а не появляется
     готовым — так заметно, что это живое число, а не картинка. */
  transform-origin: bottom; animation: stat-bar-grow 620ms cubic-bezier(0.2, 0.85, 0.3, 1) both;
}
.stat-trend-bar.is-today .stat-trend-fill { background: var(--accent); }
.stat-trend-cap {
  position: absolute; left: 50%; bottom: -15px; transform: translateX(-50%);
  font-size: 9.5px; color: var(--ink-faint); white-space: nowrap;
}
.stat-trend-bar.is-today .stat-trend-cap { color: var(--accent); font-weight: 600; }
.stat-trend-bars { margin-bottom: 16px; margin-top: 16px; }
@keyframes stat-bar-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) { .stat-trend-fill { animation: none; } }

/* Лента потока над этапами: ширина сегмента = доля этапа. */
.stage-flow { display: flex; gap: 2px; height: 6px; margin: 0 14px 12px; }
.stage-flow-seg { border-radius: var(--r-full); background: var(--rule-strong); min-width: 4px; }
.stage-flow-seg.tone-1 { background: var(--accent); }
.stage-flow-seg.tone-2 { background: var(--warn); }
.stage-flow-seg.tone-3 { background: color-mix(in srgb, var(--accent) 55%, var(--rule-strong)); }
.stage-flow-seg.tone-4 { background: var(--good); }
.stage-flow-seg.tone-5 { background: var(--critical); }

/* Кружок этапа: плотная заливка вместо тонкой обводки — обводка на светлом
   фоне выглядела как недорисованная иконка. */
.stage-row.route .stage-dot { border-width: 0; }
.stage-row.route .stage-dot.tone-accent { background: var(--accent-soft); color: var(--accent); }
.stage-row.route .stage-dot.tone-warn { background: var(--warn-soft); color: var(--warn); }
.stage-row.route .stage-dot.tone-good { background: var(--good-soft); color: var(--good); }
.stage-row.route .stage-dot.tone-crit { background: var(--critical-soft); color: var(--critical); }
.stage-total { font-size: 25px; font-weight: 700; letter-spacing: -0.025em; }

/* Полоса потока не должна липнуть к шапке панели. */
.stage-board .stage-flow { margin-top: 14px; }
/* Подсказка «i» показывается по наведению на этап, а не висит кружком в углу
   у каждого из пяти: пять серых кружков по верхней кромке читались как
   украшение и спорили с иконкой самого этапа. Выдержка клавиатуры при этом
   сохраняется — элемент остаётся в потоке и доступен по фокусу. */
.stage-row.route .stage-info { opacity: 0; transition: opacity 140ms ease; }
.stage-row.route .stage:hover .stage-info,
.stage-row.route .stage:focus-visible .stage-info { opacity: 1; }
@media (hover: none) { .stage-row.route .stage-info { opacity: 1; } }

/* Матрица «роль × право» на /users (2026-10-12). Колонок столько, сколько
   ролей, поэтому таблица прокручивается вбок внутри .table-wrap, а не
   превращается в карточки: в карточке пропала бы сама идея сравнения ролей. */
.perm-matrix th.perm-role { text-align: center; vertical-align: bottom; min-width: 128px; white-space: normal; }
.perm-role-name { font-weight: 600; color: var(--ink); }
.perm-role-meta { font-size: 11px; margin-top: 2px; }
.perm-role-actions { display: flex; justify-content: center; gap: 10px; margin-top: 6px; font-size: 11px; }
.perm-matrix tr.perm-group th { background: var(--surface-2); text-align: left; font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.perm-matrix td.perm-name { min-width: 240px; }
.perm-matrix td.perm-name > span:first-child { display: block; }
.perm-hint { display: block; font-size: 11px; margin-top: 1px; }
.perm-matrix tr.perm-child td.perm-name { padding-left: 32px; }
.perm-matrix td.perm-check { text-align: center; }
.perm-matrix td.perm-check input { width: 16px; height: 16px; cursor: pointer; }
.perm-matrix td.perm-check input:disabled { cursor: not-allowed; opacity: .35; }

/* «Повторить последнюю» внутри строки скана (2026-10-13): справа от поля,
   чтобы не отнимать у поля высоту. На узком экране подпись короче не
   становится — поле сужается, кнопка остаётся целой. */
.scan-reprint { flex: none; white-space: nowrap; }
@media (max-width: 600px) { .scan-reprint-text { display: none; } }

/* Раскладка товара (2026-10-17): своя рамка, чтобы по экрану было видно, что
   скан здесь — ПРИХОД, а не выдача, как на «Скане». */
.put-panel { border: 2px solid var(--good); }
.put-card { grid-template-columns: minmax(0, 1.4fr) auto auto auto; }
.put-qty { display: flex; flex-direction: column; gap: 6px; }
.put-stepper { display: flex; gap: 6px; align-items: center; }
.put-stepper input { width: 96px; font-size: 22px; font-weight: 700; text-align: center; }
.put-prefix { display: flex; flex-direction: column; gap: 6px; padding: 4px 16px 12px; }
.put-prefix-row { display: grid; grid-template-columns: minmax(0, 1fr) 140px auto; gap: 8px; }
.put-prefix-row .btn-input { height: 40px; font-size: 14px; }
.put-prefix-row #put-number { font-size: 18px; }
@media (max-width: 700px) { .put-prefix-row { grid-template-columns: minmax(0, 1fr) 110px; } .put-prefix-row .btn-line { grid-column: 1 / -1; } }
.put-suggest { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 0 16px 6px; }
.put-suggest[hidden] { display: none; }
.put-suggest #put-suggest-list { display: inline-flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 700px) {
  .put-card { grid-template-columns: 1fr; }
}

/* ---- /storage: карточка ячейки и таблица остатков (2026-10-25) -----------
   Карточка не длиннее окна: шапка с адресом наверху, тело прокручивается,
   полоса «Сохранить» прилипает к низу. Раньше карточка была выше экрана, и до
   сохранения приходилось листать. top учитывает липкую шапку сайта (~48px). */
.rack-layout { grid-template-columns: minmax(0, 1fr) 380px; }
.rack-detail {
  top: 62px; max-height: calc(100vh - 76px);
  display: flex; flex-direction: column;
}
.rack-detail .rack-detail-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 12px 16px 0; }
.rack-detail .rack-detail-body[hidden], .cd-head[hidden], .cd-empty[hidden] { display: none; }
.cd-empty { padding: 22px 18px; font-size: 12.5px; line-height: 1.5; color: var(--ink-muted); }
.cd-empty b { display: block; margin-bottom: 4px; font-size: 14px; color: var(--ink); }
.cd-head { padding: 14px 16px 12px; border-bottom: 1px solid var(--rule); background: var(--surface-2); }
.cd-addr-row { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.cd-addr { font-size: 26px; font-weight: 700; letter-spacing: 0.02em; line-height: 1.1; margin-right: 4px; }
.cd-sub { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
          margin-top: 6px; font-size: 11px; color: var(--ink-faint); }
.cd-sub b { color: var(--ink-muted); letter-spacing: 0.08em; }
.cd-print { display: inline-flex; gap: 6px; }
.cd-print [hidden] { display: none; }

.cd-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
            border: 1px solid var(--rule); border-radius: var(--r-sm); margin-bottom: 10px; }
.cd-stats[hidden] { display: none; }
.cd-stat { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; min-width: 0; }
.cd-stat + .cd-stat { border-left: 1px solid var(--rule); }
.cd-stat-label { font-size: 10.5px; color: var(--ink-faint); line-height: 1.2; }
.cd-stat-val { font-size: 15px; font-weight: 650; }
.cd-stat-val.is-warn { color: var(--warn); }
.cd-stat-val.is-crit { color: var(--critical); }
.rack-detail .rack-advice { display: block; margin: -2px 0 10px; padding: 8px 10px;
  border-radius: var(--r-sm); background: var(--warn-soft); }
.rack-detail .rack-advice[hidden] { display: none; }
.rack-detail .rack-cell-form { margin-top: 4px; gap: 12px; }

/* Тип адреса — сегментом, как переключатели режима схемы рядом. */
.cd-kind { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--rule);
           border-radius: var(--r-sm); background: var(--surface-2); }
.cd-kind label { flex: 1; position: relative; cursor: pointer; }
.cd-kind input { position: absolute; opacity: 0; pointer-events: none; }
.cd-kind span { display: block; padding: 6px 8px; text-align: center; font-size: 12px; font-weight: 550;
                color: var(--ink-muted); border-radius: calc(var(--r-sm) - 2px); }
.cd-kind input:checked + span { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
.cd-kind input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Строка позиции: артикул · −[кол-во]+ · убрать; под ней — что изменится. */
.rack-detail .cell-items { max-height: none; overflow: visible; gap: 6px; }
.rack-detail .cell-item-row { grid-template-columns: minmax(0, 1fr) auto 26px; row-gap: 3px; }
/* Артикул — во всю ширину карточки: в узком поле «POSTERxWHITEx105x148xNABORxF50x28»
   обрезался, и склейку двух артикулов в одном поле не было видно (2026-11-04).
   Количество и «убрать» — второй строкой, справа; подпись изменения — слева от них. */
.rack-detail .cell-item-row .ci-article { grid-column: 1 / -1; }
.rack-detail .cell-item-row .ci-stepper { grid-column: 2; grid-row: 2; }
.rack-detail .cell-item-row .ci-remove { grid-column: 3; grid-row: 2; }
.rack-detail .cell-item-row .ci-delta { grid-column: 1; grid-row: 2; align-self: center; }
.cell-item-row.is-changed .ci-article { border-color: var(--accent); }
/* Строка, найденная поиском по складу. */
.cell-item-row.is-found .ci-article { border-color: var(--warn); background: var(--warn-soft); box-shadow: 0 0 0 2px var(--warn-soft); }
.ci-stepper { display: inline-flex; align-items: stretch; border: 1px solid var(--rule-strong);
              border-radius: var(--r-sm); overflow: hidden; background: var(--surface); }
.ci-stepper .ci-stock { width: 48px; border: 0; border-radius: 0; box-shadow: none; padding: 6px 2px;
                        -moz-appearance: textfield; }
.ci-stepper .ci-stock::-webkit-outer-spin-button,
.ci-stepper .ci-stock::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ci-step { width: 28px; border: 0; background: var(--surface-2); color: var(--ink-muted);
           font-size: 15px; line-height: 1; cursor: pointer; }
.ci-step:hover { background: var(--accent-soft); color: var(--accent); }
.ci-delta { grid-column: 1 / -1; font-size: 11px; font-variant-numeric: tabular-nums; padding-left: 2px; }
.ci-delta[hidden] { display: none; }
.ci-delta.is-plus { color: var(--good); }
.ci-delta.is-minus { color: var(--critical); }
.ci-delta.is-new { color: var(--accent); }

.cd-help { font-size: 12px; color: var(--ink-muted); line-height: 1.5; }
.cd-help summary { cursor: pointer; color: var(--ink-faint); font-size: 11.5px; }
.cd-help p { margin: 6px 0 0; }

.cd-foot { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; gap: 10px;
           margin: 0 -16px; padding: 10px 16px; border-top: 1px solid var(--rule); background: var(--surface); }
.cd-dirty { flex: 1; font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.cd-dirty.is-dirty { color: var(--ink); font-weight: 650; }
.cd-foot [hidden] { display: none; }
.cd-foot .btn-line { min-width: 112px; }
.cd-foot .btn-line.accent:not(.is-ready) { opacity: 0.55; }
.cd-clear { align-self: flex-start; margin: 2px 0 14px; font-size: 12px; }

@media (max-width: 1180px) {
  .rack-layout { grid-template-columns: minmax(0, 1fr); }
  .rack-detail { max-height: none; }
  .rack-detail .rack-detail-body { overflow: visible; }
}

/* Таблица остатков: быстрые фильтры и сортировка. */
.stock-filters { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 16px; border-bottom: 1px solid var(--rule); }
.stock-filter { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
  border: 1px solid var(--rule-strong); border-radius: var(--r-full); background: var(--surface);
  color: var(--ink-muted); font-size: 12px; font-weight: 550; cursor: pointer; }
.stock-filter b { font-weight: 700; color: var(--ink); }
.stock-filter:hover { background: var(--overlay-hover); }
.stock-filter.tone-crit b { color: var(--critical); }
.stock-filter.tone-warn b { color: var(--warn); }
.stock-filter.is-zero { opacity: 0.45; }
.stock-filter.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }
.stock-filter.active b { color: inherit; }
.stock-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.rack-placed-table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.rack-placed-table th.sortable:hover { color: var(--ink); }
.rack-placed-table th.sortable::after { content: ""; margin-left: 4px; opacity: 0.6; }
.rack-placed-table th[aria-sort="ascending"]::after { content: "▲"; font-size: 8px; }
.rack-placed-table th[aria-sort="descending"]::after { content: "▼"; font-size: 8px; }
.rack-placed-table .num { text-align: right; }
.rack-placed-table .num .chip.small { margin-left: 0; }
.stock-rack { display: block; font-size: 11px; color: var(--ink-faint); margin-top: 1px; }
.stock-cover-far { color: var(--ink-faint); }
.stock-empty { margin: 0; padding: 18px 16px; font-size: 12.5px; color: var(--ink-faint); }
.stock-empty[hidden] { display: none; }
.stat-jump { cursor: pointer; }
.stat-jump:hover { background: var(--overlay-hover); }
.stat-jump:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rack-search { width: 210px; }
#placed-count { white-space: nowrap; }

/* ---- /materials: материалы производства (2026-10-25, тест) ---------------- */
.mt-flash { margin: 0 0 12px; padding: 10px 14px; border-radius: var(--r-sm); font-size: 13px; font-weight: 600; }
.mt-flash.good { background: var(--good-soft); color: var(--good); }
.mt-flash.crit { background: var(--critical-soft); color: var(--critical); }
.mt-add-form, .mt-ed-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; padding: 14px 16px; }
.mt-add-form .form-field, .mt-ed-form .form-field { min-width: 140px; }
.mt-add-form .form-field.grow, .mt-ed-form .form-field.grow { flex: 1 1 220px; }
.mt-add-form .form-field.short, .mt-ed-form .form-field.short { min-width: 0; width: 110px; }
.mt-add-form .btn-input, .mt-ed-form .btn-input { width: 100%; box-sizing: border-box; }
.mt-add-actions { display: flex; gap: 8px; }
.mt-add-form .form-field[hidden] { display: none; }
.mt-group-list { display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; }
.mt-group-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.mt-group-row .btn-input { width: 260px; max-width: 100%; box-sizing: border-box; }
.mt-add-form .mt-preview, .mt-ed-form .mt-preview { flex: 1 1 100%; }
.mt-detail { padding: 12px 16px; background: var(--surface-2); font-size: 12.5px; overflow-x: auto; }
.mt-detail p { margin: 0 0 8px; }
.mt-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.mt-filter input[type=search] { width: 220px; }
.mt-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.mt-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border: 1px solid var(--rule-strong); border-radius: var(--r-full); background: var(--surface); color: var(--ink-muted); font-size: 12.5px; font-weight: 550; text-decoration: none; }
.mt-chip b { font-family: var(--font-mono); font-weight: 600; color: var(--ink); }
.mt-chip:hover { background: var(--overlay-hover); color: var(--ink); }
.mt-chip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }
.mt-chip.on b { color: inherit; }
.mt-chip.crit b { color: var(--critical); }
.mt-table td { vertical-align: middle; }
.mt-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.mt-sub { margin-top: 2px; font-size: 11.5px; color: var(--ink-faint); }
.mt-big { font-family: var(--font-mono); font-size: 15px; font-weight: 650; }
.mt-big.neg { color: var(--critical); }
.mt-unit { font-size: 12px; color: var(--ink-faint); }
.mt-days { display: inline-block; min-width: 56px; padding: 3px 9px; border-radius: var(--r-full); font-family: var(--font-mono); font-size: 12px; font-weight: 650; text-align: center; }
.mt-days.crit { background: var(--critical-soft); color: var(--critical); }
.mt-days.warn { background: var(--warn-soft); color: var(--warn); }
.mt-days.good { background: var(--good-soft); color: var(--good); }
.mt-preview { align-self: center; font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.mt-mchips { display: inline-flex; flex-wrap: wrap; gap: 5px; }
.mt-mchip { padding: 3px 9px; border: 1px solid var(--rule); border-radius: var(--r-full); background: var(--surface-2); font-size: 12px; white-space: nowrap; }
.mt-norm { padding: 14px 16px; }
.mt-norm-h { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; margin-bottom: 8px; }
.mt-norm-rows { display: flex; flex-direction: column; gap: 6px; }
.mt-norm-row { display: grid; grid-template-columns: minmax(0, 2fr) 110px minmax(0, 1fr) minmax(0, 1fr) 30px; gap: 8px; align-items: center; }
.mt-norm-row .btn-input { width: 100%; box-sizing: border-box; }
.mt-norm-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 10px; }
@media (max-width: 700px) {
  .mt-norm-row { grid-template-columns: minmax(0, 1fr) 90px; }
    .mt-filter input[type=search] { width: 100%; }
}

/* ---- /materials: инфографика и боковая панель (2026-11-02) ---------------- */
.mt-gap { margin-bottom: 14px; }
.text-crit { color: var(--critical); }
.text-warn { color: var(--warn); }
.text-good { color: var(--good); }
.text-idle { color: var(--ink-muted); }
.mt-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-bottom: 14px; }
.mt-tile { display: grid; gap: 2px; padding: 11px 14px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); color: var(--ink); text-decoration: none; transition: border-color .15s, box-shadow .15s; }
.mt-tile:hover { border-color: var(--rule-strong); }
.mt-tile.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.mt-tile b { font-size: 14px; font-weight: 650; }
.mt-tile span { font-size: 12.5px; color: var(--ink-muted); }
.mt-tile em { font-style: normal; font-weight: 650; }
.mt-tile em.crit { color: var(--critical); }
.mt-tile em.warn { color: var(--warn); }
.mt-name { display: block; font-weight: 600; overflow-wrap: anywhere; }
.mt-sub { display: block; }
/* Шкала дней */
.mt-runlabel { display: block; font-size: 12.5px; font-weight: 600; }
/* Покрытие нормами и поток */
.mt-cover { display: grid; gap: 8px; padding: 12px 16px 14px; }
.mt-cbar { display: flex; gap: 2px; height: 20px; }
.mt-cbar span { display: block; min-width: 2px; }
.mt-cbar .yes { background: var(--good); border-radius: var(--r-xs) 0 0 var(--r-xs); }
.mt-cbar .no { background: var(--critical); border-radius: 0 var(--r-xs) var(--r-xs) 0; }
.mt-leg { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 12.5px; }
.mt-leg i { display: inline-block; width: 10px; height: 10px; margin-right: 6px; border-radius: 3px; }
.mt-leg i.yes { background: var(--good); }
.mt-leg i.no { background: var(--critical); }
.mt-flow { padding: 12px 16px 14px; overflow-x: auto; }
.mt-flow svg { display: block; width: 100%; min-width: 680px; max-width: 1040px; height: auto; margin-inline: auto; }
.mt-flow text { font-size: 13px; fill: var(--ink); }
.mt-flow text.mono { font-family: var(--font-mono); font-size: 12px; }
.mt-rib { fill: none; stroke: var(--ink-muted); stroke-opacity: .4; transition: stroke-opacity .15s; }
.mt-rib:hover { stroke-opacity: .8; }
.mt-rib.tone-crit { stroke: var(--critical); }
.mt-rib.tone-warn { stroke: var(--warn); }
.mt-rib.tone-good { stroke: var(--good); }
.mt-node { fill: var(--ink); }
.mt-node.tone-crit { fill: var(--critical); }
.mt-node.tone-warn { fill: var(--warn); }
.mt-node.tone-good { fill: var(--good); }
.mt-rate { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 13px; }
.mt-rate span { display: block; height: 7px; min-width: 2px; border-radius: 0 3px 3px 0; background: var(--accent); }
.mt-tray { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 0 16px 14px; font-size: 13px; }
/* Журнал */
.mt-mult { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
.mt-sm { display: grid; gap: 6px; padding: 11px 12px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); color: var(--ink); text-decoration: none; transition: border-color .15s; }
.mt-sm:hover { border-color: var(--rule-strong); }
.mt-sm.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.mt-bars { display: flex; align-items: flex-end; gap: 4px; height: 62px; }
.mt-bars.tall { margin-top: 8px; }
.mt-bars > span { flex: 1; display: grid; gap: 3px; justify-items: stretch; align-content: end; height: 100%; text-align: center; }
.mt-bars i { display: block; border-radius: 3px 3px 0 0; background: var(--accent); transform-origin: bottom; animation: mtRise .5s cubic-bezier(.2, .8, .2, 1) both; }
.mt-bars small { font-size: 10.5px; color: var(--ink-faint); }
@keyframes mtRise { from { transform: scaleY(.05); } }
.mt-lday { padding: 8px 16px; border-bottom: 1px solid var(--rule); background: var(--surface-2); font-size: 12.5px; font-weight: 700; }
.mt-lg { display: grid; grid-template-columns: 50px minmax(0, 1fr) auto 120px; gap: 0 12px; align-items: center; min-height: 48px; padding: 6px 16px; border-bottom: 1px solid var(--rule); }
summary.mt-lg { cursor: pointer; list-style: none; }
summary.mt-lg::-webkit-details-marker { display: none; }
summary.mt-lg:hover { background: var(--overlay-hover); }
.mt-t { font-size: 12px; color: var(--ink-muted); }
.mt-q { font-family: var(--font-mono); font-weight: 600; text-align: right; white-space: nowrap; }
.mt-hr { display: grid; grid-template-columns: 50px minmax(0, 1fr) auto; gap: 0 12px; padding: 5px 16px; border-bottom: 1px solid var(--rule); background: var(--surface-2); font-size: 12.5px; }
/* Меню «Ещё» и инвентаризация */
.mt-more { position: relative; }
.mt-more summary { list-style: none; cursor: pointer; }
.mt-more summary::-webkit-details-marker { display: none; }
.mt-more-list { position: absolute; z-index: 30; top: calc(100% + 6px); right: 0; display: grid; min-width: 190px; padding: 5px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-3); }
.mt-more-list button { min-height: 40px; padding: 0 10px; border: 0; border-radius: var(--r-sm); background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.mt-more-list button:hover { background: var(--overlay-hover); }
.mt-inv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.mt-inv-step { display: flex; flex-direction: column; gap: 10px; padding: 16px; }
.mt-inv-step h2 { margin: 0; font-size: 15px; font-weight: 650; }
.mt-inv-step p { margin: 0; font-size: 13px; color: var(--ink-muted); }
.mt-inv-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: auto; }
.mt-inv-acts input[type=file] { flex: 1 1 200px; min-width: 0; font-size: 13px; }
.mt-inv-foot { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; padding: 12px 16px; }
@media (max-width: 720px) { .mt-inv { grid-template-columns: minmax(0, 1fr); } }
/* Боковая панель */
.mt-scrim { position: fixed; inset: 0; z-index: 90; background: rgba(10, 13, 18, .42); opacity: 0; pointer-events: none; transition: opacity .18s; }
.mt-scrim.open { opacity: 1; pointer-events: auto; }
.mt-dr { position: fixed; z-index: 91; top: 0; right: 0; bottom: 0; width: 520px; max-width: 100vw; overflow-y: auto; background: var(--surface); box-shadow: var(--shadow-3); transform: translateX(103%); visibility: hidden; transition: transform .18s ease-in, visibility 0s .18s; }
.mt-dr.open { transform: none; visibility: visible; transition: transform .28s cubic-bezier(.2, .8, .2, 1), visibility 0s; }
.mt-dr-x { position: absolute; z-index: 1; top: 4px; right: 6px; width: 44px; height: 44px; border: 0; border-radius: var(--r-sm); background: none; color: var(--ink); font-size: 22px; line-height: 1; cursor: pointer; }
.mt-dr-x:hover { background: var(--overlay-hover); }
.mt-dp[hidden] { display: none; }
.mt-dp .panel-head { padding-right: 56px; min-height: 52px; }
.mt-dp .mt-add-form .form-field.short, .mt-dp .mt-ed-form .form-field.short { flex: 1 1 130px; width: auto; }
.mt-dp .mt-norm-row { grid-template-columns: minmax(0, 1fr) 120px 30px; padding-bottom: 8px; border-bottom: 1px solid var(--rule); }
.mt-dp .mt-norm-row .mt-norm-grp { grid-row: 1; grid-column: 1 / 3; }
.mt-dp .mt-norm-row .ci-remove { grid-row: 1; grid-column: 3; }
.mt-dp .mt-norm-row select[name=material_id] { grid-row: 2; grid-column: 1; }
.mt-dp .mt-norm-row input[name=qty] { grid-row: 2; grid-column: 2 / 4; }
.mt-dp .mt-norm-row select[name=basis] { grid-row: 3; grid-column: 2 / 4; }
.mt-dp .mt-norm-row select[name=share] { grid-row: 3; grid-column: 1; }
.mt-card .mt-mchips { display: flex; }
.mt-zone-sub { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.mt-card { display: grid; gap: 14px; padding: 14px 16px 18px; font-size: 13px; }
.mt-hero { font-family: var(--font-mono); font-size: 28px; font-weight: 650; }
.mt-hero.neg { color: var(--critical); }
.mt-card-acts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.mt-kv { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: 4px 12px; }
.mt-kv span:nth-child(odd) { color: var(--ink-muted); }
@media (max-width: 820px) {
  .mt-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mt-lg { grid-template-columns: 44px minmax(0, 1fr) auto; }
  .mt-lg .chip { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mt-bars i, .mt-dr, .mt-scrim { animation: none; transition: none; }
}

/* ---- /materials: схема склада (2026-10-26) ----------------------------- */
.mt-plan-layout { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px; align-items: start; }
.mt-plan-panel .panel-head { flex-wrap: wrap; gap: 6px 12px; }
.mt-room-wrap { padding: 14px 16px 16px; overflow-x: auto; }
.mt-room { position: relative; width: 100%; border: 2px solid var(--ink-muted); border-radius: 6px;
  background-color: var(--surface);
  background-image: linear-gradient(var(--rule) 1px, transparent 1px), linear-gradient(90deg, var(--rule) 1px, transparent 1px);
  background-size: calc(100% / var(--cells-x)) calc(100% / var(--cells-y)); touch-action: none; user-select: none; }
.mt-room.is-edit { outline: 2px dashed var(--accent-soft); outline-offset: 4px; }
.mt-room-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; color: var(--ink-faint); font-size: 13px; }
.mt-zone { position: absolute; box-sizing: border-box; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 5px 7px; border: 2px solid var(--rule-strong); border-radius: 6px; background: var(--surface);
  font: inherit; font-size: 12px; line-height: 1.2; color: var(--ink); text-align: left; overflow: hidden; cursor: pointer; }
.mt-zone b { font-weight: 650; overflow: hidden; text-overflow: ellipsis; max-width: 100%; white-space: nowrap; }
.mt-zone-sub { font-size: 11px; color: var(--ink-muted); white-space: nowrap; }
.mt-zone.tone-crit { border-color: var(--critical); background: var(--critical-soft); }
.mt-zone.tone-warn { border-color: var(--warn); background: var(--warn-soft); }
.mt-zone.tone-good { border-color: var(--good); background: var(--good-soft); }
.mt-zone:hover { box-shadow: 0 0 0 3px var(--accent-soft); }
.mt-zone.is-sel { box-shadow: 0 0 0 3px var(--accent); z-index: 2; }
.mt-room.is-edit .mt-zone { cursor: move; }
.mt-zone.is-drag { opacity: 0.85; z-index: 3; }
.mt-zone.is-saving { opacity: 0.6; }
.mt-grip { position: absolute; right: 0; bottom: 0; width: 14px; height: 14px; background: var(--accent); border-radius: 4px 0 3px 0; cursor: nwse-resize; }
.mt-zone-card { position: sticky; top: 62px; }
.mt-zone-none { padding: 18px 16px; font-size: 12.5px; line-height: 1.5; color: var(--ink-muted); }
.mt-zone-none b { display: block; margin-bottom: 4px; font-size: 14px; color: var(--ink); }
.mt-zone-none[hidden], .mt-zone-info[hidden] { display: none; }
.mt-zone-title { font-size: 18px; font-weight: 700; }
.mt-zone-item { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; align-items: center; padding: 7px 0; border-bottom: 1px solid var(--rule); font-size: 13px; }
.mt-put { display: flex; gap: 8px; margin-top: 12px; }
.mt-put select { flex: 1; min-width: 0; }
.mt-zone-edit { display: flex; flex-direction: column; gap: 10px; padding: 12px 16px 16px; border-top: 1px solid var(--rule); background: var(--surface-2); }
.mt-zone-edit .btn-input { width: 100%; box-sizing: border-box; }
.mt-geom { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
@media (max-width: 1000px) {
  .mt-plan-layout { grid-template-columns: minmax(0, 1fr); }
  .mt-zone-card { position: static; }
  .mt-zone-sub { display: none; }
  /* На телефоне план шире экрана и листается внутри блока: в 360px
     стеллаж превращался в чёрточку без подписи. */
  .mt-room { min-width: 640px; }
}

/* ===================================================================
   /admin/finance — ФИН.аналитика (2026-10-27, новый макет)
   Главное число + состав по маркетплейсам, плитки-переключатели метрики,
   слоистый график с прошлым периодом, каскад удержаний, выводы. Префикс
   .fz- — чтобы не пересекаться с прежними .fin-* (их правила удалены, а
   id/классы .fin-* в разметке остались только как крючки для скрипта).
   Цвета графика — классами на переменных темы: смена темы без
   перезагрузки перекрашивает SVG сама.
   ------------------------------------------------------------------- */
.fz { display: flex; flex-direction: column; gap: 16px; }
.fz .hint { margin: 0; }
.fz-card {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: var(--shadow-1); padding: 18px 20px; min-width: 0;
}
.fz-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.fz-card-head.wrap { flex-wrap: wrap; }
.fz-foot { margin-top: 12px !important; }

@media (prefers-reduced-motion: no-preference) {
  .fz-rise { animation: fzRise 520ms cubic-bezier(.2,.8,.2,1) both; }
}
@keyframes fzRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fzGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes fzReveal { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes fzPing { 75%, 100% { transform: scale(2.6); opacity: 0; } }
@keyframes fzSpin { to { transform: rotate(360deg); } }
@keyframes fzFade { from { opacity: 0; } to { opacity: 1; } }

/* Шапка */
/* ⚠ Шапка — свой слой ВЫШЕ карточек: анимация появления (.fz-rise, transform)
   делает каждый блок отдельным контекстом наложения, и без z-index поповер
   магазинов (z-index: 40 — только внутри шапки) уходил ПОД карточки ниже. */
.fz-head { position: relative; z-index: 20; display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 14px 20px; }
.fz-head-text { display: flex; flex-direction: column; gap: 5px; }
.fz-head-text h1 { margin: 0; font-size: 27px; line-height: 1.15; letter-spacing: -0.02em; }
.fz-head-text .eyebrow { line-height: 1.2; }
.fz-head-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.fz-sync-form { margin: 0; }

/* Сегменты: один вид у периода, разбивки и формы графика. */
.fz-seg { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; background: var(--surface-2); border: 1px solid var(--rule); border-radius: 10px; }
.fz-seg button {
  border: 0; background: transparent; cursor: pointer; font: inherit; font-size: 13px; font-weight: 500;
  color: var(--ink-muted); padding: 7px 12px; border-radius: 7px; min-height: 32px;
  transition: background 180ms ease, color 180ms ease, box-shadow 180ms ease;
}
.fz-seg button:hover:not(:disabled) { color: var(--ink); }
.fz-seg button.on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08), 0 0 0 1px var(--rule); }
.fz-seg button:disabled { opacity: .45; cursor: default; }
.fz-seg.small button { font-size: 12px; padding: 5px 10px; min-height: 28px; }
.fz-seg button:focus-visible, .fz-btn:focus-visible, .fz-tile:focus-visible, .fz-mprow:focus-visible,
.fz-lg:focus-visible, .fz-rank-row:focus-visible, .fz-wf-row:focus-visible, .fz-more:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.fz-btn {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; min-height: 36px;
  border-radius: 9px; border: 1px solid var(--rule-strong); background: var(--surface);
  font: inherit; font-size: 13px; font-weight: 500; color: var(--ink); cursor: pointer; text-decoration: none;
  transition: border-color 180ms ease, background 180ms ease, transform 120ms ease;
}
.fz-btn:hover { border-color: var(--accent); }
.fz-btn:active { transform: scale(.97); }
.fz-btn svg { width: 16px; height: 16px; flex: none; }
.fz-btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.fz-btn.primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
.fz-btn.small { padding: 5px 10px; min-height: 30px; font-size: 12px; }
.fz-btn:disabled { cursor: default; opacity: .75; }
.fz-btn.is-busy svg { animation: fzSpin .9s linear infinite; }
.fz-btn.is-filtered { border-color: var(--accent); color: var(--accent); }

/* Фильтр магазинов (поповер) */
.fin-stores-picker { position: relative; flex: none; }
.fin-stores-btn .fin-caret, #fin-stores-btn .fin-caret { width: 12px; height: 12px; opacity: .6; transition: transform 150ms ease; }
#fin-stores-btn[aria-expanded="true"] .fin-caret { transform: rotate(180deg); }
.fin-stores-pop {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  width: min(420px, 88vw); max-height: 60vh; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  opacity: 0; transform: translateY(-4px); pointer-events: none;
  transition: opacity 140ms ease, transform 140ms ease;
}
.fin-stores-pop.open { opacity: 1; transform: none; pointer-events: auto; }
.fin-stores-pop-head { display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid var(--rule); }
.fin-stores-pop-head .btn-input { flex: 1; min-width: 0; }
.fin-stores-all { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-muted); cursor: pointer; }
.fin-stores-pop-body { overflow-y: auto; padding: 6px 4px 10px; }
.fin-stores-group[hidden] { display: none; }
.fin-stores-group-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 8px 5px; }
.fin-stores-group-all { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--ink-faint); cursor: pointer; }
.fin-store-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--r-xs); font-size: 12px; cursor: pointer; }
.fin-store-row[hidden] { display: none; }
.fin-store-row:hover { background: var(--overlay-hover); }
.fin-store-row:has(input:checked) { background: var(--accent-soft); }
.fin-store-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fin-store-row.is-empty { opacity: .45; cursor: default; }
.fin-stores-none { padding: 10px; margin: 0; }

/* «Свой период» раскрывается сегментом */
.fz-range-form { display: none; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: -6px; }
.fz-range-form.is-open { display: flex; }
.fz-range-form .btn-input { padding: 5px 8px; font-size: 12px; width: auto; }
.fin-range-dash { color: var(--ink-faint); font-size: 12px; }

/* Свежесть данных */
.fz-fresh { display: flex; align-items: center; gap: 9px; font-size: 12px; color: var(--ink-muted); margin-top: -6px; }
.fz-live { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--good); flex: none; }
.fz-live.warn { background: var(--warn); }
@media (prefers-reduced-motion: no-preference) {
  .fz-live::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: inherit; animation: fzPing 2.2s cubic-bezier(0,0,.2,1) infinite; }
}

/* Чипы динамики: знак + цвет, не цвет один. */
.fz-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: var(--r-full); font-size: 12px; font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.fz-chip[hidden] { display: none; }
.fz-chip.up { background: var(--good-soft); color: var(--good); }
.fz-chip.down { background: var(--critical-soft); color: var(--critical); }
.fz-chip.flat { background: var(--surface-2); color: var(--ink-muted); }

.fz-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; flex: none; }
.fz-bg-wb { background: var(--wb); }
.fz-bg-ozon { background: var(--ozon); }
.fz-bg-yandex { background: var(--yandex); }
.fz-bg-accent { background: var(--accent); }

/* Итог */
.fz-top { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); gap: 16px; }
.fz-hero { display: flex; flex-direction: column; gap: 12px; padding: 22px 24px; }
.fz-hero-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.fz-hero-main { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.fz-hero-value { font-size: 40px; font-weight: 700; letter-spacing: -0.03em; line-height: 1.05; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fz-hero-head .fz-chip { margin-top: 22px; }
#fin-hero-prev { margin-top: -6px !important; }
.fz-split { display: flex; height: 12px; gap: 2px; border-radius: var(--r-full); overflow: hidden; background: var(--surface-2); }
.fz-split span { min-width: 0; transition: flex-grow 600ms cubic-bezier(.2,.8,.2,1); }
.fz-mprows { display: flex; flex-direction: column; gap: 2px; margin: 0 -10px; }
.fz-mprow {
  display: grid; grid-template-columns: 12px minmax(0, 1fr) auto auto; align-items: center; gap: 10px;
  padding: 9px 10px; border: 0; border-radius: 9px; background: transparent; font: inherit; color: inherit;
  text-align: left; cursor: pointer; transition: background 180ms ease, opacity 250ms ease;
}
.fz-mprow:hover { background: var(--overlay-hover); }
.fz-mprow-name { display: flex; flex-direction: column; min-width: 0; }
.fz-mprow-name b { font-weight: 600; }
.fz-mprow-sum { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fz-mprow:not(.is-on) { opacity: .45; }
.fz-mprow:not(.is-on) .fz-dot { background: transparent; box-shadow: inset 0 0 0 2px var(--ink-faint); }
.fz-mprow.is-partial .fz-dot { box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--ink-faint); }
.fz-facts { display: flex; flex-wrap: wrap; gap: 10px 22px; padding-top: 12px; border-top: 1px solid var(--rule); }
.fz-facts[hidden] { display: none; }
.fz-fact { display: flex; flex-direction: column; gap: 2px; }
.fz-fact b { font-size: 15px; font-variant-numeric: tabular-nums; }

.fz-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.fz-tile {
  display: flex; flex-direction: column; gap: 8px; padding: 15px 17px; min-width: 0; text-align: left;
  border-radius: var(--r-md); background: var(--surface); border: 1px solid var(--rule); box-shadow: var(--shadow-1);
  font: inherit; color: inherit; cursor: pointer;
  transition: border-color 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}
.fz-tile:hover:not(:disabled):not(.is-static) { transform: translateY(-2px); border-color: var(--rule-strong); box-shadow: var(--shadow-2); }
.fz-tile.on { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-1); }
.fz-tile.is-static { cursor: default; }
.fz-tile:disabled { opacity: .5; cursor: not-allowed; }
.fz-tile-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 22px; }
.fz-tile-val { font-size: 23px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fz-spark { width: 100%; height: 34px; display: block; }
.fz-spark-line { fill: none; stroke: var(--ink-faint); stroke-width: 1.6; vector-effect: non-scaling-stroke; stroke-linejoin: round; transition: d 500ms cubic-bezier(.2,.8,.2,1); }
.fz-spark-fill { fill: transparent; stroke: none; transition: d 500ms cubic-bezier(.2,.8,.2,1), fill 200ms ease; }
.fz-tile.on .fz-spark-line { stroke: var(--accent); }
.fz-tile.on .fz-spark-fill { fill: var(--accent-soft); }

/* График */
.fz-chart-card { padding: 18px 20px 14px; }
.fz-chart-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.fz-chart-title { display: flex; flex-direction: column; gap: 4px; }
.fz-chart-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.fz-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
.fz-lg { display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 12px; color: var(--ink-muted); padding: 5px 8px; border: 0; border-radius: 7px; background: transparent; cursor: pointer; transition: background 180ms ease; }
.fz-lg:hover { background: var(--overlay-hover); }
.fz-lg.is-static { cursor: default; }
.fz-lg.is-static:hover { background: transparent; }
.fz-swatch { width: 14px; height: 3px; border-radius: 2px; background: var(--accent); }
.fz-dash { width: 16px; border-top: 2px dashed var(--ink-muted); }

.fz-chart-wrap { padding-left: 58px; }
.fz-chart { position: relative; height: 300px; }
.fz-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.fz-yaxis { position: absolute; inset: 0; pointer-events: none; }
.fz-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--rule); }
.fz-grid.base { border-top: 1px solid var(--rule-strong); }
.fz-ylab { position: absolute; left: -58px; width: 50px; text-align: right; transform: translateY(-50%); font-size: 10.5px; color: var(--ink-faint); white-space: nowrap; }
@media (prefers-reduced-motion: no-preference) {
  .fz-plot.is-reveal { animation: fzReveal 1s cubic-bezier(.3,.7,.2,1) both; }
  .fz-layer, .fz-line-total, .fz-line-prev { transition: d 550ms cubic-bezier(.2,.8,.2,1), opacity 300ms ease; }
}
.fz-layer { stroke: var(--surface); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.fz-fill-wb { fill: var(--wb); fill-opacity: .85; }
.fz-fill-ozon { fill: var(--ozon); fill-opacity: .8; }
.fz-fill-yandex { fill: var(--yandex); fill-opacity: .85; }
.fz-layer.fz-fill-total { fill: var(--accent); fill-opacity: .13; stroke: none; }
.fz-bar.fz-fill-total { fill: var(--accent); fill-opacity: .85; }
.fz-bar { stroke: none; }
.fz-line-total { fill: none; stroke: var(--accent); stroke-width: 2.4; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.fz-line-prev { fill: none; stroke: var(--ink); stroke-opacity: .5; stroke-width: 1.6; stroke-dasharray: 5 5; vector-effect: non-scaling-stroke; }
.fz-now { stroke: var(--accent); stroke-opacity: .4; stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.fz-plot .is-hidden { opacity: 0; pointer-events: none; }

.fz-hit { position: absolute; inset: 0; cursor: crosshair; }
.fz-cross { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--ink); opacity: .22; pointer-events: none; }
.fz-cross[hidden] { display: none; }
.fz-hdot { position: absolute; width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px; border-radius: 50%; border: 2px solid var(--surface); pointer-events: none; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
.fz-best {
  position: absolute; transform: translate(-50%, calc(-100% - 12px)); padding: 4px 9px; border-radius: 7px;
  background: var(--surface); border: 1px solid var(--rule-strong); font-size: 11.5px; white-space: nowrap;
  pointer-events: none; box-shadow: var(--shadow-1); transition: opacity 150ms ease;
}
.fz-best[hidden] { display: none; }
.fz-best::after {
  content: ""; position: absolute; left: 50%; bottom: -5px; width: 8px; height: 8px; background: var(--surface);
  border-right: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule-strong); transform: translateX(-50%) rotate(45deg);
}
.fz-best.is-dim { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  /* Только прозрачность: fzRise гасит transform, и метка съезжала с точки. */
  .fz-best.is-in { animation: fzFade 450ms 700ms both; }
}
/* Подсказка — тёмная плашка поверх любого цвета слоёв. */
.fz-tip {
  position: absolute; top: 6px; left: 0; z-index: 3; pointer-events: none; min-width: 230px;
  padding: 11px 13px; border-radius: 10px; background: var(--ink); color: var(--surface);
  font-size: 12px; box-shadow: 0 12px 32px -8px rgba(0,0,0,.35);
  opacity: 0; transform: translateY(4px); transition: opacity 120ms ease, transform 120ms ease;
}
.fz-tip.show { opacity: 1; transform: none; }
.fz-tt-day { font-weight: 700; margin-bottom: 6px; }
.fz-tt-day span { display: block; font-weight: 400; font-size: 10.5px; opacity: .65; margin-top: 1px; }
.fz-tt-table { border-collapse: collapse; width: 100%; }
.fz-tt-table th, .fz-tt-table td { padding: 2px 0 2px 12px; border: 0; background: none; color: inherit; font-size: 12px; text-align: right; white-space: nowrap; }
.fz-tt-table th { padding-left: 0; text-align: left; font-weight: 500; display: flex; align-items: center; gap: 7px; }
.fz-tt-table thead td { font-size: 10px; opacity: .6; text-transform: uppercase; letter-spacing: .05em; }
.fz-tt-table .fz-dot { width: 8px; height: 8px; }
.fz-tt-was { opacity: .65; }
.fz-tt-table tr.is-total th, .fz-tt-table tr.is-total td { border-top: 1px solid color-mix(in srgb, var(--surface) 25%, transparent); padding-top: 5px; font-weight: 700; }
.fz-tt-d.up { color: color-mix(in srgb, var(--good) 70%, var(--surface)); }
.fz-tt-d.down { color: color-mix(in srgb, var(--critical) 70%, var(--surface)); }

.fz-xaxis { position: relative; height: 22px; margin-top: 6px; }
.fz-xaxis span { position: absolute; top: 2px; transform: translateX(-50%); font: 400 10.5px/1 var(--font-mono); color: var(--ink-faint); white-space: nowrap; }
.fz-xaxis span.is-today { color: var(--accent); font-weight: 700; }
.fz-xaxis.is-tilted { height: 40px; }
.fz-xaxis.is-tilted span { transform: translateX(-50%) rotate(-45deg); transform-origin: top center; margin-top: 2px; }
.fz-chart-note { margin-top: 6px !important; }
.fz-details { margin-top: 6px; }
.fz-details summary { cursor: pointer; }

/* Два блока в ряд */
.fz-grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 520px), 1fr)); gap: 16px; align-items: start; }

/* Каскад удержаний */
.fz-keep { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.fz-keep-big { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; color: var(--good); font-variant-numeric: tabular-nums; }
.fz-wf-row {
  display: grid; grid-template-columns: minmax(0, 190px) minmax(0, 1fr) 96px 46px 14px; align-items: center; gap: 12px;
  width: 100%; padding: 9px 4px; border: 0; border-bottom: 1px solid var(--rule); background: transparent;
  font: inherit; color: inherit; text-align: left;
}
button.fz-wf-row { cursor: pointer; border-radius: 0; transition: background 180ms ease; }
button.fz-wf-row:hover { background: var(--overlay-hover); }
.fz-wf-row.is-total { border-bottom: 0; }
.fz-wf-cat:last-of-type .fz-wf-row { border-bottom-color: var(--rule-strong); }
.fz-wf-name { display: flex; flex-direction: column; min-width: 0; }
.fz-wf-name > span:first-child, .fz-wf-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-wf-name .hint { display: flex; align-items: center; gap: 4px; }
.fz-wf-name .fz-dot { width: 7px; height: 7px; }
.fz-wf-track { position: relative; height: 20px; }
.fz-wf-bar { position: absolute; top: 0; bottom: 0; border-radius: 4px; transform-origin: right center; }
.fz-wf-bar.pos { background: var(--accent); transform-origin: left center; }
.fz-wf-bar.neg { background: var(--critical); opacity: .85; }
.fz-wf-bar.refund { background: var(--good); opacity: .6; transform-origin: left center; }
.fz-wf-bar.keep { background: var(--good); transform-origin: left center; }
@media (prefers-reduced-motion: no-preference) {
  .fz-wf-bar { animation: fzGrow 700ms cubic-bezier(.2,.8,.2,1) both; }
}
.fz-wf-val { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fz-wf-pct { text-align: right; }
.fz-wf-caret { width: 14px; height: 14px; color: var(--ink-faint); transition: transform 180ms ease; }
.fin-exp-toggle[aria-expanded="true"] .fz-wf-caret { transform: rotate(180deg); }
.fz-wf-items { padding: 6px 4px 10px 16px; border-bottom: 1px solid var(--rule); }
.fz-wf-items[hidden] { display: none; }
.fz-wf-item { display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 3px 0; font-size: 12.5px; }
.fz-wf-item-dots { display: flex; gap: 3px; }
.fz-wf-item-dots .fz-dot { width: 7px; height: 7px; }
.fz-wf-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-wf-item-name.is-pending { color: var(--ink-faint); font-style: italic; }
#fin-expense-range.is-wider { color: var(--warn); cursor: help; }
#fin-expense-note:empty { display: none; }

/* Что важно */
.fz-insights { display: flex; flex-direction: column; }
.fz-ins { display: flex; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--rule); }
.fz-ins:last-child { border-bottom: 0; }
.fz-ins-ic { width: 30px; height: 30px; border-radius: 8px; flex: none; display: grid; place-items: center; }
.fz-ins-ic svg { width: 16px; height: 16px; }
.fz-ins-ic.up { background: var(--good-soft); color: var(--good); }
.fz-ins-ic.down { background: var(--critical-soft); color: var(--critical); }
.fz-ins-ic.info { background: var(--accent-soft); color: var(--accent); }
.fz-ins-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fz-ins-body b { font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .fz-ins { animation: fzRise 400ms cubic-bezier(.2,.8,.2,1) both; }
  .fz-ins:nth-child(2) { animation-delay: 50ms; }
  .fz-ins:nth-child(3) { animation-delay: 100ms; }
  .fz-ins:nth-child(4) { animation-delay: 150ms; }
  .fz-ins:nth-child(n+5) { animation-delay: 200ms; }
}

/* Рейтинг магазинов и причины */
.fz-rank { display: flex; flex-direction: column; margin: 0 -8px; }
.fz-rank-row {
  display: grid; grid-template-columns: 22px minmax(0, 1.25fr) minmax(0, 2fr) 104px 62px; align-items: center; gap: 12px;
  padding: 8px; border: 0; border-radius: 9px; background: transparent; font: inherit; color: inherit; text-align: left; cursor: pointer;
  transition: background 180ms ease;
}
.fz-rank-row:hover { background: var(--overlay-hover); }
.fz-rank-row .fz-chip { justify-self: end; }
.fz-rank-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.fz-rank-name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-rank-sum { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1.2; }
.fz-rank-sum i { display: block; font-style: normal; font-weight: 400; font-size: 10.5px; color: var(--ink-faint); }
.fz-track { position: relative; display: block; height: 9px; border-radius: var(--r-full); background: var(--surface-2); overflow: hidden; }
.fz-track.thin { height: 6px; }
.fz-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: var(--r-full); transform-origin: left center; }
@media (prefers-reduced-motion: no-preference) {
  .fz-fill { animation: fzGrow 800ms cubic-bezier(.2,.8,.2,1) both; }
}
.fz-more {
  display: block; width: 100%; margin-top: 8px; padding: 9px; border: 1px dashed var(--rule-strong); border-radius: 9px;
  background: transparent; font: inherit; font-size: 12.5px; color: var(--ink-muted); cursor: pointer; transition: border-color 180ms ease, color 180ms ease;
}
.fz-more:hover { border-color: var(--accent); color: var(--accent); }
.fz-more[hidden] { display: none; }
.fz-reasons { display: flex; flex-direction: column; gap: 13px; }
.fz-reason { display: flex; flex-direction: column; gap: 6px; }
.fz-reason-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.fz-reason-num { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Товары: цена и что остаётся */
.fz-sku { padding: 18px 20px 14px; }
.fz-sku-stats { display: flex; flex-wrap: wrap; gap: 10px 26px; padding-bottom: 14px; }
.fz-sku-stats .fz-fact b .hint { font-weight: 400; }
.fz-sku-tier {
  display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; margin-bottom: 14px;
  border-radius: 10px; background: var(--accent-soft); font-size: 12.5px; line-height: 1.5;
}
.fz-sku-tier[hidden] { display: none; }
.fz-sku-tier svg { width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--accent); }
.fz-sku-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.fz-sku-bar .btn-input { width: 260px; max-width: 100%; }
.fz-count { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 3px; border-radius: var(--r-full); background: var(--surface-2); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.fz-seg button.on .fz-count { background: var(--accent-soft); color: var(--accent); }
.fz-sku-wrap { margin: 0 -8px; }
.fz-sku-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.fz-sku-table th { padding: 8px; border-bottom: 1px solid var(--rule); text-align: left; white-space: nowrap; }
.fz-sku-table th.r, .fz-sku-table td.r, .fz-scen .r { text-align: right; }
.fz-sort {
  border: 0; background: none; padding: 0; font: inherit; cursor: pointer;
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted);
}
.fz-sort:hover, .fz-sort.is-on { color: var(--ink); }
.fz-sort.is-on::after { content: " ↓"; color: var(--accent); }
.fz-sort.is-on[data-dir="asc"]::after { content: " ↑"; }
.fz-sku-split-h { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.fz-sku-table td { padding: 9px 8px; border-bottom: 1px solid var(--rule); vertical-align: top; font-variant-numeric: tabular-nums; }
.fz-sku-row { cursor: pointer; transition: background 160ms ease; }
.fz-sku-row:hover, .fz-sku-row.is-open { background: var(--overlay-hover); }
.fz-sku-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.fz-sku-row.is-bad td:first-child { box-shadow: inset 3px 0 0 var(--critical); }
@media (prefers-reduced-motion: no-preference) {
  .fz-sku-row { animation: fzFade 300ms ease both; }
}
.fz-sku-art { display: block; font-size: 12px; font-weight: 500; word-break: break-all; }
.fz-sku-sub { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 3px; font-size: 11px; color: var(--ink-muted); font-family: inherit; }
td.r .fz-sku-sub { justify-content: flex-end; }
.fz-sku-sub .fz-dot { width: 7px; height: 7px; }
.fz-sku-flagn { padding: 0 6px; border-radius: var(--r-full); font-size: 10.5px; font-weight: 600; }
.fz-sku-flagn.down { background: var(--critical-soft); color: var(--critical); }
.fz-sku-flagn.info { background: var(--accent-soft); color: var(--accent); }
.fz-kept { display: flex; gap: 2px; width: 120px; height: 8px; border-radius: var(--r-full); overflow: hidden; background: var(--surface-2); margin: 5px 0 2px; }
.fz-kept span { min-width: 2px; transform-origin: left center; }
@media (prefers-reduced-motion: no-preference) {
  .fz-kept span { animation: fzGrow 700ms cubic-bezier(.2,.8,.2,1) both; }
}
.fz-kept-net { background: var(--good); }
.fz-kept-comm { background: var(--ozon); }
.fz-kept-deliv { background: var(--ink-faint); }
.fz-kept-legend { display: flex; flex-wrap: wrap; gap: 8px 18px; padding-top: 10px; }
.fz-kept-legend > span { display: inline-flex; align-items: center; gap: 6px; }

/* Карточка товара */
.fz-sku-detail > td { background: var(--surface-2); padding: 0 !important; text-align: left !important; }
.fz-sku-detail > td::before { display: none !important; }
@media (max-width: 700px) {
  .fz-sku-detail > td { display: block !important; }
}
.fz-sku-table td.mono, .fz-sku-table td.r { white-space: nowrap; }
.fz-scen-name { font-weight: 600; }
.fz-skud { display: grid; grid-template-columns: minmax(240px, 1fr) minmax(0, 2.2fr); gap: 18px 26px; padding: 16px 18px 18px; cursor: default; }
@media (prefers-reduced-motion: no-preference) {
  .fz-skud { animation: fzRise 320ms cubic-bezier(.2,.8,.2,1) both; }
}
.fz-skud-col { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.fz-skud-gap { margin-top: 8px; }
.fz-skud-dl { display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; margin: 0; font-size: 12.5px; }
.fz-skud-dl dt { color: var(--ink-muted); }
.fz-skud-dl dd { margin: 0; font-variant-numeric: tabular-nums; }
.fz-skud-stores { display: flex; flex-direction: column; gap: 4px; font-size: 12px; }
.fz-skud-stores > div { display: flex; justify-content: space-between; gap: 10px; }
.fz-skud-inputs { display: flex; flex-wrap: wrap; gap: 10px 16px; }
.fz-skud-inputs label { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-muted); }
.fz-skud-inputs .btn-input { width: 120px; }
.fz-scen { width: 100%; border-collapse: collapse; font-size: 12.5px; background: var(--surface); border-radius: 8px; overflow: hidden; }
.fz-scen th { padding: 7px 10px; font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-muted); border-bottom: 1px solid var(--rule); text-align: left; white-space: nowrap; }
.fz-scen td { padding: 7px 10px; border-bottom: 1px solid var(--rule); white-space: nowrap; }
.fz-scen tr:last-child td { border-bottom: 0; }
.fz-skud-scen { overflow-x: auto; }
.fz-skud-flags { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 6px; }
.fz-skud-flag { padding: 8px 12px; border-radius: 8px; font-size: 12.5px; border-left: 0; }
.fz-skud-flag.down { background: var(--critical-soft); color: var(--critical); }
.fz-skud-flag.info { background: var(--accent-soft); color: var(--accent-deep); }
@media (max-width: 1100px) {
  .fz-skud { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .fz-wf-row { grid-template-columns: minmax(0, 120px) minmax(0, 1fr) 86px 14px; }
  .fz-wf-row .fz-wf-pct { display: none; }
  .fz-rank-row { grid-template-columns: 20px minmax(0, 1fr) 92px 58px; }
  .fz-rank-row .fz-track { display: none; }
}
@media (max-width: 640px) {
  .fz-hero { padding: 18px; }
  .fz-hero-value { font-size: 30px; }
  .fz-tiles { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; }
  .fz-tile { padding: 12px; }
  .fz-tile-val { font-size: 18px; }
  .fz-chart { height: 230px; }
  .fz-chart-wrap { padding-left: 44px; }
  .fz-ylab { left: -46px; width: 40px; }
  .fz-head-controls { width: 100%; }
  .fz-head-controls .fz-seg { width: 100%; }
  .fz-head-controls .fz-seg button { flex: 1 1 auto; padding: 7px 6px; font-size: 12px; white-space: nowrap; }
  .fz-hero-head { flex-wrap: wrap; }
  .fz-hero-head .fz-chip { margin-top: 0; }
  .fin-stores-picker, .fz-sync-form { flex: 1; }
  .fin-stores-picker .fz-btn, .fz-sync-form .fz-btn { width: 100%; justify-content: center; }
  .fin-stores-pop { right: auto; left: 0; width: 100%; }
  .fz-tip { min-width: 200px; }
  .fz-best { display: none; }
}

/* Шапка: красная точка «что-то кончается». Живёт отдельно от .topbar-stat,
   потому что те прячутся на узких экранах, а тревога прятаться не должна. */
.topbar-alert { position: relative; display: flex; align-items: center; }
.topbar-alert-dot { position: relative; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; }
/* Кольцо крутится, центр вырастает из мелкой точки в крупную и мигает. */
.topbar-alert-dot::before { content: ""; position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 0deg, transparent 0 40%, var(--critical)); -webkit-mask: radial-gradient(closest-side, transparent 72%, #000 76%); mask: radial-gradient(closest-side, transparent 72%, #000 76%); animation: alertSpin 1.8s linear infinite; }
.topbar-alert-dot::after { content: ""; width: 12px; height: 12px; border-radius: 50%; background: var(--critical); box-shadow: 0 0 6px var(--critical); animation: alertGrow 2.4s cubic-bezier(.3, .7, .3, 1) infinite; }
@keyframes alertSpin { to { transform: rotate(360deg); } }
@keyframes alertGrow {
  0% { transform: scale(.3); opacity: .5; }
  45% { transform: scale(1.15); opacity: 1; }
  60% { transform: scale(1); opacity: .65; }
  75% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(.3); opacity: .5; }
}
@media (prefers-reduced-motion: reduce) {
  .topbar-alert-dot::before, .topbar-alert-dot::after { animation: none; }
}
.topbar-alert-pop { position: absolute; z-index: 60; top: 100%; right: -12px; display: none; width: 300px; max-width: calc(100vw - 24px); margin-top: 8px; padding: 10px 6px 6px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-3); font-size: 13px; }
/* Мостик над зазором: курсор идёт от точки к списку, не закрывая его. */
.topbar-alert-pop::before { content: ""; position: absolute; left: 0; right: 0; top: -10px; height: 10px; }
.topbar-alert:hover .topbar-alert-pop, .topbar-alert:focus-within .topbar-alert-pop { display: block; }
.topbar-alert-pop b { display: block; padding: 0 8px 6px; font-weight: 650; }
.topbar-alert-pop a { display: grid; gap: 1px; padding: 6px 8px; border-radius: var(--r-sm); color: var(--ink); text-decoration: none; }
.topbar-alert-pop a:hover { background: var(--overlay-hover); }
.topbar-alert-pop small { color: var(--critical); font-size: 12px; }
/* Иначе display у карточного режима таблицы перебивает hidden. */
.mt-table tr[data-sup][hidden] { display: none; }
/* /equipment: паспорта оборудования (2026-11-02) */
.eq-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(240px, 1.4fr); margin-bottom: 12px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); }
.eq-sum > div { display: grid; gap: 2px; align-content: center; padding: 12px 16px; border-right: 1px solid var(--rule); }
.eq-sum > div:last-child { border-right: 0; }
.eq-sum b { font-family: var(--font-mono); font-size: 21px; font-weight: 600; letter-spacing: -.02em; white-space: nowrap; }
.eq-sum span { font-size: 12px; color: var(--ink-muted); }
.eq-state { display: flex; gap: 2px; height: 8px; margin-bottom: 6px; }
.eq-state i { min-width: 6px; background: var(--rule-strong); }
.eq-state i:first-child { border-radius: 4px 0 0 4px; }
.eq-state i:last-child { border-radius: 0 4px 4px 0; }
.eq-state i.tone-crit, .eq-dot.tone-crit { background: var(--critical); }
.eq-state i.tone-warn, .eq-dot.tone-warn { background: var(--warn); }
.eq-state i.tone-good, .eq-dot.tone-good { background: var(--good); }
.eq-legend { display: flex; flex-wrap: wrap; gap: 2px 12px; }
.eq-legend span { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.eq-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--rule-strong); }
.eq-plan { margin-bottom: 12px; }
.eq-plan-h { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 2px 12px; padding: 10px 16px 0; }
.eq-plan-h b { font-size: 13.5px; font-weight: 650; }
.eq-plan-h span { font-size: 12px; color: var(--ink-muted); }
.eq-plan-cols { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(64px, 1fr); gap: 4px; padding: 10px 16px 12px; overflow-x: auto; }
.eq-q { display: grid; grid-template-rows: 58px auto auto; gap: 4px; min-width: 0; }
.eq-q-units { display: flex; flex-wrap: wrap-reverse; align-content: flex-start; gap: 3px; padding-bottom: 4px; border-bottom: 2px solid var(--rule-strong); }
.eq-q.is-past .eq-q-units { border-bottom-color: var(--critical); }
.eq-q-units button { width: 13px; height: 13px; padding: 0; border: 0; border-radius: 3px; background: var(--ink-faint); cursor: pointer; }
.eq-q-units button.tone-crit { background: var(--critical); }
.eq-q-units button.tone-warn { background: var(--warn); }
.eq-q-units button.tone-good { background: var(--good); }
.eq-q b { font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.eq-q span { font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted); white-space: nowrap; }
.eq-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-bottom: 10px; }
.eq-filter input[type=search] { width: 240px; }
button.mt-chip { border-style: solid; font-family: inherit; cursor: pointer; }
.eq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 10px; }
.eq-card { position: relative; display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 4px 14px; align-items: center; padding: 14px 16px 12px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); transition: border-color .15s, box-shadow .15s; }
.eq-card:hover { border-color: var(--rule-strong); box-shadow: var(--shadow-2); }
.eq-card:focus-within { border-color: var(--accent); }
.eq-card[hidden] { display: none; }
.eq-ring { position: relative; display: grid; place-items: center; width: 72px; height: 72px; }
.eq-ring svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.eq-ring circle { fill: none; stroke-width: 7; }
.eq-ring .bg { stroke: var(--surface-2); }
.eq-ring .bg.none { stroke: var(--rule-strong); stroke-width: 2; stroke-dasharray: 3 4; }
.eq-ring .fg { stroke: var(--ink-muted); stroke-linecap: round; animation: eqRing .9s cubic-bezier(.2, .8, .2, 1) both; }
.eq-ring .fg.tone-crit { stroke: var(--critical); }
.eq-ring .fg.tone-warn { stroke: var(--warn); }
.eq-ring .fg.tone-good { stroke: var(--good); }
@keyframes eqRing { from { stroke-dasharray: 0 170; } }
.eq-ring b { font-family: var(--font-mono); font-size: 15px; font-weight: 600; letter-spacing: -.03em; }
.eq-ring b.none { color: var(--ink-faint); }
.eq-c-main { display: grid; gap: 1px; min-width: 0; }
.eq-c-name { padding: 0; border: 0; background: none; color: var(--ink); font: inherit; font-size: 15px; font-weight: 650; text-align: left; cursor: pointer; overflow-wrap: anywhere; }
.eq-c-name::after { content: ""; position: absolute; inset: 0; border-radius: var(--r-md); }
.eq-c-name:focus-visible { outline: none; }
.eq-c-sub { overflow: hidden; font-size: 12px; color: var(--ink-muted); text-overflow: ellipsis; white-space: nowrap; }
.eq-c-state { margin-top: 3px; font-size: 13px; font-weight: 600; }
.eq-c-state small { font-size: 12px; font-weight: 400; color: var(--ink-muted); }
.eq-facts { display: grid; grid-column: 1 / -1; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 12px; margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--rule); }
.eq-facts div { min-width: 0; }
.eq-facts dt { font-size: 11.5px; color: var(--ink-faint); }
.eq-facts dd { margin: 0; overflow: hidden; font-size: 12.5px; font-weight: 550; text-overflow: ellipsis; white-space: nowrap; }
.eq-facts dd.mono { font-family: var(--font-mono); }
.eq-empty { display: grid; justify-items: start; gap: 8px; padding: 22px 18px; border: 1px dashed var(--rule-strong); border-radius: var(--r-md); }
.eq-pass { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 14px; align-items: center; padding: 16px 56px 12px 16px; }
.eq-pass h2 { margin: 0; font-size: 18px; font-weight: 650; line-height: 1.25; overflow-wrap: anywhere; }
.eq-dkv { padding: 0 16px 14px; font-size: 13px; }
.eq-fold { border-top: 1px solid var(--rule); }
.eq-fold > summary { display: flex; align-items: center; justify-content: space-between; min-height: 42px; padding-inline: 16px; font-size: 13.5px; font-weight: 650; cursor: pointer; list-style: none; }
.eq-fold > summary::-webkit-details-marker { display: none; }
.eq-fold > summary::after { content: "+"; font-size: 18px; font-weight: 400; color: var(--ink-muted); }
.eq-fold[open] > summary::after { content: "−"; }
.eq-tl-h { padding: 12px 16px 6px; border-top: 1px solid var(--rule); font-size: 13.5px; font-weight: 650; }
.eq-tl { display: grid; padding: 0 16px 12px; font-size: 13px; }
.eq-part { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto 28px; gap: 0 10px; align-items: start; padding: 6px 0 8px 18px; }
.eq-part::before { content: ""; position: absolute; left: 3px; top: 12px; bottom: -12px; width: 1px; background: var(--rule-strong); }
.eq-part:last-child::before { display: none; }
.eq-part::after { content: ""; position: absolute; left: 0; top: 10px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.eq-part .mt-q { font-size: 12.5px; }
@media (prefers-reduced-motion: reduce) { .eq-ring .fg, .mt-cells i { animation: none; } }
@media (max-width: 900px) {
  .eq-sum { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .eq-sum > div { padding: 10px 12px; }
  .eq-sum b { font-size: 15px; }
  .eq-sum > div:nth-child(3) { border-right: 0; }
  .eq-sum > div:last-child { grid-column: 1 / -1; border-top: 1px solid var(--rule); }
  .eq-grid { grid-template-columns: minmax(0, 1fr); }
  .eq-filter input[type=search] { flex: 1 1 100%; width: auto; }
}
/* Свёрнутые блоки и срок поставки на /materials */
.mt-fold > summary { cursor: pointer; list-style: none; }
.mt-fold > summary::-webkit-details-marker { display: none; }
.mt-fold > summary::after { content: "Показать"; margin-left: auto; font-size: 13px; color: var(--accent-deep); }
.mt-fold[open] > summary::after { content: "Скрыть"; }
.mt-lead { display: grid; grid-template-columns: minmax(0, 1fr) 80px auto; gap: 6px 8px; align-items: center; }
.mt-lead .hint { grid-column: 1 / -1; }
/* /materials: шапка, вкладки и таблица запасов (2026-11-02) */
.mt-say { color: var(--critical); }
.mt-head { align-items: center; margin-bottom: 12px; }
nav.mt-nav { flex-wrap: nowrap; gap: 22px; margin-bottom: 12px; overflow-x: auto; border-bottom: 1px solid var(--rule); }
nav.mt-nav a.mt-nav-i { flex: none; padding: 9px 0; border-radius: 0; background: none; border-bottom: 2px solid transparent; margin-bottom: -1px; color: var(--ink-muted); font-size: 14px; font-weight: 600; text-decoration: none; }
nav.mt-nav a.mt-nav-i:hover { background: none; color: var(--ink); }
nav.mt-nav a.mt-nav-i.active { border-bottom-color: var(--accent); color: var(--ink); }
.mt-nav-i em { margin-left: 4px; font-size: 12.5px; font-style: normal; font-weight: 600; }
.mt-nav-i em.crit { color: var(--critical); }
.mt-nav-i em.warn { color: var(--warn); }
.mt-r { display: grid; grid-template-columns: minmax(200px, 1.3fr) minmax(330px, 1.7fr) 92px 142px 132px 112px 200px; gap: 0 12px; align-items: center; min-height: 34px; padding-inline: 14px; border-bottom: 1px solid var(--rule); font-size: 13px; cursor: pointer; transition: background-color .12s; }
.mt-r .num { font-family: inherit; text-align: right; white-space: nowrap; }
.mt-r:hover { background: var(--overlay-hover); }
.mt-r.is-hot { background: var(--good-soft); }
.mt-r-h { min-height: 26px; font-size: 11.5px; color: var(--ink-muted); cursor: default; }
.mt-r-h:hover { background: none; }
.mt-r-n { display: flex; align-items: baseline; gap: 8px; min-width: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.mt-r .mt-name { flex: 0 1 auto; min-width: 0; overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.mt-r .mt-sub { flex: 1 9999 0; min-width: 0; margin: 0; overflow: hidden; font-size: 11.5px; text-overflow: ellipsis; white-space: nowrap; }
.mt-r .mt-big { font-family: var(--font-mono); font-size: 13px; font-weight: 600; }
.mt-r .mt-unit { font-size: 11px; color: var(--ink-faint); }
.mt-sect { display: flex; align-items: center; gap: 6px; min-height: 24px; padding-inline: 14px; border-bottom: 1px solid var(--rule); background: var(--surface-2); font-size: 11.5px; font-weight: 650; color: var(--ink-muted); scroll-margin-top: 70px; }
.mt-sect b { font-family: var(--font-mono); font-weight: 600; }
.mt-sect i, .mt-dist a i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); }
.mt-dist { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; padding: 7px 14px; border-bottom: 1px solid var(--rule); font-size: 12px; }
.mt-dist-bar { display: flex; flex: 1 1 200px; gap: 2px; height: 6px; }
.mt-dist-bar i { min-width: 4px; background: var(--ink-faint); opacity: .55; }
.mt-dist-bar i:first-child { border-radius: 3px 0 0 3px; }
.mt-dist-bar i:last-child { border-radius: 0 3px 3px 0; }
.mt-dist a { display: inline-flex; align-items: center; gap: 5px; color: var(--ink-muted); text-decoration: none; white-space: nowrap; }
.mt-dist a:hover { color: var(--ink); }
.mt-dist b { font-family: var(--font-mono); font-weight: 600; color: var(--ink); }
.mt-dist i.tone-crit, .mt-sect i.tone-crit { background: var(--critical); opacity: 1; }
.mt-dist i.tone-warn, .mt-sect i.tone-warn { background: var(--warn); opacity: 1; }
.mt-dist i.tone-good, .mt-sect i.tone-good { background: var(--good); opacity: 1; }
.mt-left { display: grid; grid-template-columns: minmax(70px, 1fr) minmax(0, 196px); gap: 10px; align-items: center; min-width: 0; }
/* Запас ячейками: заполнено = доля от срока «докупить на», риска = срок поставки. */
.mt-run { position: relative; display: block; min-width: 0; }
.mt-cells { display: block; height: 10px; overflow: hidden; background: var(--rule); -webkit-mask: repeating-linear-gradient(90deg, #000 0 5px, transparent 5px 7px); mask: repeating-linear-gradient(90deg, #000 0 5px, transparent 5px 7px); }
.mt-cells i { display: block; height: 100%; background: var(--ink-muted); transform-origin: left; animation: mtGrow .55s cubic-bezier(.2, .8, .2, 1) both; }
.mt-cells i.tone-crit { background: linear-gradient(90deg, color-mix(in srgb, var(--critical) 62%, transparent), var(--critical)); }
.mt-cells i.tone-warn { background: linear-gradient(90deg, color-mix(in srgb, var(--warn) 62%, transparent), var(--warn)); }
.mt-cells i.tone-good { background: linear-gradient(90deg, color-mix(in srgb, var(--good) 62%, transparent), var(--good)); }
.mt-run u { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); text-decoration: none; }
.mt-r .mt-runlabel { overflow: hidden; font-size: 12px; font-weight: 500; color: var(--ink-muted); text-overflow: ellipsis; white-space: nowrap; }
.mt-r .mt-runlabel.text-crit { color: var(--critical); font-weight: 600; }
.mt-r .mt-runlabel.text-warn { color: var(--warn); font-weight: 600; }
.mt-r .mt-rate-d { display: flex; align-items: center; justify-content: flex-end; gap: 8px; font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted); }
.mt-spark { display: inline-flex; align-items: flex-end; gap: 1px; height: 14px; }
.mt-spark i { display: block; width: 3px; min-height: 1px; border-radius: 1px 1px 0 0; background: var(--accent); opacity: .7; }
.mt-r .mt-buy { font-family: var(--font-mono); font-size: 13px; }
.mt-buy b { font-weight: 650; }
.mt-buy-l, .mt-rate-l { display: none; font-family: inherit; font-size: 12px; color: var(--ink-muted); }
.mt-acts { display: flex; justify-content: flex-end; gap: 0; }
.mt-acts .btn-line.small { min-height: 24px; padding: 2px 6px; border-color: transparent; background: none; box-shadow: none; color: var(--ink-muted); font-size: 12px; font-weight: 500; }
.mt-acts .btn-line.small.accent { color: var(--accent-deep); font-weight: 600; }
.mt-acts .btn-line.small:hover, .mt-acts .btn-line.small:focus-visible { border-color: var(--rule-strong); background: var(--surface); color: var(--ink); }
@media (max-width: 1320px) {
  .mt-r { grid-template-columns: minmax(170px, 1.2fr) minmax(260px, 1.5fr) 88px 124px 108px 200px; }
  .mt-left { grid-template-columns: minmax(70px, 1fr) minmax(0, 150px); }
  .mt-rate-d, .mt-r-h > :nth-child(4) { display: none; }
  .mt-r .mt-rate-d { display: none; }
}
@media (max-width: 1020px) {
  .mt-r { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; padding-block: 7px; cursor: default; }
  .mt-r-h { display: none; }
  .mt-left, .mt-acts { grid-column: 1 / -1; }
  .mt-left { grid-row: 2; grid-template-columns: minmax(70px, 1fr) minmax(0, auto); }
  .mt-r .mt-rate-d { display: flex; justify-content: flex-start; }
  .mt-buy-l, .mt-rate-l { display: inline; }
  .mt-spark { display: none; }
  .mt-acts .btn-line.small { flex: 1; min-height: 38px; border-color: var(--rule); }
}

/* ---- «Материалы» → схема склада в объёме (v0.9.283) -------------------------
   Чистый CSS 3D. ⚠ На .m3-floor/.m3-zone/.m3-rise (preserve-3d) нельзя ставить
   opacity < 1: браузер сплющит сцену. Приглушение — цветом граней. */
.m3 { --m3-top: var(--surface); --m3-side: var(--surface-2); --m3-side2: var(--rule-strong); --m3-ink: var(--ink);
      --m3-floor: var(--surface); --m3-grid: var(--rule); --m3-wall: var(--ink); --m3-hot: #e07a1f; --m3-hot-soft: rgba(224, 122, 31, 0.18); }
.m3-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 10px 16px; border-bottom: 1px solid var(--rule); }
.m3-grow { flex: 1; }
.m3-seg { display: inline-flex; background: var(--surface-2); border-radius: var(--r-sm); padding: 3px; gap: 2px; }
.m3-seg button { font: inherit; border: 0; background: transparent; border-radius: var(--r-xs); padding: 0 12px; height: 34px; font-size: 13px; font-weight: 600; color: var(--ink-muted); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: background .15s, color .15s; }
.m3-seg button.on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(18, 22, 32, .12); }
.m3-seg button b { font-size: 12px; padding: 1px 7px; border-radius: var(--r-full); background: var(--surface-2); }
.m3.is-pick .m3-seg button[data-mode="pick"] b { background: var(--m3-hot); color: #fff; }
.m3-ib { font: inherit; width: 38px; height: 38px; border-radius: var(--r-sm); border: 1px solid var(--rule-strong); background: var(--surface); color: var(--ink); display: grid; place-items: center; cursor: pointer; }
.m3-ib:hover { background: var(--surface-2); }
.m3-note { margin: 10px 16px 0; padding: 10px 12px; border-radius: var(--r-sm); background: var(--warn-soft); color: var(--ink); font-size: 13px; line-height: 1.45; }
.m3-note[hidden] { display: none; }
.m3-scene { position: relative; height: clamp(420px, 62vh, 640px); perspective: 3600px; perspective-origin: 50% 25%; overflow: hidden; cursor: grab; touch-action: none; user-select: none;
            background: radial-gradient(ellipse at 50% 35%, var(--surface) 0%, var(--bg) 75%); }
.m3-scene.is-drag { cursor: grabbing; }
.m3-world { position: absolute; inset: 0; transform-style: preserve-3d; }
.m3-floor { position: absolute; left: 50%; top: 54%; transform-style: preserve-3d; transition: transform .8s cubic-bezier(.2, .75, .2, 1);
            background-color: var(--m3-floor); background-image: linear-gradient(var(--m3-grid) 1px, transparent 1px), linear-gradient(90deg, var(--m3-grid) 1px, transparent 1px);
            box-shadow: 0 0 0 3px var(--m3-wall), 0 40px 60px -30px rgba(18, 22, 32, .35); }
.m3-scene.is-drag .m3-floor { transition: none; }
.m3-zone { position: absolute; transform-style: preserve-3d; transition: transform .5s cubic-bezier(.3, 1.4, .5, 1); cursor: pointer; outline: none; }
.m3-rise { position: absolute; inset: 0; transform-style: preserve-3d; animation: m3Rise .8s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes m3Rise { from { transform: scale3d(1, 1, .01); } to { transform: scale3d(1, 1, 1); } }
.m3-face { position: absolute; box-sizing: border-box; background-color: var(--m3-side); transition: background-color .35s, border-color .35s; }
.m3-f-top { inset: 0; background-color: var(--m3-top); border: 1px solid var(--m3-side2); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.m3-f-s { left: 0; top: 100%; width: 100%; transform-origin: top; transform: rotateX(90deg); }
.m3-f-n { left: 0; bottom: 100%; width: 100%; transform-origin: bottom; transform: rotateX(-90deg); }
.m3-f-e { left: 100%; top: 0; height: 100%; transform-origin: left; transform: rotateY(-90deg); background-color: var(--m3-side2); }
.m3-f-w { right: 100%; top: 0; height: 100%; transform-origin: right; transform: rotateY(90deg); background-color: var(--m3-side2); }
.m3-top-name { font-size: 11px; font-weight: 700; color: var(--m3-ink); text-align: center; line-height: 1.15; padding: 2px 4px; overflow: hidden; }
.k-shelf .m3-f-s, .k-shelf .m3-f-n, .k-shelf .m3-f-e, .k-shelf .m3-f-w { background-image: repeating-linear-gradient(to bottom, transparent 0 10px, rgba(18, 22, 32, .2) 10px 12px); }
.k-work { --m3-top: #fbf3e6; --m3-side: #e9d7b9; --m3-side2: #d8c29c; --m3-ink: #5a4320; }
.k-pallet { --m3-top: #efe3cf; --m3-side: #cdb58d; --m3-side2: #b99e72; --m3-ink: #4d3a1c; }
.m3-zone.tone-crit { --m3-top: #f6d3cf; --m3-side: #de8f87; --m3-side2: #c9736a; --m3-ink: #7d1f19; }
.m3-zone.tone-warn { --m3-top: #f6e6c8; --m3-side: #dcb36e; --m3-side2: #c99c50; --m3-ink: #6b4708; }
.m3-zone.tone-good .m3-f-top { border-top: 3px solid var(--good); }
.m3-zone.is-dim { --m3-top: var(--surface-2); --m3-side: var(--rule); --m3-side2: var(--rule-strong); --m3-ink: var(--ink-faint); }
.m3-zone.is-hot { --m3-top: #fde7d2; --m3-side: #f0b98a; --m3-side2: #e3a26d; --m3-ink: #7a3c06; }
.m3-zone.is-sel { --m3-top: var(--accent); --m3-side: var(--accent-deep); --m3-side2: #173f92; --m3-ink: #fff; }
.m3-zone:focus-visible .m3-f-top { outline: 3px solid var(--accent); outline-offset: 2px; }
.m3-cells { position: absolute; inset: 0; }
.m3-cell { position: absolute; box-sizing: border-box; border: 1px solid rgba(18, 22, 32, .18); display: grid; place-items: center; }
.m3-cell.is-occ { background: rgba(39, 99, 216, .28); }
.m3-cell.is-free { background: rgba(255, 255, 255, .25); }
.m3-cell.is-took { background: rgba(26, 138, 90, .7); }
.m3-cell.is-pend { background: var(--m3-hot); border-color: #fff; animation: m3Glow 1.4s ease-in-out infinite; }
.m3-cell.is-focus { background: #3b1411; border: 1px solid #1d0907; animation: none; }
/* Выдвинутый ящик. Наружу по нормали грани — это translateZ с минусом у всех четырёх граней. */
.m3-face.has-drawer, .m3-face.has-drawer .m3-cells, .m3-cell.is-focus { transform-style: preserve-3d; }
.m3-face.has-drawer .m3-cells { transform: translateZ(-.5px); }
.m3-drawer { position: absolute; inset: 0; transform-style: preserve-3d; pointer-events: none; transform: translateZ(calc(-1 * var(--d))); animation: m3Pull .7s cubic-bezier(.25, 1.45, .45, 1) both; }
@keyframes m3Pull { from { transform: translateZ(0); } to { transform: translateZ(calc(-1 * var(--d))); } }
.m3-dr-front { position: absolute; inset: 0; box-sizing: border-box; background: #d9473c; border: 1px solid rgba(255, 255, 255, .7); display: grid; place-items: center; animation: m3DrGlow 1.2s ease-in-out .7s infinite; }
.m3-dr-front b { font-size: 9px; font-weight: 800; color: #fff; }
.m3-dr-handle { position: absolute; left: 30%; right: 30%; top: 18%; height: 2px; border-radius: 2px; background: rgba(255, 255, 255, .85); }
@keyframes m3DrGlow { 0%, 100% { box-shadow: 0 0 0 1px rgba(194, 59, 50, .8); } 50% { box-shadow: 0 0 16px 5px rgba(217, 71, 60, .8); } }
.m3-dr-s { position: absolute; box-sizing: border-box; border: 1px solid rgba(0, 0, 0, .25); }
.m3-dr-t { left: 0; top: 0; width: 100%; height: var(--d); transform-origin: top; transform: rotateX(90deg); background: #e8776c; }
.m3-dr-b { left: 0; bottom: 0; width: 100%; height: var(--d); transform-origin: bottom; transform: rotateX(-90deg); background: #9e2a22; }
.m3-dr-l { left: 0; top: 0; width: var(--d); height: 100%; transform-origin: left; transform: rotateY(-90deg); background: #b8342b; }
.m3-dr-r { right: 0; top: 0; width: var(--d); height: 100%; transform-origin: right; transform: rotateY(90deg); background: #b8342b; }
.has-focus .m3-cell.is-pend:not(.is-focus) { animation: none; background: rgba(39, 99, 216, .28); border-color: rgba(18, 22, 32, .18); }
.has-focus .m3-zone.is-sel .m3-cell.is-pend:not(.is-focus) { background: rgba(255, 255, 255, .42); }
.has-focus .m3-cell.is-pend:not(.is-focus) b { display: none; }
.m3-cell.is-focus > b { display: none; }
.m3-zone.is-sel .m3-cell.is-occ { background: rgba(255, 255, 255, .42); }
.m3-zone.is-sel .m3-cell.is-free { background: rgba(255, 255, 255, .12); }
.m3-cell b { font-size: 10px; font-weight: 800; color: #fff; }
/* Грань стеллажа повёрнута в 3D, и текст на ней лёг бы на бок или вверх ногами:
   у каждой грани своя обратная развёртка, чтобы цифры стояли ровно для зрителя. */
.m3-cell b, .m3-dr-front b { display: block; white-space: nowrap; line-height: 1; }
.m3-cell.f-s b { transform: scaleY(-1); }
.m3-cell.f-n b { transform: scaleX(-1); }
.m3-cell.f-e b { transform: matrix(0, -1, -1, 0, 0, 0); }
.m3-cell.f-w b { transform: matrix(0, 1, 1, 0, 0, 0); }
@keyframes m3Glow { 0%, 100% { box-shadow: 0 0 0 0 rgba(224, 122, 31, .0), inset 0 0 0 0 rgba(255, 255, 255, .0); } 50% { box-shadow: 0 0 10px 3px rgba(224, 122, 31, .75), inset 0 0 0 2px rgba(255, 255, 255, .6); } }
.m3-ring { position: absolute; border: 2px solid var(--accent); border-radius: 10px; pointer-events: none; animation: m3Ring 1.8s ease-out infinite; }
.m3-ring.hot { border-color: var(--m3-hot); }
@keyframes m3Ring { 0% { transform: scale(1); opacity: .9; } 100% { transform: scale(1.5); opacity: 0; } }
.m3-pinwrap { position: absolute; left: 50%; top: 50%; width: 0; height: 0; transform-style: preserve-3d; pointer-events: none; }
.m3-pin { position: absolute; left: 0; bottom: 0; display: flex; flex-direction: column; align-items: center; transform: translateX(-50%); animation: m3Bob 1.8s ease-in-out infinite; }
.m3-pin-card { background: var(--ink); color: var(--surface); border-radius: var(--r-sm); padding: 7px 11px; white-space: nowrap; font-size: 12.5px; font-weight: 600; box-shadow: 0 10px 24px -8px rgba(18, 22, 32, .5); animation: m3PinIn .45s cubic-bezier(.3, 1.5, .5, 1) both; }
.m3-pin.hot .m3-pin-card { background: var(--m3-hot); color: #fff; }
.m3-pin.focus .m3-pin-card { background: #c23b32; color: #fff; max-width: 280px; overflow: hidden; text-overflow: ellipsis; }
.m3-pin.focus .m3-pin-stem { background: linear-gradient(#c23b32, transparent); }
.m3-pin-stem { width: 2px; height: 28px; background: linear-gradient(var(--ink), transparent); }
.m3-pin.hot .m3-pin-stem { background: linear-gradient(var(--m3-hot), transparent); }
@keyframes m3Bob { 0%, 100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, -5px); } }
@keyframes m3PinIn { from { transform: scale(.4); opacity: 0; } to { transform: none; opacity: 1; } }
.m3-tip { position: absolute; left: 12px; top: 12px; z-index: 2; width: 270px; max-width: calc(100% - 24px); box-sizing: border-box; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md); padding: 10px 12px;
          box-shadow: 0 10px 30px -14px rgba(18, 22, 32, .35); display: flex; flex-direction: column; gap: 5px; font-size: 13px; pointer-events: none; animation: m3TipIn .2s ease-out; }
.m3-tip[hidden] { display: none; }
@keyframes m3TipIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.m3-tip-sub, .m3-tip .muted { color: var(--ink-muted); }
.m3-tip-row { display: flex; align-items: center; gap: 8px; }
.m3-tip-row.hot { color: #b45309; font-weight: 600; }
.m3-tip-row.good { color: var(--good); font-weight: 600; }
.m3-tip-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m3-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--ink-faint); }
.m3-dot.tone-crit { background: var(--critical); } .m3-dot.tone-warn { background: var(--warn); } .m3-dot.tone-good { background: var(--good); }
.m3-legend { position: absolute; left: 12px; bottom: 10px; display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--ink-muted); background: var(--surface); border-radius: var(--r-sm); padding: 6px 10px; pointer-events: none; }
.m3-leg { display: inline-flex; align-items: center; gap: 6px; }
.m3-leg-i { width: 12px; height: 12px; border-radius: 3px; border: 1px solid rgba(18, 22, 32, .15); }
.m3-leg-i.is-pend { background: var(--m3-hot); } .m3-leg-i.is-took { background: rgba(26, 138, 90, .7); } .m3-leg-i.is-occ { background: rgba(39, 99, 216, .28); } .m3-leg-i.is-free { background: var(--surface-2); }
.m3-leg-i.tone-crit { background: #de8f87; } .m3-leg-i.tone-warn { background: #dcb36e; } .m3-leg-i.tone-good { background: var(--good); }
.m3-hint { position: absolute; right: 12px; bottom: 10px; font-size: 12px; color: var(--ink-faint); pointer-events: none; }
.m3-picks { border-top: 1px solid var(--rule); padding: 12px 16px 14px; }
.m3-picks[hidden] { display: none; }
.m3-picks-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 12px; font-size: 14px; margin-bottom: 10px; }
.m3-picks-head .muted { color: var(--ink-muted); font-size: 13px; }
.m3-picks-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; }
.m3-pick { font: inherit; text-align: left; border: 1px solid var(--rule-strong); background: var(--surface); color: var(--ink); border-radius: var(--r-sm); padding: 9px 12px; display: flex; flex-direction: column; gap: 2px; cursor: pointer; transition: border-color .15s, background .15s; animation: m3TipIn .3s ease-out both; }
.m3-pick:hover { background: var(--surface-2); }
.m3-pick.on { border-color: #c23b32; box-shadow: inset 3px 0 0 #c23b32; }
.m3-pick-addr { font-size: 15px; font-weight: 700; }
.m3-pick-art { font-size: 12px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m3-pick-n { font-size: 12.5px; color: #b45309; font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
  .m3-floor, .m3-zone { transition: none; }
  .m3-rise, .m3-ring, .m3-pin, .m3-pin-card, .m3-cell.is-pend, .m3-cell.is-focus, .m3-tip, .m3-pick, .m3-drawer, .m3-dr-front { animation: none; }
}
@media (max-width: 700px) {
  .m3-scene { height: 420px; }
  .m3-hint { display: none; }
}
/* Тёмная тема схемы: тёплые и тревожные грани приглушены, иначе «стол» и
   «паллеты» светятся пятном на тёмном полу. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .k-work:not(.is-sel):not(.is-dim) { --m3-top: #3a3121; --m3-side: #2c2519; --m3-side2: #241e14; --m3-ink: #e9d7b9; }
  :root:not([data-theme="light"]) .k-pallet:not(.is-sel):not(.is-dim) { --m3-top: #3b3020; --m3-side: #2d2418; --m3-side2: #251d13; --m3-ink: #e3d0ae; }
  :root:not([data-theme="light"]) .m3-zone.tone-crit { --m3-top: #4a1f1c; --m3-side: #6b2a25; --m3-side2: #57221e; --m3-ink: #f6c9c4; }
  :root:not([data-theme="light"]) .m3-zone.tone-warn { --m3-top: #45351a; --m3-side: #634b20; --m3-side2: #503c19; --m3-ink: #f3dca8; }
  :root:not([data-theme="light"]) .m3-zone.is-hot { --m3-top: #4b2e14; --m3-side: #6e4019; --m3-side2: #5a3414; --m3-ink: #fde0c2; }
}
:root[data-theme="dark"] .k-work:not(.is-sel):not(.is-dim) { --m3-top: #3a3121; --m3-side: #2c2519; --m3-side2: #241e14; --m3-ink: #e9d7b9; }
:root[data-theme="dark"] .k-pallet:not(.is-sel):not(.is-dim) { --m3-top: #3b3020; --m3-side: #2d2418; --m3-side2: #251d13; --m3-ink: #e3d0ae; }
:root[data-theme="dark"] .m3-zone.tone-crit { --m3-top: #4a1f1c; --m3-side: #6b2a25; --m3-side2: #57221e; --m3-ink: #f6c9c4; }
:root[data-theme="dark"] .m3-zone.tone-warn { --m3-top: #45351a; --m3-side: #634b20; --m3-side2: #503c19; --m3-ink: #f3dca8; }
:root[data-theme="dark"] .m3-zone.is-hot { --m3-top: #4b2e14; --m3-side: #6e4019; --m3-side2: #5a3414; --m3-ink: #fde0c2; }

/* ---- «Материалы»: цена закупки (v0.9.284) ---------------------------------- */
.mt-money { margin-bottom: 12px; }
.mt-money .stat-delta-note a { color: var(--accent); font-weight: 600; }
.mt-r .mt-price, .mt-r .mt-buy { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; line-height: 1.25; }
.mt-r .mt-price b { font-family: var(--font-mono); font-size: 13px; font-weight: 600; }
.mt-r .mt-price small, .mt-r .mt-buy small { font-size: 11px; color: var(--ink-faint); font-family: var(--font-mono); }
.mt-r .mt-price small.mt-landed { color: var(--ink-muted); }
.mt-price-f { display: flex; gap: 4px; align-items: center; }
.mt-price-f .btn-input { width: 82px; height: 26px; padding: 0 6px; font-size: 12px; text-align: right; }
.mt-price-f .btn-line.small { min-height: 26px; padding: 2px 8px; }
.mt-chip.on.warn { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
.mt-pricebox { display: grid; gap: 4px; padding: 12px; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface-2); }
.mt-pricebox-h { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.mt-pricebox-h .mono { font-weight: 700; }
.mt-pricebox-r { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0; border-top: 1px dashed var(--rule-strong); font-size: 12.5px; color: var(--ink-muted); }
.mt-pricebox-r b { color: var(--ink); }
.mt-pricebox .mt-lead { margin-top: 4px; }
@media (max-width: 1020px) {
  .mt-r .mt-price { align-items: flex-start; }
}

/* ---- «Оборудование»: аппараты, запчасти, тонер; «Поставщики» (v0.9.284) ---- */
.mt-nav-i em { font-style: normal; font-size: 11px; font-weight: 700; margin-left: 4px; padding: 1px 6px; border-radius: var(--r-full); background: var(--surface-2); color: var(--ink-muted); }
.mt-nav-i em.crit { background: var(--critical-soft); color: var(--critical); }
.eqx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; }
.eqx-card { display: flex; flex-direction: column; gap: 12px; padding: 16px 18px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-1); transition: box-shadow .2s, border-color .2s; }
.eqx-card:hover { box-shadow: var(--shadow-2); }
.eqx-card.is-hot { border-color: var(--good); }
.eqx-card-h { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.eqx-name { padding: 0; border: 0; background: none; color: var(--ink); font: inherit; font-size: 17px; font-weight: 700; text-align: left; cursor: pointer; letter-spacing: -.01em; }
.eqx-name:hover { color: var(--accent); }
.eqx-name.sm { font-size: 13px; font-weight: 650; }
.eqx-h2 { margin: 0; font-size: 19px; }
.eqx-sub { font-size: 12.5px; color: var(--ink-muted); margin-top: 2px; }
.eqx-kv { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.eqx-kv > * { display: grid; gap: 2px; padding: 8px 10px; border-radius: var(--r-sm); background: var(--surface-2); color: inherit; text-decoration: none; transition: background .15s; }
.eqx-kv a:hover { background: var(--accent-soft); }
.eqx-kv span { font-size: 11px; color: var(--ink-muted); }
.eqx-kv b { font-family: var(--font-mono); font-size: 15px; }
.eqx-label { margin: 2px 0 4px; font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint); }
.eqx-line { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; gap: 10px; align-items: baseline; padding: 6px 0; border-top: 1px solid var(--rule); font-size: 12.5px; }
.eqx-line.wide { grid-template-columns: 84px minmax(0, 1fr) auto auto; }
.eqx-line .mono { color: var(--ink-muted); }
.eqx-line small { display: block; color: var(--ink-faint); font-size: 11px; }
.eqx-acts { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.eqx-dp { display: grid; gap: 12px; padding: 14px 16px; }
.eqx-tag { display: inline-block; margin: 1px 4px 1px 0; padding: 1px 7px; border: 1px solid var(--rule); border-radius: var(--r-xs); background: var(--surface-2); font-size: 11.5px; color: var(--ink-muted); white-space: nowrap; }
.eqx-row-acts { display: inline-flex; gap: 4px; justify-content: flex-end; flex-wrap: wrap; }
.eqx-table tbody tr.is-hot td, .sup-table tbody tr.is-hot td { animation: eqxFlash 2.2s ease-out; }
.eqx-table tr[hidden], .sup-table tr[hidden] { display: none !important; }
.eqx-color { display: inline-flex; align-items: center; gap: 10px; }
.eqx-tube { position: relative; display: inline-block; width: 12px; height: 28px; flex: none; overflow: hidden; border-radius: 4px 4px 3px 3px; background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--rule-strong); }
.eqx-tube i { position: absolute; left: 0; right: 0; bottom: 0; display: block; height: 100%; transform-origin: bottom; }
.eqx-tube.lg { width: 30px; height: 64px; }
.eqx-tube.sm { width: 9px; height: 20px; }
.eqx-fits { display: flex; flex-wrap: wrap; gap: 6px; }
.eqx-fit { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--rule-strong); border-radius: var(--r-full); font-size: 12.5px; cursor: pointer; }
.eqx-fit:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.eqx-cond { border: 0; padding: 0; margin: 0; }
.eqx-cond legend { padding: 0; margin-bottom: 4px; font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.eqx-seg { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface-2); }
.eqx-seg label { cursor: pointer; }
.eqx-seg input { position: absolute; opacity: 0; pointer-events: none; }
.eqx-seg span { display: inline-block; padding: 6px 14px; border-radius: 6px; font-size: 12.5px; font-weight: 650; color: var(--ink-muted); transition: background .15s, color .15s; }
.eqx-seg input:checked + span { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-1); }
.eqx-seg input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 1px; }
.eqx-mileage[hidden] { display: none; }
.eqx-pour-view { display: flex; align-items: flex-end; gap: 14px; width: 100%; min-height: 76px; padding: 4px 0 8px; }
.eqx-drops { position: relative; flex: 1; height: 40px; margin-bottom: 14px; border-bottom: 2px dashed var(--rule-strong); }
.eqx-drops i { position: absolute; top: 0; left: 30%; width: 4px; height: 6px; border-radius: 2px; opacity: 0; }
.eqx-drops i:nth-child(2) { left: 50%; animation-delay: .3s !important; }
.eqx-drops i:nth-child(3) { left: 70%; animation-delay: .6s !important; }
.eqx-smalls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 4px; max-width: 200px; }
.sup-items { max-width: 420px; white-space: normal; }
@keyframes eqxFlash { from { background: var(--good-soft); } to { background: transparent; } }
@keyframes eqxRow { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
@keyframes eqxPop { 0% { transform: scale(.3); opacity: 0; } 70% { transform: scale(1.1); } 100% { transform: none; opacity: 1; } }
@keyframes eqxFall { 0% { transform: translateY(-6px); opacity: 0; } 30% { opacity: 1; } 100% { transform: translateY(30px); opacity: 0; } }
@keyframes eqxFill { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@media (prefers-reduced-motion: no-preference) {
  .eqx-card { animation: eqxRow .32s cubic-bezier(.22,.85,.3,1) both; animation-delay: calc(var(--i, 0) * 40ms); }
  .eqx-table tbody tr, .sup-table tbody tr { animation: eqxRow .28s cubic-bezier(.22,.85,.3,1) both; animation-delay: calc(min(var(--i, 0), 10) * 25ms); }
  .eqx-tube i { animation: eqxFill .7s cubic-bezier(.2,.8,.2,1) both; }
  .eqx-smalls .eqx-tube { animation: eqxPop .4s cubic-bezier(.3,1.4,.5,1) both; }
  .eqx-drops i { animation: eqxFall .9s ease-in infinite; }
}
@media (max-width: 700px) {
  .eqx-grid { grid-template-columns: minmax(0, 1fr); }
  .eqx-kv { grid-template-columns: minmax(0, 1fr); }
}
/* Карта склада отдельной страницей /map: сцена выше. */
.m3-scene { height: clamp(460px, 68vh, 760px); }
/* Наезд камеры на место (v0.9.284): пол увеличивается в --k раз, таблички —
   нет, иначе подпись выбранного места закрывала бы полсцены. */
.m3-pin-card { font-size: calc(12.5px / var(--k, 1)); padding: calc(7px / var(--k, 1)) calc(11px / var(--k, 1)); border-radius: calc(8px / var(--k, 1)); transition: font-size .8s cubic-bezier(.2, .75, .2, 1), padding .8s cubic-bezier(.2, .75, .2, 1); }
.m3-pin-stem { height: calc(28px / var(--k, 1)); width: calc(2px / var(--k, 1)); }
.eqx-kv a { color: inherit; }

/* === Шапка по варианту А (2026-11-04) ====================================
   Плашка статуса, «Все разделы» с группами и меню под именем. Старые
   .topbar-stat/.line-pip/.clock в шапке больше не рисуются. */
.status-pill {
  --pill-tone: var(--good);
  display: inline-flex; align-items: center; height: 32px; padding: 0 4px 0 11px;
  border: 1px solid var(--rule); border-radius: var(--r-full); background: var(--surface);
  font-size: 12px; color: var(--ink-muted); white-space: nowrap; flex: none;
}
.status-pill.err { --pill-tone: var(--critical); border-color: color-mix(in srgb, var(--critical) 45%, var(--rule)); }
.status-pill b { color: var(--ink); font-family: var(--font-mono); font-weight: 700; font-variant-numeric: tabular-nums; }
.status-pill-line { display: inline-flex; align-items: center; gap: 7px; color: var(--ink); font-weight: 600; }
.status-pill.err .status-pill-line { color: var(--critical); }
.status-pill-line .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--pill-tone); flex: none; }
.status-pill-part { display: inline-flex; align-items: center; }
.status-pill-part::before { content: ""; width: 1px; height: 14px; background: var(--rule); margin: 0 10px; }
.status-pill-clock {
  margin-left: 10px; padding: 3px 9px; border-radius: var(--r-full); background: var(--surface-2);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink-muted);
}
.status-pill-clock:empty { display: none; }
@keyframes pillPulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pill-tone) 45%, transparent); }
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: no-preference) {
  .status-pill-line .dot { animation: pillPulse 2.4s ease-out infinite; }
}

/* Котик в плашке статуса (2026-11-10, v0.9.313), по мотивам RunCat.
   ok — бежит; .err — лежит, уши прижаты, хвост на полу, капает слеза.
   Позы задаются трансформациями групп одной SVG: transform-box: fill-box
   нужен, чтобы transform-origin считался от самой детали, а не от холста. */
.runcat { flex: none; overflow: visible; margin: -2px 0; color: var(--ink); }
.status-pill.err .runcat { color: var(--critical); }
.runcat * { transform-box: fill-box; }
.runcat .rc-body, .runcat .rc-head > circle, .runcat .rc-ear { fill: currentColor; }
.runcat .rc-leg, .runcat .rc-tail { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
.runcat .rc-tail { stroke-width: 2; transform-origin: 100% 100%; }
.runcat .rc-far { opacity: .55; }
.runcat .rc-leg { transform-origin: 50% 0; }
.runcat .rc-head { transform-origin: 0% 100%; }
.runcat .rc-eye-ok, .runcat .rc-eye-sad path { fill: none; stroke: var(--surface); stroke-width: .9; stroke-linecap: round; stroke-linejoin: round; }
.runcat .rc-eye-sad circle { fill: var(--surface); }
.runcat .rc-eye-sad, .runcat .rc-tear { display: none; }
.runcat .rc-tear { fill: #4ea3e8; }
.runcat .rc-ground { stroke: var(--rule); stroke-width: 1.2; stroke-linecap: round; stroke-dasharray: 4 3; }

/* Грустный: лёг, голова опущена, лапы спрятаны под туловище. */
.status-pill.err .rc-ground { display: none; }
.status-pill.err .rc-torso, .status-pill.err .rc-tail { transform: translateY(3.2px); }
.status-pill.err .rc-tail { d: path("M9 11 C6 12.5 3.5 13 1.5 12.2"); }
.status-pill.err .rc-leg { transform: scaleY(.3); }
.status-pill.err .rc-head { transform: translate(-.5px, 3.4px) rotate(14deg); }
.status-pill.err .rc-ear-b { transform: rotate(-28deg); transform-origin: 100% 100%; }
.status-pill.err .rc-ear-f { transform: rotate(28deg); transform-origin: 0% 100%; }
.status-pill.err .rc-eye-ok { display: none; }
.status-pill.err .rc-eye-sad { display: inline; }
.status-pill.err .rc-tear { display: inline; }

@keyframes rcLeg { 0%, 100% { transform: rotate(38deg); } 50% { transform: rotate(-38deg); } }
@keyframes rcBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-1.3px); } }
@keyframes rcTail { 0%, 100% { transform: rotate(-12deg); } 50% { transform: rotate(14deg); } }
@keyframes rcNod { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(-5deg); } }
@keyframes rcRoad { to { stroke-dashoffset: 14; } }
@keyframes rcBreath { 0%, 100% { transform: translateY(3.2px) scaleY(1); } 50% { transform: translateY(3.2px) scaleY(1.07); } }
@keyframes rcSigh { 0%, 100% { transform: translate(-.5px, 3.4px) rotate(14deg); } 50% { transform: translate(-.5px, 3.9px) rotate(18deg); } }
@keyframes rcTwitch { 0%, 80%, 100% { transform: translateY(3.2px) rotate(0); } 88% { transform: translateY(3.2px) rotate(-9deg); } }
@keyframes rcTear { 0% { transform: translateY(0); opacity: 0; } 15% { opacity: 1; } 75% { opacity: 1; } 100% { transform: translateY(7px); opacity: 0; } }

@media (prefers-reduced-motion: no-preference) {
  .status-pill:not(.err) .rc-l1, .status-pill:not(.err) .rc-l4 { animation: rcLeg .44s linear infinite; }
  .status-pill:not(.err) .rc-l2, .status-pill:not(.err) .rc-l3 { animation: rcLeg .44s linear infinite -.22s; }
  .status-pill:not(.err) .rc-cat { animation: rcBob .22s ease-in-out infinite; }
  .status-pill:not(.err) .rc-tail { animation: rcTail .44s ease-in-out infinite; }
  .status-pill:not(.err) .rc-head { animation: rcNod .44s ease-in-out infinite; }
  .status-pill:not(.err) .rc-ground { animation: rcRoad .44s linear infinite; }
  .status-pill.err .rc-torso { animation: rcBreath 3.2s ease-in-out infinite; transform-origin: 50% 100%; }
  .status-pill.err .rc-head { animation: rcSigh 3.2s ease-in-out infinite; }
  .status-pill.err .rc-tail { animation: rcTwitch 4s ease-in-out infinite; transform-origin: 100% 50%; }
  .status-pill.err .rc-tear { animation: rcTear 2.4s ease-in infinite; }
}
@media (max-width: 1560px) { .status-pill-opt { display: none; } }
@media (max-width: 1400px) { .status-pill-word, .status-pill-clock { display: none; } }

.topbar-inner { position: relative; }
.admin-menu:has(.nav-mega) { position: static; }
.admin-dropdown.nav-mega { left: 0; right: 0; margin: 0 auto; top: calc(100% - 2px); }
.admin-toggle .admin-toggle-burger { width: 14px; height: 14px; transform: none !important; }
.nav-mega {
  width: min(calc(var(--mega-cols, 4) * 220px + 32px), calc(100vw - 24px)); min-width: 0; padding: 14px;
  display: grid; grid-template-columns: repeat(var(--mega-cols, 4), minmax(0, 1fr)); gap: 6px 16px;
  box-shadow: var(--shadow-3); transform: translateY(-6px) scale(.985); transform-origin: top right;
  transition: opacity 160ms ease, transform 200ms cubic-bezier(.2, .8, .2, 1);
}
.nav-mega.open { transform: none; }
.nav-mega-group { display: grid; align-content: start; gap: 1px; min-width: 0; }
.nav-mega-title { padding: 2px 8px 6px; font-size: 12px; font-weight: 650; color: var(--ink-faint); }
.admin-dropdown.nav-mega a {
  display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 1px 10px; align-items: start;
  padding: 7px 8px; font-size: 13px;
}
.nav-mega a svg { width: 18px; height: 18px; color: var(--ink-muted); grid-row: span 2; margin-top: 1px; }
.nav-mega a b { font-weight: 650; color: var(--ink); }
.nav-mega a span { font-size: 11.5px; line-height: 1.3; color: var(--ink-muted); }
.nav-mega a.active b, .nav-mega a.active svg { color: var(--accent); }
.nav-mega a.active { background: var(--accent-soft); }
.nav-mega-foot {
  grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  margin-top: 6px; padding: 10px 8px 0; border-top: 1px solid var(--rule); font-size: 12px; color: var(--ink-faint);
}
@media (prefers-reduced-motion: no-preference) {
  .nav-mega.open .nav-mega-group { animation: rowIn 260ms cubic-bezier(.22, .85, .3, 1) both; }
  .nav-mega.open .nav-mega-group:nth-child(2) { animation-delay: 30ms; }
  .nav-mega.open .nav-mega-group:nth-child(3) { animation-delay: 60ms; }
  .nav-mega.open .nav-mega-group:nth-child(4) { animation-delay: 90ms; }
}
@media (max-width: 900px) { .nav-mega { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
@media (max-width: 520px) { .nav-mega { grid-template-columns: minmax(0, 1fr); max-height: 70vh; overflow-y: auto; } }

.user-menu { position: relative; flex: none; }
.user-toggle {
  height: 32px; display: inline-flex; align-items: center; gap: 7px; padding: 0 9px 0 4px;
  border: 1px solid var(--rule); border-radius: var(--r-full); background: var(--surface-2);
  color: var(--ink); font: inherit; font-size: 12.5px; font-weight: 650; cursor: pointer;
}
.user-toggle:hover { border-color: var(--rule-strong); }
.user-toggle i {
  width: 24px; height: 24px; border-radius: 50%; background: #16191f; color: #fff; font-style: normal;
  font-size: 10px; font-weight: 800; display: inline-flex; align-items: center; justify-content: center;
}
.user-toggle svg { width: 12px; height: 12px; color: var(--ink-muted); transition: transform 150ms ease; }
.user-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.user-name { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-dropdown {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 40; width: 230px; padding: 6px;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-3);
  opacity: 0; transform: translateY(-6px); pointer-events: none;
  transition: opacity 140ms ease, transform 180ms cubic-bezier(.2, .8, .2, 1);
}
.user-dropdown.open { opacity: 1; transform: none; pointer-events: auto; }
.user-who { padding: 8px 10px 10px; margin-bottom: 4px; border-bottom: 1px solid var(--rule); font-size: 12px; color: var(--ink-muted); }
.user-who b { display: block; font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.user-dropdown a, .user-dropdown button {
  display: block; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--r-sm); background: none;
  color: var(--ink); font: inherit; font-size: 13.5px; text-align: left; text-decoration: none; cursor: pointer;
}
.user-dropdown a:hover, .user-dropdown button:hover { background: var(--overlay-hover); }
.user-dropdown .user-logout { color: var(--critical); }
.user-dropdown form { margin: 0; }
@media (max-width: 1100px) { .user-name { display: none; } }
@media (max-width: 640px) {
  .account-menu { flex-wrap: wrap; gap: 8px; }
}

/* === Дашборд (2026-11-04) ================================================
   Сводка одной полосой, поток товара строкой, внизу магазины и лента. */
.dq-sum { display: grid; grid-template-columns: 1.7fr 1fr 1fr 1fr; margin-bottom: 14px; }
.dq-cell { position: relative; display: grid; align-content: start; gap: 3px; min-width: 0; padding: 13px 18px 14px; border-right: 1px solid var(--rule); }
.dq-cell:last-child { border-right: 0; }
.dq-label { font-size: 12px; font-weight: 650; color: var(--ink-muted); }
.dq-label small { font-weight: 500; color: var(--ink-faint); }
.dq-big { font-family: var(--font-mono); font-size: 26px; font-weight: 750; letter-spacing: -.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.dq-big small { font-size: 13px; font-weight: 500; color: var(--ink-faint); letter-spacing: 0; }
.dq-sub { font-size: 12px; color: var(--ink-muted); line-height: 1.35; }
.dq-sub .crit { color: var(--critical); }
.dq-sub .good { color: var(--good); }
.dq-today-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.dq-days { display: flex; align-items: flex-end; gap: 4px; height: 30px; }
.dq-days i { width: 14px; height: var(--h); min-height: 2px; border-radius: 3px 3px 1px 1px; background: var(--rule-strong); transform-origin: bottom; }
.dq-days i.is-today { background: var(--accent); }
.dq-mp { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
.dq-c-wb { color: var(--wb); }
.dq-c-ozon { color: var(--ozon); }
.dq-c-yandex { color: color-mix(in srgb, var(--yandex) 70%, var(--ink)); }
.dq-grid .panel-head { min-height: 50px; }
.dq-mp > span, .dq-stage-mp > span { font-family: var(--font-mono); font-weight: 650; font-size: 11.5px; }
.dq-link { margin-left: auto; font-weight: 650; color: var(--accent); text-decoration: none; }
.dq-link:hover { text-decoration: underline; }
.dq-state { display: inline-flex; align-items: center; gap: 8px; font-size: 18px; font-weight: 750; line-height: 1.55; }
.dq-state i { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-faint); flex: none; }
.dq-state.tone-good i { background: var(--good); }
.dq-state.tone-warn i { background: var(--warn); }
.dq-state.tone-crit { color: var(--critical); }
.dq-state.tone-crit i { background: var(--critical); }
.dq-poll { position: absolute; top: 10px; right: 12px; margin: 0; }
.dq-poll .btn { padding: 4px 10px; font-size: 12px; }

.dq-flow { margin-bottom: 14px; }
.dq-flow .panel-head .eyebrow, .dq-stores .panel-head .eyebrow, .dq-orders .panel-head .eyebrow { text-transform: none; letter-spacing: 0; font-family: inherit; font-size: 13px; font-weight: 700; color: var(--ink); }
.dq-flow-row { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.dq-stage { display: grid; gap: 4px; align-content: start; padding: 11px 16px 13px; border-right: 1px solid var(--rule); color: inherit; text-decoration: none; transition: background 150ms ease; }
.dq-stage:last-child { border-right: 0; }
.dq-stage:hover { background: var(--surface-2); }
.dq-stage.is-empty { opacity: .5; }
.dq-stage-name { font-size: 12px; font-weight: 650; color: var(--ink-muted); }
.dq-stage b { font-family: var(--font-mono); font-size: 20px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.dq-stage-bar { height: 4px; border-radius: 2px; background: var(--surface-2); overflow: hidden; }
.dq-stage-bar i { display: block; height: 100%; min-width: 3px; border-radius: 2px; background: var(--accent); transform-origin: left; }
.dq-stage.tone-warn .dq-stage-bar i { background: var(--warn); }
.dq-stage.tone-good .dq-stage-bar i { background: var(--good); }
.dq-stage.tone-crit .dq-stage-bar i { background: var(--critical); }
.dq-stage-mp { display: flex; flex-wrap: wrap; gap: 2px 9px; color: var(--ink-faint); }

.dq-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; align-items: start; }
.dq-list { height: 520px; max-height: none; overflow-y: auto; }
.dq-stores .store-row[hidden] { display: none; }
.dq-seg { display: inline-flex; padding: 2px; border-radius: var(--r-full); background: var(--surface-2); border: 1px solid var(--rule); }
.dq-seg button { border: 0; background: none; font: inherit; font-size: 12px; font-weight: 650; color: var(--ink-muted); padding: 4px 10px; border-radius: var(--r-full); cursor: pointer; transition: background 150ms ease, color 150ms ease; }
.dq-seg button span { font-family: var(--font-mono); font-weight: 500; color: var(--ink-faint); margin-left: 2px; }
.dq-seg button.active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-1); }
.dq-feed-day { position: sticky; top: 0; z-index: 1; padding: 6px 16px; background: var(--surface-2); border-bottom: 1px solid var(--rule); font-size: 11.5px; font-weight: 650; color: var(--ink-muted); }
.dq-feed-row { display: grid; grid-template-columns: 44px 18px minmax(0, 1fr); align-items: center; gap: 10px; min-height: 44px; padding: 4px 16px; border-bottom: 1px solid var(--rule); }
.dq-feed-time { font-size: 12.5px; color: var(--ink-muted); }
.dq-feed-mp { width: 18px; height: 18px; object-fit: contain; }
.dq-feed-main { display: grid; min-width: 0; }
.dq-feed-art, .dq-feed-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dq-feed-art { font-size: 12.5px; font-weight: 600; }
.dq-feed-meta { font-size: 11.5px; color: var(--ink-muted); }

@keyframes dqGrowX { from { transform: scaleX(0); } }
@keyframes dqGrowY { from { transform: scaleY(0); } }
@media (prefers-reduced-motion: no-preference) {
  .dq-stage-bar i { animation: dqGrowX .9s cubic-bezier(.2, .8, .2, 1) both; }
  .dq-days i { animation: dqGrowY .7s cubic-bezier(.2, .8, .2, 1) both; }
  .dq-days i:nth-child(2) { animation-delay: 80ms; }
  .dq-days i:nth-child(3) { animation-delay: 160ms; }
  .dq-feed-row { animation: rowIn 280ms cubic-bezier(.22, .85, .3, 1) both; }
}
@media (max-width: 1100px) {
  .dq-sum { grid-template-columns: 1fr 1fr; }
  .dq-cell:nth-child(2) { border-right: 0; }
  .dq-cell:nth-child(-n+2) { border-bottom: 1px solid var(--rule); }
  .dq-flow-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dq-stage { border-bottom: 1px solid var(--rule); }
  .dq-grid { grid-template-columns: minmax(0, 1fr); }
  .dq-list { height: auto; max-height: 480px; }
}
@media (max-width: 560px) {
  .dq-sum { grid-template-columns: minmax(0, 1fr); }
  .dq-cell { border-right: 0; border-bottom: 1px solid var(--rule); }
  .dq-cell:last-child { border-bottom: 0; }
  .dq-flow-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dq-stores .panel-head { flex-wrap: wrap; gap: 8px; }
}

/* Отделы склада на плоской схеме (2026-11-04): контур здания из помещений.
   Вне отделов — штриховка «не наше», внутри — сетка. */
.mt-room.has-depts { background-color: var(--surface-2); background-image: repeating-linear-gradient(45deg, transparent 0 9px, var(--rule) 9px 10px); border-style: dashed; border-color: var(--rule-strong); }
.mt-dept { position: absolute; box-sizing: border-box; border: 2px solid var(--ink-muted); background-color: var(--surface);
  background-image: linear-gradient(var(--rule) 1px, transparent 1px), linear-gradient(90deg, var(--rule) 1px, transparent 1px);
  background-size: calc(100% / var(--cx)) calc(100% / var(--cy)); }
.mt-dept-name { position: absolute; left: 6px; bottom: 4px; font-size: 11.5px; font-weight: 650; color: var(--ink-faint); pointer-events: none; white-space: nowrap; }
.mt-room.is-edit .mt-dept { cursor: move; }
.mt-dept.is-sel { box-shadow: 0 0 0 3px var(--accent-soft); border-color: var(--accent); }
.mt-dept.is-sel .mt-dept-name { color: var(--accent); }
.mt-dept.is-drag { z-index: 1; }
.mt-dept .mt-grip { background: var(--ink-muted); }
.mt-zone { z-index: 1; }

/* Карта склада (2026-11-04): камера ведётся из JS кадр за кадром, CSS-переход
   на полу больше не нужен — он и давал рывок. Отделы — плиты с низкими
   стенами; вне отделов пола нет. */
.m3-floor, .m3-scene.is-drag .m3-floor { transition: none; }
.m3-zone { transition: transform .55s cubic-bezier(.3, 1.3, .5, 1); }
.m3-floor.has-depts { background: none; box-shadow: none; border-color: transparent; }
.m3-dept { position: absolute; box-sizing: border-box; transform-style: preserve-3d; transform: translateZ(1px);
  background-color: var(--m3-floor); border: 1px solid var(--rule-strong);
  background-image: linear-gradient(var(--m3-grid) 1px, transparent 1px), linear-gradient(90deg, var(--m3-grid) 1px, transparent 1px);
  transition: background-color .35s, border-color .35s; cursor: zoom-in; }
.m3-dept.is-sel { border-color: var(--accent); background-color: color-mix(in srgb, var(--accent) 7%, var(--m3-floor)); cursor: zoom-out; }
.m3-wall { position: absolute; box-sizing: border-box; pointer-events: none;
  background: color-mix(in srgb, var(--ink-muted) 22%, transparent); border: 1px solid color-mix(in srgb, var(--ink-muted) 45%, transparent); }
.m3-wall-s { transform-origin: top; transform: rotateX(90deg); }
.m3-wall-n { transform-origin: bottom; transform: rotateX(-90deg); }
.m3-wall-e { transform-origin: left; transform: rotateY(-90deg); }
.m3-wall-w { transform-origin: right; transform: rotateY(90deg); }
.m3-dept.is-sel .m3-wall { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.m3-dept-name { position: absolute; left: 8px; bottom: 6px; font-size: calc(13px / var(--k, 1)); font-weight: 700; color: var(--ink-faint); white-space: nowrap; pointer-events: none; }
.m3-dept.is-sel .m3-dept-name { color: var(--accent); }
@keyframes m3WallIn { from { opacity: 0; } }
@media (prefers-reduced-motion: no-preference) {
  .m3-dept:not(.no-anim) .m3-wall, .m3-dept:not(.no-anim) .m3-dept-name { animation: m3WallIn .8s ease both; animation-delay: var(--d, 0ms); }
}
/* Задняя половина наклонённого пола уходит «за» плоскость .m3-world, и клик
   попадал в неё, а не в отдел. Мир прозрачен для мыши, пол и места — нет. */
.m3-world { pointer-events: none; }
.m3-floor, .m3-dept, .m3-zone { pointer-events: auto; }
.m3-wall, .m3-ring, .m3-pinwrap { pointer-events: none; }
/* Формы «+ Место» / «+ Отдел»: выпадающие списки не растягиваются на всю
   строку (глобальный select.btn-input шириной 100%). */
.mt-filter select.btn-input { width: auto; max-width: 280px; }

/* Дашборд, вторая итерация (2026-11-04): три дня с числами и прежние
   карточки этапов — с анимацией. */
.dq-today-row { align-items: flex-end; }
.dq-trend.stat-trend-bars { flex: 1 1 auto; max-width: 300px; height: 44px; margin: 18px 0 16px; }
.dq-trend .stat-trend-fill { animation-duration: 800ms; }
.dq-trend .stat-trend-bar { cursor: default; }
.dq-trend .stat-trend-bar:hover .stat-trend-fill { filter: brightness(1.08); }
.dq-board .stage-board { margin-bottom: 14px; }
.dq-board .stage-row.route .stage { padding: 10px 8px 12px; transition: background-color 160ms ease, transform 200ms cubic-bezier(.2, .8, .2, 1); border-radius: var(--r-md); }
.dq-board .stage-row.route a.stage:hover { transform: translateY(-2px); }
.dq-board .stage-row.route a.stage:hover .stage-dot { transform: scale(1.14); box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 12%, transparent); }
.dq-board .stage-flow-seg { transform-origin: left; }
@keyframes dqPop { 0% { transform: scale(.3); opacity: 0; } 70% { transform: scale(1.12); opacity: 1; } 100% { transform: scale(1); } }
@keyframes dqUp { from { opacity: 0; transform: translateY(6px); } }
@keyframes dqRing { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 40%, transparent); } 70%, 100% { box-shadow: 0 0 0 9px transparent; } }
@media (prefers-reduced-motion: no-preference) {
  .dq-board .stage-flow-seg { animation: dqGrowX .9s cubic-bezier(.2, .8, .2, 1) both; }
  .dq-board .stage-flow-seg:nth-child(2) { animation-delay: 80ms; }
  .dq-board .stage-flow-seg:nth-child(3) { animation-delay: 160ms; }
  .dq-board .stage-flow-seg:nth-child(4) { animation-delay: 240ms; }
  .dq-board .stage-flow-seg:nth-child(5) { animation-delay: 320ms; }
  .dq-board .stage-dot { animation: dqPop .55s cubic-bezier(.3, 1.4, .5, 1) both; }
  .dq-board .stage-label, .dq-board .stage-total, .dq-board .stage-split { animation: dqUp .45s cubic-bezier(.2, .8, .2, 1) both; }
  .dq-board .stage:nth-child(2) > * { animation-delay: 90ms; }
  .dq-board .stage:nth-child(3) > * { animation-delay: 180ms; }
  .dq-board .stage:nth-child(4) > * { animation-delay: 270ms; }
  .dq-board .stage:nth-child(5) > * { animation-delay: 360ms; }
  /* «Ждёт отгрузки» — то, что ещё на нас: точка мягко пульсирует. */
  .dq-board .stage:not(.stage-empty) .stage-dot.tone-warn { animation: dqPop .55s cubic-bezier(.3, 1.4, .5, 1) both, dqRing 2.4s ease-out 1s infinite; }
}

nav a.nav-map { display: inline-flex; align-items: center; gap: 6px; }
nav a.nav-map svg { width: 14px; height: 14px; flex: none; }

/* === Карта склада: экран целиком (2026-11-04) =========================
   Карта слева, инспектор справа. Префикс wm- (warehouse map). */
.wm { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 14px; align-items: start; }
.wm-stage { overflow: visible; min-width: 0; }
.wm-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 10px 12px; border-bottom: 1px solid var(--rule); }
.wm-bar .btn-line { display: inline-flex; align-items: center; gap: 6px; }
.wm-bar-title { font-weight: 700; font-size: 14px; }
.wm-bar-hint { font-size: 12.5px; color: var(--ink-muted); }
.wm-tools { display: inline-flex; align-items: center; padding: 2px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); }
.wm-tools button { width: 32px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border: 0; background: none; border-radius: var(--r-sm); color: var(--ink-muted); cursor: pointer; transition: background 140ms, color 140ms; }
.wm-tools button:hover { background: var(--surface-2); color: var(--ink); }
.wm-tools button:active { transform: scale(.94); }
.wm-tools svg { width: 16px; height: 16px; }
.wm-tools i { width: 1px; height: 18px; background: var(--rule); margin: 0 2px; }
.wm .m3-scene { border-radius: 0 0 var(--r-md) var(--r-md); }
.wm .mt-room-wrap { padding: 14px; }
.wm-pop { position: relative; }
.wm-pop > summary { list-style: none; cursor: pointer; }
.wm-pop > summary::-webkit-details-marker { display: none; }
.wm-pop[open] > summary { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.wm-pop-body { position: absolute; right: 0; top: calc(100% + 8px); z-index: 30; width: 320px; max-width: calc(100vw - 32px); display: grid; gap: 10px; padding: 14px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md); box-shadow: var(--shadow-3); animation: dqUp .2s cubic-bezier(.2, .8, .2, 1) both; }
.wm-pop-body .btn-input { width: 100%; box-sizing: border-box; }

.wm-insp { position: sticky; top: 62px; max-height: calc(100vh - 76px); overflow-y: auto; }
.wm-card { animation: dqUp .25s cubic-bezier(.2, .8, .2, 1) both; }
.wm-card[hidden] { display: none; }
.wm-head { display: grid; gap: 2px; padding: 14px 16px 12px; }
.wm-head b { font-size: 17px; font-weight: 750; line-height: 1.25; }
.wm-head > span:not(.wm-kicker) { font-size: 12.5px; color: var(--ink-muted); }
.wm-kicker { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 650; color: var(--ink-faint); }
.wm-kicker svg { width: 15px; height: 15px; }
.wm-back { margin: 10px 12px 0; padding: 4px 8px; border: 0; border-radius: var(--r-sm); background: none; color: var(--accent); font: inherit; font-size: 12.5px; font-weight: 650; cursor: pointer; }
.wm-back:hover { background: var(--accent-soft); }
.wm-alerts { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 16px 10px; }
.wm-tag { flex: none; font-size: 11px; font-weight: 650; padding: 2px 8px; border-radius: var(--r-full); white-space: nowrap; }
.wm-tag.crit { background: var(--critical-soft); color: var(--critical); }
.wm-tag.warn { background: var(--warn-soft); color: var(--warn); }
.wm-search { padding: 0 12px 8px; }
.wm-search .btn-input { width: 100%; box-sizing: border-box; }
.wm-list { padding: 0 6px 8px; }
.wm-group[hidden], .wm-row[hidden] { display: none; }
.wm-dept { width: 100%; display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-top: 6px; padding: 7px 10px 5px; border: 0; background: none; border-radius: var(--r-sm);
  font: inherit; font-size: 12px; font-weight: 700; color: var(--ink-muted); text-align: left; cursor: pointer; }
.wm-dept small { font-weight: 500; color: var(--ink-faint); font-family: var(--font-mono); }
.wm-dept:hover { background: var(--surface-2); color: var(--ink); }
.wm-dept.is-static { cursor: default; }
.wm-dept.is-static:hover { background: none; color: var(--ink-muted); }
.wm-row { width: 100%; display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 7px 10px; border: 0; border-left: 3px solid transparent;
  border-radius: var(--r-sm); background: none; font: inherit; text-align: left; color: var(--ink); cursor: pointer; transition: background 140ms, transform 160ms; }
.wm-row:hover { background: var(--surface-2); transform: translateX(2px); }
.wm-row.tone-crit { border-left-color: var(--critical); }
.wm-row.tone-warn { border-left-color: var(--warn); }
.wm-row.tone-good { border-left-color: var(--good); }
.wm-kind { width: 20px; height: 20px; color: var(--ink-muted); }
.wm-row-main { display: grid; min-width: 0; }
.wm-row-main b { font-size: 13px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wm-row-main small { font-size: 11.5px; color: var(--ink-muted); }
.wm-empty { margin: 4px 10px 8px; font-size: 12.5px; color: var(--ink-faint); }
.wm-unplaced { border-top: 1px solid var(--rule); padding: 10px 16px; font-size: 13px; }
.wm-unplaced summary { cursor: pointer; font-weight: 650; color: var(--ink-muted); }
.wm-unplaced summary b { font-family: var(--font-mono); color: var(--warn); margin-left: 4px; }
.wm-unplaced > div { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.wm-roomsize { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 12px 16px; border-top: 1px solid var(--rule); font-size: 12.5px; color: var(--ink-muted); }
.wm-roomsize .btn-input { width: 64px; }
.wm-rackln { display: grid; gap: 1px; margin: 0 16px 10px; padding: 9px 12px; border: 1px solid var(--rule); border-radius: var(--r-md); text-decoration: none; color: var(--accent); font-size: 13px; font-weight: 650; }
.wm-rackln small { color: var(--ink-muted); font-weight: 500; }
.wm-rackln:hover { background: var(--accent-soft); }
.wm-mats { display: grid; padding: 0 8px 6px; }
.wm-mat { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; grid-template-rows: auto auto; column-gap: 10px; align-items: center; padding: 8px; border-radius: var(--r-sm); color: var(--ink); text-decoration: none; }
.wm-mat:hover { background: var(--surface-2); }
.wm-mat i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); grid-row: span 2; }
.wm-mat.tone-crit i { background: var(--critical); }
.wm-mat.tone-warn i { background: var(--warn); }
.wm-mat.tone-good i { background: var(--good); }
.wm-mat span { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wm-mat b { font-size: 13px; text-align: right; }
.wm-mat b small { font-weight: 500; color: var(--ink-muted); }
.wm-mat em { grid-column: 2 / 4; font-style: normal; font-size: 11.5px; color: var(--ink-muted); }
.wm-mat.tone-crit em { color: var(--critical); }
.wm-put { margin: 4px 16px 12px; }
.wm-edit { border-top: 1px solid var(--rule); }
.wm-edit > summary { padding: 11px 16px; cursor: pointer; font-size: 13px; font-weight: 650; color: var(--accent); list-style: none; }
.wm-edit > summary::-webkit-details-marker { display: none; }
.wm-edit > summary::before { content: "›"; display: inline-block; margin-right: 8px; transition: transform 160ms; }
.wm-edit[open] > summary::before { transform: rotate(90deg); }
.wm-edit .mt-zone-edit { border-top: 0; padding-top: 2px; background: none; }
.wm-field { display: grid; gap: 4px; font-size: 12px; font-weight: 650; color: var(--ink-muted); }
.wm-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.wm-kindopt { position: relative; display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid var(--rule); border-radius: var(--r-md); font-size: 12.5px; font-weight: 600; cursor: pointer; background: var(--surface); transition: border-color 140ms, background 140ms; }
.wm-kindopt input { position: absolute; opacity: 0; pointer-events: none; }
.wm-kindopt svg { width: 18px; height: 18px; color: var(--ink-muted); flex: none; }
.wm-kindopt:hover { border-color: var(--rule-strong); }
.wm-kindopt:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }
.wm-kindopt:has(input:checked) svg { color: var(--accent); }
.wm-kindopt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.wm-rack[hidden] { display: none; }
.wm-size { display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: end; }
.wm-step { display: grid; gap: 4px; font-size: 12px; font-weight: 650; color: var(--ink-muted); }
.wm-step small { display: none; }
.wm-step-ctl { display: grid; grid-template-columns: 30px minmax(0, 1fr) 30px; border: 1px solid var(--rule-strong); border-radius: var(--r-md); overflow: hidden; background: var(--surface); }
.wm-step-ctl button { border: 0; background: var(--surface-2); font: inherit; font-size: 16px; color: var(--ink); cursor: pointer; }
.wm-step-ctl button:hover { background: var(--accent-soft); color: var(--accent); }
.wm-step-ctl input { width: 100%; min-width: 0; border: 0; background: none; text-align: center; font-size: 13px; padding: 7px 0; color: var(--ink); }
.wm-rot { width: 36px; height: 34px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--rule-strong); border-radius: var(--r-md); background: var(--surface); color: var(--ink-muted); cursor: pointer; }
.wm-rot:hover { color: var(--accent); border-color: var(--accent); }
.wm-rot svg { width: 17px; height: 17px; }
@media (max-width: 1100px) {
  .wm { grid-template-columns: minmax(0, 1fr); }
  .wm-insp { position: static; max-height: none; }
}

/* ---- Инвентаризация адресного хранения (v0.9.291) ---- */
.good-text { color: var(--good); }
.crit-text { color: var(--critical); }
.warn-text { color: var(--warn); }
.kbd { display: inline-flex; align-items: center; height: 20px; padding: 0 6px; border: 1px solid var(--rule-strong); border-bottom-width: 2px; border-radius: 5px; background: var(--surface); font: 600 11px var(--font-mono); color: var(--ink-muted); }
.inv-rack-form { display: inline-flex; }
.inv-rack-form .btn-input { max-width: 280px; }
.inv-done .modal-body { color: var(--good); font-weight: 600; }
.inv-start p { margin: 0 0 10px; max-width: 760px; line-height: 1.5; }
.inv-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 16px 28px; padding: 14px 18px; }
.inv-sum-main { display: flex; flex-direction: column; gap: 2px; }
.inv-sum-big { font-size: 22px; font-weight: 650; }
.inv-sum-big .muted { font-size: 14px; font-weight: 500; }
.inv-sum-bar { flex: 1 1 300px; display: flex; flex-direction: column; gap: 8px; }
.inv-bar { display: flex; height: 10px; border-radius: var(--r-full); overflow: hidden; background: var(--surface-2); }
.inv-bar i { display: block; height: 100%; transition: width .3s; }
.inv-bar i.ok { background: var(--good); }
.inv-bar i.diff { background: var(--warn); }
.inv-sum-legend { display: flex; flex-wrap: wrap; gap: 16px; font-size: 12.5px; color: var(--ink-muted); }
.inv-sum-side { display: flex; gap: 22px; }
.inv-sum-side > span { display: flex; flex-direction: column; gap: 2px; }
.inv-sum-side b { font-size: 17px; }
.inv-sum-filter { display: flex; align-items: center; gap: 12px; }
.inv-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; }
.inv-grid { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr) minmax(260px, 340px); gap: 14px; align-items: start; }
@media (max-width: 1180px) { .inv-grid { grid-template-columns: minmax(0, 1fr); } }
.inv-map { display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; }
.inv-map-row { display: flex; align-items: center; gap: 8px; }
.inv-map-row > b { width: 16px; font-size: 11px; color: var(--ink-faint); }
.inv-map-cells { display: flex; flex-wrap: wrap; gap: 2px; }
.inv-map .mc { width: 14px; height: 14px; border-radius: 3px; }
.mc { display: inline-block; width: 18px; height: 18px; padding: 0; border-radius: 4px; border: 1px solid var(--rule-strong); background: var(--surface-2); cursor: pointer; }
.mc.empty { background: repeating-linear-gradient(135deg, var(--surface) 0 3px, var(--surface-2) 3px 6px); }
.mc.ok { background: var(--good); border-color: var(--good); }
.mc.diff { background: var(--warn); border-color: var(--warn); }
.mc.cur { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
.mc.out { opacity: .35; }
.inv-legend { display: flex; flex-wrap: wrap; gap: 10px; padding: 10px 14px 14px; border-top: 1px solid var(--rule); font-size: 11.5px; color: var(--ink-muted); }
.inv-legend span { display: inline-flex; align-items: center; gap: 5px; }
.inv-legend .mc { width: 11px; height: 11px; cursor: default; }
.inv-card { display: flex; flex-direction: column; }
.inv-card-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; padding: 16px 18px 14px; border-bottom: 1px solid var(--rule); }
.inv-addr { font-size: 40px; font-weight: 650; line-height: 1.05; letter-spacing: -.02em; margin: 4px 0; }
.inv-nav { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.inv-nav #inv-scan { width: 190px; }
.inv-lines { display: flex; flex-direction: column; gap: 10px; padding: 16px 18px; }
.inv-line { display: flex; flex-direction: column; gap: 10px; padding: 14px; border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--surface); }
.inv-line.is-ok { border-color: var(--good); background: var(--good-soft); }
.inv-line.is-diff { border-color: var(--warn); background: var(--warn-soft); }
.inv-line-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; font-size: 12.5px; }
.inv-art { font-size: 13px; color: var(--ink-faint); word-break: break-all; }
.inv-art b { color: var(--ink); font-weight: 650; }
.inv-count { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-size: 13px; }
.inv-stepper { display: inline-flex; align-items: stretch; height: 48px; border: 1px solid var(--rule-strong); border-radius: var(--r-md); overflow: hidden; background: var(--surface); }
.inv-stepper button { width: 46px; border: 0; background: var(--surface-2); color: var(--ink); font-size: 20px; cursor: pointer; }
.inv-stepper button:hover { background: var(--accent-soft); color: var(--accent); }
.inv-stepper input { width: 70px; border: 0; text-align: center; font-size: 22px; font-weight: 650; background: var(--surface); color: var(--ink); }
.inv-stepper input:focus { outline: none; background: var(--accent-soft); }
.inv-count .btn-line { height: 48px; }
.inv-res { margin-left: auto; font-weight: 650; }
.inv-quick { display: flex; gap: 8px; }
.inv-add { display: flex; flex-direction: column; gap: 6px; padding: 0 18px 14px; }
.inv-add[hidden] { display: none; }
.inv-card-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 14px 18px; border-top: 1px solid var(--rule); background: var(--surface-2); }
.inv-state { flex: 1 1 200px; display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-muted); }
.inv-state.ok { color: var(--good); }
.inv-state.warn { color: var(--warn); }
.inv-state.bad { color: var(--critical); }
.inv-diff-list { display: flex; flex-direction: column; padding: 4px 14px; max-height: 460px; overflow-y: auto; }
.inv-diff-row { display: grid; grid-template-columns: 78px minmax(0, 1fr) auto; gap: 8px; align-items: center; padding: 9px 2px; border: 0; border-bottom: 1px solid var(--rule); background: transparent; text-align: left; font-size: 12.5px; color: var(--ink); cursor: pointer; }
.inv-diff-row:hover { background: var(--overlay-hover); }
.inv-diff-row > span:nth-child(2) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-diff-row b.mono:first-child { color: var(--accent); }
.inv-empty { padding: 18px 4px; text-align: center; font-size: 13px; }
.inv-diff-foot { display: flex; flex-direction: column; gap: 10px; }
.inv-diff-foot p { margin: 0; }

/* Отмена операции с оборудованием (v0.9.292). */
.eqx-undo { display: inline; margin-left: 10px; }
.eqx-undo .link-btn { font-weight: 600; }
tr.eqx-undone td:not(:last-child) { color: var(--ink-faint); text-decoration: line-through; text-decoration-color: var(--rule-strong); }

/* ==== Стеллаж: новый вид (v0.9.293, по макету «Адресное хранение — стеллаж в
   объёме», без 3D — его владелец отверг). Только оформление: разметка и
   скрипты те же, режимы «Нужно собрать / Лежит / Спрос / Свободно» работают
   своими правилами выше (у них специфичность больше). ==== */
.rack-elevation-wrap {
  margin: 0 14px 4px; padding: 22px 22px 8px; border-radius: var(--r-md);
  background: radial-gradient(120% 90% at 50% 0%, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--rule);
}
.rack-upright {
  width: 12px; border-radius: 4px;
  background:
    repeating-linear-gradient(180deg, transparent 0 10px, rgba(0, 0, 0, 0.45) 10px 15px),
    linear-gradient(90deg, #2a2e35, #4a505a 45%, #1d2026);
  box-shadow: none;
}
.rack-body { padding-left: 26px; padding-right: 4px; }
.rack-shelf-row { gap: 10px; padding: 12px 0 0; }

/* Коробка — открытый лоток: белые стенки, дно чуть темнее, ярлык на переднем
   бортике внизу, как наклейка на настоящей коробке. */
.rack-box {
  padding: 7px 7px 0; gap: 0; border: 1px solid var(--rule-strong); border-bottom: 0;
  border-radius: 10px 10px 0 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--surface-2) 70%, var(--rule)) 0%, var(--surface-2) 38%);
  box-shadow: inset 0 10px 14px -12px rgba(0, 0, 0, 0.28);
}
.rack-box.plain { padding: 0; border: 0; background: none; box-shadow: none; border-radius: 0; }
.rack-box-cells { display: flex; gap: 5px; grid-template-columns: none; padding-bottom: 6px; }
.rack-box-cells > .rack-cell { flex: 1 1 0; }
.rack-box-head {
  order: 2; margin: 0 -8px; padding: 5px 9px 5px; gap: 8px; align-items: center; flex-wrap: nowrap;
  background: var(--surface); border: 1px solid var(--rule-strong); border-radius: 0 0 6px 6px;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04);
}
.rack-box-name { font: 650 11px var(--font-mono); letter-spacing: 0.02em; }
.rack-box-fill { font: 500 10.5px var(--font-mono); }
.rack-box.is-wanted { border-color: var(--warn); }
.rack-box.is-wanted .rack-box-head { border-color: var(--warn); background: var(--warn-soft); }

/* Ячейка — плитка: номер и количество сверху, хвост артикула под ними. */
.rack-cell {
  min-height: 58px; padding: 6px 8px 9px; gap: 2px; justify-content: flex-start;
  border-radius: 7px; border: 1px solid var(--rule-strong); background: var(--surface);
}
.rack-cell-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: 6px; row-gap: 3px; }
.rack-cell-head .rack-cell-addr { grid-column: 1; grid-row: 1; font: 600 10px var(--font-mono); color: var(--ink-faint); }
.rack-cell-head .rack-cell-qty { grid-column: 2; grid-row: 1; margin: 0; font: 650 15px var(--font-mono); letter-spacing: -0.02em; }
.rack-cell-head .rack-cell-article { grid-column: 1 / -1; grid-row: 2; font: 500 10.5px var(--font-mono); color: var(--ink-muted); }
.rack-cell-article .ra-t { color: var(--ink); font-weight: 650; }
.rack-cell.filled { box-shadow: 0 1px 2px rgba(15, 17, 21, 0.06); }
.rack-cell.filled .rack-cell-addr { color: var(--ink-faint); }
.rack-cell.is-box { background: color-mix(in srgb, var(--accent) 5%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 35%, var(--rule-strong)); }
.rack-cell:not(.filled) {
  border-style: dashed; border-color: var(--rule-strong); box-shadow: none;
  background: repeating-linear-gradient(135deg, transparent 0 6px, color-mix(in srgb, var(--rule) 45%, transparent) 6px 12px);
}
.rack-cell-stock { left: 8px; right: 8px; bottom: 5px; height: 3px; }
.rack-cell:hover { transform: translateY(-1px); box-shadow: 0 4px 10px rgba(15, 17, 21, 0.1); }
.rack-cell { transition: background .15s ease, border-color .15s ease, box-shadow .15s ease, transform .15s ease; }
/* Выбранная — залита акцентом, как в макете: её видно с другого конца стеллажа. */
.rack-elevation-wrap .rack-cell.selected {
  outline: none; background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent), 0 8px 18px color-mix(in srgb, var(--accent) 35%, transparent);
  transform: translateY(-2px);
}
.rack-elevation-wrap .rack-cell.selected .rack-cell-addr,
.rack-elevation-wrap .rack-cell.selected .rack-cell-qty,
.rack-elevation-wrap .rack-cell.selected .rack-cell-article,
.rack-elevation-wrap .rack-cell.selected .rack-cell-article .ra-t,
.rack-elevation-wrap .rack-cell.selected .rack-cell-need { color: var(--accent-ink); }
.rack-elevation-wrap .rack-cell.selected .rack-cell-stock { background: color-mix(in srgb, var(--accent-ink) 30%, transparent); }
.rack-elevation-wrap .rack-cell.selected .rack-cell-stock::after { background: var(--accent-ink); }
.rack-elevation-wrap .rack-cell.hit { outline: 2px solid var(--warn); outline-offset: 1px; }

/* Полка — алюминиевая доска с торцом; буква полки — тёмная бирка на стойке. */
.rack-shelf-board {
  height: 10px; border-radius: 2px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--surface) 80%, var(--rule-strong)) 0 3px,
                                      var(--rule-strong) 3px, color-mix(in srgb, var(--rule-strong) 70%, var(--ink-faint)) 100%);
  box-shadow: 0 4px 8px -5px rgba(0, 0, 0, 0.45);
}
.rack-shelf-tag {
  left: -28px; width: 20px; padding: 2px 0; border: 0; border-radius: 5px;
  background: var(--ink); color: var(--surface); font: 650 10.5px/1.3 var(--font-mono);
}
.rack-level.golden .rack-shelf-tag { background: var(--warn); color: #fff; }
.rack-level.golden { background: none; box-shadow: inset 3px 0 0 var(--warn); }
.rack-base::before { height: 4px; background: #2a2e35; opacity: 0.9; }

@media (max-width: 700px) {
  .rack-elevation-wrap { margin: 0 8px 4px; padding: 14px 10px 6px; }
  .rack-box-cells { flex-wrap: wrap; }
  .rack-box-cells > .rack-cell { flex: 1 1 100px; }
}
.rack-box-head { overflow: hidden; }
.rack-box-name { flex: none; }
.rack-box-fill { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.rack-box-need { flex: none; }
/* Узкая плитка (15 ячеек на полке): номер ячейки и начало артикула уходят,
   остаются количество и хвост — то, по чему ячейки различают. */
.rack-box-cells > .rack-cell { container-type: inline-size; }
@container (max-width: 88px) { .rack-cell-article .ra-h { display: none; } }
@container (max-width: 58px) {
  .rack-cell-head { grid-template-columns: minmax(0, 1fr); }
  .rack-cell-head .rack-cell-addr { display: none; }
  .rack-cell-head .rack-cell-qty { grid-column: 1; justify-self: start; }
}
/* Хвост не влезает целиком — режем его СЛЕВА: «…54x16», а не «xf54». */
@container (max-width: 88px) {
  .rack-cell-article .ra-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
}

/* Аппараты: фильтр по типу и сортировка (v0.9.295). */
.eqx-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.eqx-kinds { display: flex; flex-wrap: wrap; gap: 6px; }
.eqx-sort { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-muted); }
.eqx-sort .btn-input { height: 34px; width: auto; min-width: 190px; }
.eqx-group-h { display: flex; align-items: baseline; gap: 8px; margin: 18px 2px 10px; font-size: 13px; font-weight: 650; letter-spacing: .02em; text-transform: uppercase; color: var(--ink-muted); }
.eqx-group-h:first-of-type { margin-top: 4px; }
.eqx-group-h em { font-style: normal; font-family: var(--font-mono); color: var(--ink-faint); }
.eqx-card-chips { display: inline-flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.chip.eqx-kind-printer { background: var(--accent-soft); color: var(--accent); }
.chip.eqx-kind-plotter { background: var(--warn-soft); color: var(--warn); }

/* Выбор оборудования: категория → аппараты (v0.9.296). */
.eqx-pick { display: flex; flex-direction: column; gap: 8px; }
.eqx-pick-tabs { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--rule); align-self: flex-start; }
.eqx-pick-tab { display: inline-flex; align-items: baseline; gap: 4px; height: 30px; padding: 0 12px; border: 0; border-radius: calc(var(--r-sm) - 2px); background: transparent; color: var(--ink-muted); font-size: 12.5px; font-weight: 550; cursor: pointer; }
.eqx-pick-tab span { font-size: 11px; color: var(--ink-faint); }
.eqx-pick-tab b { font-family: var(--font-mono); color: var(--accent); }
.eqx-pick-tab.on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
.eqx-pick-group[hidden] { display: none; }
.eqx-fit-all { border-style: dashed; color: var(--ink-muted); }
/* Чипы аппаратов — не подписи поля: общее правило `.form-field label`
   делало их капслоком. Поле с выбором — на всю ширину формы. */
.form-field .eqx-fit { font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--ink); }
.form-field .eqx-fit:has(input:checked) { color: var(--accent); }
.form-field .eqx-fit-all { color: var(--ink-muted); }
.mt-add-form .form-field:has([data-pick]), .mt-ed-form .form-field:has([data-pick]) { flex: 1 1 100%; }
.eqx-pick { width: 100%; }

/* Шкала остатка, цвет узла, перестановка (v0.9.297). */
.eqx-scale { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 3px; min-width: 96px; }
.eqx-scale-val { white-space: nowrap; font-size: 12.5px; }
.eqx-scale-val em { font-style: normal; font-size: 11px; font-weight: 650; color: var(--critical); margin-left: 4px; }
.eqx-scale-bar { position: relative; display: block; width: 96px; height: 6px; border-radius: var(--r-full); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--rule); overflow: hidden; }
.eqx-scale-bar i { display: block; height: 100%; border-radius: var(--r-full); background: var(--good); transition: width .3s; }
.eqx-scale-bar s { position: absolute; left: 50%; top: -1px; bottom: -1px; width: 2px; background: var(--ink); opacity: .55; text-decoration: none; }
.eqx-scale.tone-warn .eqx-scale-bar i { background: var(--warn); }
.eqx-scale.tone-crit .eqx-scale-bar i { background: var(--critical); }
.eqx-scale.tone-crit .eqx-scale-val b { color: var(--critical); }
.eqx-scale-min { font-size: 10.5px; color: var(--ink-faint); }
.mt-chip.on.warn { border-color: var(--critical); background: var(--critical-soft); color: var(--critical); }
.eqx-slot { display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; padding: 0 7px; height: 18px; border-radius: var(--r-full); background: var(--surface-2); font-size: 11px; color: var(--ink-muted); vertical-align: middle; }
.eqx-slot i, .eqx-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15); }
.eqx-line.is-removed > span:nth-child(2) { color: var(--ink-faint); }
.eqx-moved { display: block; color: var(--warn); }
.eqx-move { margin: -2px 0 8px 70px; }
.eqx-move summary { cursor: pointer; font-size: 12px; color: var(--accent); }
.eqx-move-form { padding: 8px 0 0; }
.eqx-move-to[hidden] { display: none; }
.mt-sub.nowrap { white-space: nowrap; }

/* ==== «Оборудование» по образцу «Материалов» (v0.9.299) ==== */
.mt-r.eqr-m { grid-template-columns: minmax(220px, 1.4fr) 170px minmax(200px, 1fr) minmax(200px, 1.1fr) 230px; }
.mt-r.eqr-s { grid-template-columns: minmax(240px, 1.5fr) minmax(220px, 1.3fr) 150px 100px 150px 260px; }
.mt-r.eqr-o { grid-template-columns: minmax(240px, 1.6fr) 100px 100px 120px 110px 130px; cursor: default; }
.mt-r.eqr-o:hover { background: none; }
.mt-r.eqr-m:not(.mt-r-h), .mt-r.eqr-s:not(.mt-r-h), .mt-r.eqr-o:not(.mt-r-h) {
  animation: rowIn .32s cubic-bezier(.2, .8, .2, 1) both; animation-delay: calc(min(var(--i, 0), 24) * 18ms);
}
.mt-r .mt-r-n .eqx-dot { flex: none; align-self: center; }
.eqr-run .mt-unit, .eqr-sub { display: block; }
.eqr-sub { font-size: 11px; color: var(--ink-faint); }
.eqr-inks { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; min-width: 0; font-size: 12px; }
.eqr-ink { display: inline-flex; align-items: center; gap: 4px; }
.eqr-ink i { width: 10px; height: 10px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18); }
.eqr-ink b { font-size: 12.5px; font-weight: 600; }
.eqr-ink.is-empty b { color: var(--critical); }
.eqr-last { overflow: hidden; font-size: 12.5px; color: var(--ink-muted); text-overflow: ellipsis; white-space: nowrap; }
.eqr-price { font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-muted); }
.mt-r .mt-buy small { color: var(--ink-faint); font-size: 11.5px; }
.eqx-money .stat-val { font-variant-numeric: tabular-nums; }
.eqo { margin-bottom: 14px; }
.eqo .panel-head { display: flex; justify-content: space-between; align-items: baseline; }
.eqo-total { font-weight: 650; }
.eqo-q { width: 78px; height: 30px; padding: 0 8px; text-align: right; }
.eqo-foot { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-top: 1px solid var(--rule); }
.eqo-foot [hidden] { display: none; }
/* «Записать работу» */
.eqw-hint { font-size: 11.5px; color: var(--ink-faint); }
.eqw-list { flex: 1 1 100%; display: flex; flex-direction: column; gap: 6px; }
.eqw-line { display: grid; grid-template-columns: minmax(0, 1fr) 72px 28px; grid-template-areas: "act act x" "item item item" "slot qty qty"; gap: 6px; align-items: center; padding: 8px; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--surface); animation: rowIn .24s cubic-bezier(.2, .8, .2, 1) both; }
.eqw-line .eqw-act { grid-area: act; }
.eqw-line > select:not(.eqw-slot) { grid-area: item; }
.eqw-line .eqw-slot { grid-area: slot; }
.eqw-line .eqw-qty { grid-area: qty; }
.eqw-line .ci-remove { grid-area: x; justify-self: end; }
.eqw-act { font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.eqw-line .btn-input { height: 32px; font-size: 13px; }
.eqw-line .eqw-shelf { grid-area: slot; display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-muted); }
.eqw-line .eqw-slot.is-need { border-color: var(--critical); }
.eqw-empty { margin: 0; padding: 10px 2px; }
.eqw-add { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 6px; }
.eqw-add .btn-line.small { border-style: dashed; }
@media (max-width: 1320px) {
  .mt-r.eqr-m { grid-template-columns: minmax(200px, 1.3fr) 150px minmax(160px, 1fr) 210px; }
  .mt-r.eqr-m .eqr-last, .mt-r.eqr-m.mt-r-h > :nth-child(4) { display: none; }
  .mt-r.eqr-s { grid-template-columns: minmax(200px, 1.3fr) minmax(180px, 1.2fr) 130px 130px 250px; }
  .mt-r.eqr-s .eqr-price, .mt-r.eqr-s.mt-r-h > :nth-child(4) { display: none; }
}
@media (max-width: 1020px) {
  .mt-r.eqr-m, .mt-r.eqr-s, .mt-r.eqr-o { grid-template-columns: minmax(0, 1fr) auto; }
  .mt-r.eqr-m .eqr-inks, .mt-r.eqr-m .mt-acts, .mt-r.eqr-s .mt-acts { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .mt-r.eqr-m, .mt-r.eqr-s, .mt-r.eqr-o, .eqw-line { animation: none; }
}
.eqr-buy { white-space: nowrap; }

/* === «Требуют внимания»: где оборвалось (2026-11-05) =======================
   Цепочка «напечатали → сдали → маркетплейс принял» по ответам маркетплейсов. */
.pb-why { display: inline-flex; flex-direction: column; gap: 1px; min-width: 0; }
.pb-why small { font-size: 11.5px; color: var(--ink-muted); font-weight: 500; }
.pb-diag { margin: 4px 0 10px; padding: 10px 12px; border-radius: var(--r-sm); background: var(--surface-2); border-left: 3px solid var(--rule-strong); }
.pb-diag.warn { border-left-color: var(--warn); }
.pb-diag.crit { border-left-color: var(--critical); }
.pb-diag.good { border-left-color: var(--good); }
.pb-diag p { margin: 8px 0 0; font-size: 13px; line-height: 1.45; }
.pb-chain { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 0; }
.pb-chain li { display: flex; align-items: flex-start; gap: 7px; font-size: 12.5px; padding-right: 26px; position: relative; }
.pb-chain li:not(:last-child)::after { content: ""; position: absolute; right: 8px; top: 8px; width: 10px; height: 1px; background: var(--rule-strong); }
.pb-chain li span { display: flex; flex-direction: column; gap: 1px; }
.pb-chain li small { color: var(--ink-muted); font-size: 11.5px; }
.pb-chain li > i { flex: none; width: 16px; height: 16px; border-radius: 50%; margin-top: 1px; display: grid; place-items: center; background: var(--rule); }
.pb-chain li > i::before { font-style: normal; font-size: 10px; font-weight: 800; color: #fff; line-height: 1; }
.pb-chain li.is-ok > i { background: var(--good); } .pb-chain li.is-ok > i::before { content: "✓"; }
.pb-chain li.is-bad > i { background: var(--critical); } .pb-chain li.is-bad > i::before { content: "✕"; }
.pb-chain li.is-wait > i { background: var(--rule-strong); } .pb-chain li.is-wait > i::before { content: "…"; }

/* === «Требуют внимания» v0.9.303: пять видов, мини-цепочка, история ====== */
.pb-kinds { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (min-width: 1181px) {
  .pb-board { grid-template-columns: minmax(0, 3fr) minmax(240px, .9fr); }
  .pb-h, .pb-s { grid-template-columns: 74px minmax(170px, 1.2fr) minmax(150px, 1fr) 104px minmax(110px, .9fr) 200px 16px; }
}
.pb-kind { padding-inline: 14px; }
.pb-kind.drive.is-on::after { background: var(--accent); }
.pb-kind.crit.is-on::after { background: var(--critical); }
.pb-kind.crit b { color: var(--critical); }
.pb-kind b.mono[data-count="0"] { color: var(--ink-faint); }
.pb-what i.drive { background: var(--accent); } .pb-what i.crit { background: var(--critical); } .pb-what i.warn { background: var(--warn); } .pb-what i.ozon { background: var(--ozon); }
.pb-mini { display: inline-flex; gap: 3px; margin-right: 6px; vertical-align: 1px; }
.pb-mini i { width: 7px; height: 7px; border-radius: 50%; background: var(--rule-strong); }
.pb-mini i.is-ok { background: var(--good); } .pb-mini i.is-bad { background: var(--critical); }
.pb-b { animation: pbIn .35s cubic-bezier(.2, .8, .2, 1) both; animation-delay: calc(var(--i, 0) * 45ms); }
@keyframes pbIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.pb-b[open] .pb-chain li { animation: pbStep .3s ease-out both; }
.pb-b[open] .pb-chain li:nth-child(2) { animation-delay: .08s; } .pb-b[open] .pb-chain li:nth-child(3) { animation-delay: .16s; } .pb-b[open] .pb-chain li:nth-child(4) { animation-delay: .24s; }
.pb-b[open] .pb-chain li.is-bad > i { animation: pbBad 1.6s ease-in-out .4s 2; }
@keyframes pbStep { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }
@keyframes pbBad { 0%, 100% { box-shadow: 0 0 0 0 rgba(194, 59, 50, 0); } 50% { box-shadow: 0 0 0 5px rgba(194, 59, 50, .18); } }
.pb-hist { margin-top: 12px; overflow: visible; }
.pb-hist .panel-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
.pb-hist .empty { padding: 18px 16px; }
.pb-hist-list { border-top: 1px solid var(--rule); }
.pb-hist-r { border-bottom: 1px solid var(--rule); }
.pb-hist-r[hidden] { display: none; }
.pb-hist-r > summary { display: grid; grid-template-columns: 92px minmax(180px, 1fr) minmax(0, 2fr) 170px; gap: 0 12px; align-items: center; padding: 8px 14px; cursor: pointer; list-style: none; font-size: 13px; transition: background-color .12s; }
.pb-hist-r > summary::-webkit-details-marker { display: none; }
.pb-hist-r > summary:hover { background: var(--overlay-hover); }
.pb-hist-when { font-size: 12px; color: var(--ink-muted); }
.pb-hist-what { display: flex; flex-direction: column; min-width: 0; }
.pb-hist-what small { font-size: 11.5px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-hist-path { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; min-width: 0; }
.pb-hist-path > i { font-style: normal; color: var(--ink-faint); font-size: 12px; }
.pb-hist-step { font-size: 12px; padding: 2px 8px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--rule); white-space: nowrap; }
.pb-hist-step.warn { border-color: var(--warn); } .pb-hist-step.drive { border-color: var(--accent); } .pb-hist-step.crit { border-color: var(--critical); color: var(--critical); }
.pb-hist-step.ozon { border-color: var(--ozon); } .pb-hist-step.good { border-color: var(--good); color: var(--good); }
.pb-hist-end { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.pb-hist-end small { font-size: 11.5px; color: var(--ink-muted); }
.pb-hist-ev { list-style: none; margin: 0; padding: 4px 14px 12px 118px; display: flex; flex-direction: column; gap: 5px; font-size: 12.5px; }
.pb-hist-ev li { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; position: relative; }
.pb-hist-ev li::before { content: ""; position: absolute; left: -14px; top: 6px; width: 7px; height: 7px; border-radius: 50%; background: var(--rule-strong); }
.pb-hist-ev li.good::before { background: var(--good); } .pb-hist-ev li.crit::before { background: var(--critical); } .pb-hist-ev li.warn::before { background: var(--warn); } .pb-hist-ev li.drive::before { background: var(--accent); }
.pb-hist-ev li.pb-hist-links::before { display: none; }
.pb-hist-ev .mono { color: var(--ink-muted); font-size: 12px; }
.pb-hist-foot { padding: 8px 14px 12px; margin: 0; }
@media (max-width: 1100px) { .pb-kinds { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 700px) {
  .pb-kinds { grid-template-columns: minmax(0, 1fr); }
  .pb-hist-r > summary { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px; }
  .pb-hist-when { grid-column: 1 / -1; }
  .pb-hist-path { grid-column: 1 / -1; order: 3; }
  .pb-hist-ev { padding-left: 30px; }
}
@media (prefers-reduced-motion: reduce) { .pb-b, .pb-b[open] .pb-chain li, .pb-b[open] .pb-chain li > i { animation: none; } }

/* ── «Требуют внимания» по макету «Цех» (2026-11-05, v0.9.306 плотнее) ──
   Сводка одной полосой: сколько висит → путь заказа с числами обрыва →
   недостача WB. Задача: логотип маркетплейса, действие, одна строка «что
   случилось · МП · магазин · прогон». Префикс .tv- — только эта страница. */
.tv-lead { margin: 4px 0 0; }
.tv-sum { overflow: visible; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0; }
.tv-count { display: flex; gap: 12px; align-items: center; padding: 14px 20px; border-right: 1px solid var(--rule); }
.tv-count > b { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
.tv-count span { display: flex; flex-direction: column; font-size: 14px; font-weight: 600; line-height: 1.3; }
.tv-count small { font-size: 12.5px; font-weight: 500; color: var(--ink-muted); }
.tv-red { color: var(--critical); font-style: normal; font-weight: 650; }
.tv-road { display: flex; flex-wrap: wrap; row-gap: 8px; align-items: center; gap: 6px; padding: 12px 18px; min-width: 0; }
.tv-node { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--ink-muted); white-space: nowrap; padding: 0 2px; }
.tv-node svg { width: 16px; height: 16px; color: var(--ink-faint); }
.tv-node.is-end svg { color: var(--good); }
.tv-road > .tv-node:not(:first-child)::before { content: ""; display: inline-block; width: 14px; height: 1px; background: var(--rule-strong); margin-right: 4px; }
.tv-brk { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px; border-radius: var(--r-full); border: 1px solid var(--rule); background: var(--surface); cursor: pointer; font-family: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink-muted); white-space: nowrap; transition: border-color 120ms ease, background 120ms ease; }
.tv-brk b { font-size: 14px; color: var(--ink); }
.tv-brk:hover { border-color: var(--rule-strong); color: var(--ink); }
.tv-brk.is-hot { border-color: color-mix(in srgb, var(--critical) 45%, var(--rule)); background: var(--critical-soft); color: var(--critical); }
.tv-brk.is-hot b { color: var(--critical); }
.tv-brk.is-zero { color: var(--ink-faint); }
.tv-brk.is-zero b { color: var(--ink-faint); font-weight: 500; }
.tv-brk[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.tv-brk[aria-pressed="true"] b { color: var(--accent); }
.tv-road-sep { width: 1px; height: 22px; background: var(--rule); margin: 0 6px; }
.tv-wb { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; padding: 8px 20px; border-top: 1px solid var(--rule); background: var(--surface-2); font-size: 12.5px; border-radius: 0 0 var(--r-md) var(--r-md); }
.tv-wb-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); flex: none; }
.tv-wb.is-calm .tv-wb-dot { background: var(--good); }
.tv-wb.is-alert { background: var(--critical-soft); }
.tv-wb.is-alert .tv-wb-dot { background: var(--critical); }
.tv-wb-more summary { cursor: pointer; color: var(--ink-muted); }
.tv-wb-more[open] { flex-basis: 100%; }
.tv-wb-more form { margin-top: 6px; }
.tv-wb-status:empty { display: none; }
.tv-wb-status { margin: 0; }

.tv-list { overflow: visible; }
.tv-tools { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 10px 16px; border-bottom: 1px solid var(--rule); }
.tv-tools select.btn-input { width: auto; max-width: 240px; }
.tv-search { display: flex; gap: 8px; align-items: center; height: 32px; padding: 0 10px; border: 1px solid var(--rule-strong); border-radius: var(--r-sm); background: var(--surface); width: 260px; color: var(--ink-faint); }
.tv-search input { border: 0; outline: 0; background: transparent; font: inherit; font-size: 13px; color: var(--ink); width: 100%; }
.tv-search:focus-within { border-color: var(--accent); }
.tv-flt { display: inline-flex; gap: 4px; align-items: center; height: 28px; padding: 0 4px 0 10px; border-radius: var(--r-sm); background: var(--accent-soft); color: var(--accent); font-size: 12.5px; font-weight: 650; }
.tv-flt[hidden] { display: none; }
.tv-flt button { border: 0; background: transparent; color: inherit; font-size: 16px; line-height: 1; width: 22px; height: 22px; border-radius: 5px; cursor: pointer; }
.tv-tools-end { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.tv-tools-end [hidden] { display: none; }
.tv-mp { width: 26px; height: 26px; border-radius: 7px; display: block; flex: none; }
.tv-task { border-top: 1px solid var(--rule); }
.tv-task:first-of-type { border-top: 0; }
.tv-task[hidden] { display: none; }
.tv-task-h { display: grid; grid-template-columns: 26px minmax(0, 1fr) 56px 76px 96px 14px; gap: 14px; align-items: center; padding: 10px 16px; cursor: pointer; list-style: none; }
.tv-task-h::-webkit-details-marker { display: none; }
.tv-task-h:hover { background: var(--surface-2); }
.tv-task-t { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tv-task-t b { font-size: 14px; font-weight: 650; }
.tv-task-t small { font-size: 12.5px; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tv-task-t small a { color: var(--accent); }
.tv-st { font-weight: 600; color: var(--ink); }
.tv-st-make, .tv-st-lost, .tv-st-back { color: var(--critical); }
.tv-st-deliver { color: var(--warn); }
.tv-task.is-wait .tv-task-t b { color: var(--ink-muted); }
.tv-task-n { font-size: 12.5px; color: var(--ink-muted); text-align: right; white-space: nowrap; }
.tv-task-n b { font-size: 15px; color: var(--ink); }
.tv-task-age { font-size: 12.5px; color: var(--ink-muted); text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.tv-task-age.is-old { color: var(--critical); font-weight: 650; }
.tv-who { font-size: 12px; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tv-task[open] .pb-chev { transform: rotate(45deg); }
.tv-task-b { border-top: 1px solid var(--rule); padding: 10px 16px 6px 56px; display: flex; flex-direction: column; gap: 8px; overflow-x: auto; }
.tv-why p { margin: 0; font-size: 13px; color: var(--ink-muted); max-width: 90ch; }
.tv-chain { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 6px 0 0; padding: 0; list-style: none; font-size: 12px; }
.tv-chain li { display: inline-flex; gap: 6px; align-items: baseline; }
.tv-chain i { width: 8px; height: 8px; border-radius: 50%; background: var(--rule-strong); flex: none; align-self: center; }
.tv-chain .is-ok i { background: var(--good); }
.tv-chain .is-bad i { background: var(--critical); }
.tv-chain b { font-weight: 600; }
.tv-chain small { color: var(--ink-muted); }
.tv-table { width: auto; align-self: flex-start; border-collapse: collapse; font-size: 13px; }
.tv-table td { padding: 4px 18px 4px 0; border: 0; white-space: nowrap; vertical-align: middle; }
.tv-table tr[hidden] { display: none; }
.tv-ph { width: 34px; }
.tv-ph img { width: 26px; height: 34px; object-fit: cover; border-radius: 4px; border: 1px solid var(--rule); display: block; }
.tv-age.is-old { color: var(--critical); font-weight: 650; }
.tv-task-f { font-size: 12.5px; padding-bottom: 4px; }
.tv-nothing { padding: 26px 18px; text-align: center; color: var(--ink-muted); }
.tv-nothing[hidden] { display: none; }
.tv-foot { margin: 2px 2px 0; }
.tv-hist { overflow: visible; }
.tv-hist-s { display: flex; gap: 12px; align-items: center; padding: 12px 16px; cursor: pointer; list-style: none; flex-wrap: wrap; }
.tv-hist-s::-webkit-details-marker { display: none; }
.tv-hist-s h2 { margin: 0; font-size: 15px; }
.tv-hist-s .pb-chev { margin-left: auto; }
.tv-hist[open] .tv-hist-s .pb-chev { transform: rotate(45deg); }
.tv-hist-tabs { padding: 0 16px 10px; }
.tv-htable { width: 100%; border-collapse: collapse; font-size: 13px; }
.tv-htable th { text-align: left; font-size: 11.5px; color: var(--ink-faint); font-weight: 500; padding: 6px 12px; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); white-space: nowrap; text-transform: none; letter-spacing: 0; }
.tv-htable td { padding: 6px 12px; border-bottom: 1px solid var(--rule); vertical-align: middle; }
.tv-htable td:first-child, .tv-htable th:first-child { padding-left: 16px; white-space: nowrap; }
.tv-htable tr[hidden] { display: none; }
.tv-htable .tv-mp { width: 18px; height: 18px; border-radius: 5px; }
.tv-h-store { white-space: nowrap; }
.tv-h-what small, .tv-htable td small { display: block; font-size: 11.5px; }
.tv-ok { color: var(--good); font-weight: 600; white-space: nowrap; }
.tv-open { color: var(--warn); font-weight: 600; white-space: nowrap; }
.tv-hist-more { display: block; width: 100%; padding: 9px 16px; border: 0; background: transparent; color: var(--accent); font: inherit; font-size: 13px; font-weight: 600; text-align: left; cursor: pointer; }
.tv-hist-more[hidden] { display: none; }
@media (max-width: 1800px) {
  .tv-node .tv-nl { display: none; }
}
@media (max-width: 1100px) {
  .tv-sum { grid-template-columns: 1fr; }
  .tv-count { border-right: 0; border-bottom: 1px solid var(--rule); }
  .tv-task-h { grid-template-columns: 26px minmax(0, 1fr) 56px 70px 14px; }
  .tv-who { display: none; }
  .tv-task-b { padding-left: 16px; }
}
@media (max-width: 640px) {
  .tv-task-h { grid-template-columns: 26px minmax(0, 1fr) 14px; gap: 10px; }
  .tv-task-n, .tv-task-age { display: none; }
  .tv-task-t small { white-space: normal; }
  .tv-search { width: 100%; }
  .tv-tools-end { margin-left: 0; }
  .tv-table td:nth-child(3) { display: none; }
}

/* Б/у запчасть (2026-11-06): отметка и описание состояния. */
.eqx-used-box { display: flex; gap: 8px; align-items: center; font-weight: 600; cursor: pointer; }
.eqx-used-note { margin-top: 8px; }
.eqx-used-note[hidden] { display: none; }
.eqx-used-note textarea { width: 100%; resize: vertical; }
.eqr-used { font-weight: 600; color: var(--warn); }

/* «Что на что расходуется»: свой цвет у материала и подсветка связей (2026-11-06). */
.mt-flow { --fl0: #2763d8; --fl1: #0f8f78; --fl2: #c27c0e; --fl3: #8a1fc4; --fl4: #d0453b; --fl5: #0a9bbd; --fl6: #c74b86; --fl7: #6a7d12; }
.mt-flow .mt-rib { stroke-opacity: .5; cursor: pointer; transition: stroke-opacity .15s; }
.mt-flow .mt-fn { cursor: pointer; outline: none; }
.mt-flow .mt-fn text { transition: opacity .15s; }
.mt-flow .mt-fn:focus-visible text { text-decoration: underline; }
.mt-flow svg.has-pick .mt-rib { stroke-opacity: .07; }
.mt-flow svg.has-pick .mt-rib.is-on { stroke-opacity: .85; }
.mt-flow svg.has-pick .mt-fn { opacity: .3; }
.mt-flow svg.has-pick .mt-fn.is-on { opacity: 1; }
.mt-flow svg.has-pick .mt-fn.is-on text { font-weight: 650; }
@media (prefers-reduced-motion: reduce) { .mt-flow .mt-rib, .mt-flow .mt-fn text { transition: none; } }

/* Строка материала под мышью подсвечивается целиком (2026-11-06): материалов
   много, и глаз должен держать строку от названия до кнопок. Прежний ховер
   (3,5 % серого) было почти не видно. Те же строки у «Оборудования». */
.mt-r:not(.mt-r-h):hover { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.mt-r:not(.mt-r-h):hover .mt-name { color: var(--accent); }
.mt-r.is-hot:hover { background: var(--accent-soft); }
.mt-table tbody tr { transition: background-color .12s; }
.mt-table tbody tr:hover td { background: var(--accent-soft); }
.mt-table tbody tr:hover td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
@media (prefers-reduced-motion: reduce) { .mt-r, .mt-table tbody tr { transition: none; } }

/* Справка «?» и тур (2026-11-06). */
.help-toggle { width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--rule-strong); background: var(--surface); color: var(--ink-muted); font: 700 14px/1 inherit; font-family: inherit; display: inline-grid; place-items: center; cursor: pointer; text-decoration: none; flex: none; }
.help-toggle:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.help-scrim { position: fixed; inset: 0; background: rgba(10, 12, 18, .28); z-index: 90; }
.help-scrim[hidden], .help-drawer[hidden] { display: none; }
.help-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(460px, 100%); background: var(--surface); z-index: 91; box-shadow: -12px 0 32px rgba(10, 12, 18, .16); display: flex; flex-direction: column; padding-top: env(safe-area-inset-top, 0px); }
.help-head { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; padding: 18px 20px 12px; border-bottom: 1px solid var(--rule); }
.help-head h2 { margin: 2px 0 0; font-size: 18px; letter-spacing: -0.01em; }
.help-who { font-size: 12px; color: var(--ink-muted); }
.help-x { width: 32px; height: 32px; border: 0; border-radius: 8px; background: transparent; color: var(--ink-muted); font-size: 22px; line-height: 1; cursor: pointer; }
.help-x:hover { background: var(--surface-2); color: var(--ink); }
.help-body { overflow: auto; padding: 14px 20px 24px; display: flex; flex-direction: column; gap: 12px; font-size: 14px; line-height: 1.5; }
.help-goal { margin: 0; color: var(--ink-muted); }
.help-tour-btn { align-self: flex-start; }
.help-steps { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 9px; }
.help-steps li::marker { color: var(--accent); font-weight: 700; }
.help-body h3 { margin: 8px 0 0; font-size: 14px; }
.help-faq { border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 8px 12px; }
.help-faq summary { cursor: pointer; font-weight: 600; }
.help-faq p { margin: 6px 0 0; color: var(--ink-muted); }
.help-all { font-weight: 600; color: var(--accent); margin-top: 4px; }
.tour-box { position: fixed; z-index: 95; border-radius: 10px; box-shadow: 0 0 0 3px var(--accent), 0 0 0 9999px rgba(10, 12, 18, .42); pointer-events: none; transition: top .2s, left .2s, width .2s, height .2s; }
.tour-pop { position: fixed; z-index: 96; width: min(340px, calc(100vw - 24px)); background: var(--surface); border-radius: 12px; box-shadow: 0 12px 32px rgba(10, 12, 18, .25); padding: 14px 16px 12px; font-size: 14px; line-height: 1.45; }
.tour-text { margin: 0 0 12px; }
.tour-foot { display: flex; gap: 8px; align-items: center; }
.tour-n { font-size: 12px; color: var(--ink-muted); margin-right: auto; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .tour-box { transition: none; } }
/* Страница «Как работать». */
.help-page { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 20px; align-items: start; }
.help-nav { position: sticky; top: 80px; display: flex; flex-direction: column; gap: 2px; }
.help-nav a { padding: 7px 10px; border-radius: var(--r-sm); color: var(--ink-muted); font-weight: 600; text-decoration: none; }
.help-nav a:hover { background: var(--surface-2); color: var(--ink); }
.help-sec { padding: 18px 22px 20px; scroll-margin-top: 80px; }
.help-sec + .help-sec { margin-top: 16px; }
.help-sec h2 { margin: 0; font-size: 18px; }
.help-sec .help-who { display: block; margin-top: 2px; }
.help-sec .help-goal { margin: 8px 0 12px; }
.help-sec .help-steps { max-width: 80ch; }
.help-sec .help-faqs { display: flex; flex-direction: column; gap: 8px; max-width: 80ch; margin-top: 8px; }
@media (max-width: 900px) { .help-page { grid-template-columns: 1fr; } .help-nav { position: static; flex-direction: row; flex-wrap: wrap; } }

/* Раскладка: откуда товар (2026-11-12). Без выбора сохранить нельзя. */
.put-source { display: grid; gap: 6px; align-content: start; max-width: 260px; }
.put-source.is-need .cd-kind { border-color: var(--critical); }

/* «Группы»: упаковка и распределение материалов (2026-11-12). */
.mt-group-pack { margin: -2px 0 4px; font-size: 12.5px; color: var(--ink-muted); }
.mt-group-pack label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.mt-group-assign { max-height: 46vh; overflow: auto; }
.mt-group-assign .mt-group-row { flex-wrap: nowrap; }
.mt-group-assign .mt-group-row[hidden] { display: none; }
.mt-ga-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.mt-group-assign .mt-group-row .btn-input { width: 160px; flex: none; }
.mt-sort { padding-top: 8px; padding-bottom: 8px; }

/* Отчёт Excel: быстрые периоды (2026-11-12). */
.xr-presets { display: flex; flex-wrap: wrap; gap: 6px; flex-basis: 100%; }

/* Карточка ячейки: линейка + номер и «откуда товар» (2026-11-12, v0.9.317) */
.cf-prefix { display: grid; grid-template-columns: minmax(0, 1fr) 96px auto; gap: 6px; }
.rack-field .cf-prefix .btn-input, .rack-field .cf-prefix select.btn-input { width: 100%; min-width: 0; }
.cf-source { display: flex; flex-direction: column; gap: 5px; }
.cf-source[hidden] { display: none; }
.cf-source.is-need .cd-kind { border-color: var(--critical); box-shadow: 0 0 0 2px color-mix(in srgb, var(--critical) 25%, transparent); }
.cf-source.is-need .rack-field-hint { color: var(--critical); }

/* Telegram: ссылка-приглашение (2026-11-13, v0.9.319) */
.tg-invite-new { display: flex; gap: 16px; align-items: center; border-bottom: 1px solid var(--rule); }
.tg-invite-qr { width: 132px; flex: none; background: #fff; padding: 6px; border-radius: 6px; }
.tg-invite-qr svg { width: 100%; height: auto; display: block; }
.tg-invite-text { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
@media (max-width: 700px) { .tg-invite-new { flex-direction: column; align-items: stretch; } .tg-invite-qr { align-self: center; } }
.tg-invite-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.tg-invites { padding: 0 16px 14px; }
