/*
  TOC Planner — визуальный редизайн M11/02 (design-канвас, скилл `design`,
  одобрено 2026-08-20). Тёмный сайдбар (фиксирован, не зависит от темы) +
  светлая/тёмная тема рабочей области на CSS custom properties, индиго-акцент,
  карточный вид со скруглениями/тенями. Ручной переключатель темы (.nav__theme,
  M11/03) переопределяет автоопределение по prefers-color-scheme через явные
  классы .theme-dark/.theme-light на <html> — здесь CSS-система обеих тем.
  Цвета зон буфера (красный/жёлтый/зелёный/чёрный) остаются функциональной
  сигнализацией состояния, не декором и не в счёт акцентного цвета.
*/

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/inter-var.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116, U+0000-00FF;
}

:root {
  /* light dark = страница реально поддерживает обе схемы (см. media-запрос
     ниже) -- без этого браузер может сам "форсировать" тёмную перекраску
     непредсказуемой эвристикой вместо настоящих токенов темы (та самая
     "не видно группы в Маршрутах": эвристика красит текст в цвет её же
     перекрашенного фона). Объявив обе схемы официально и правда её
     поддерживая, отдаём выбор браузеру/ОС пользователя, а не заставляем
     его лезть в настройки. */
  color-scheme: light dark;
  --sidebar-bg: #12172b; /* не меняется темой */
  --sidebar-ink: #c7cbe0;
  --sidebar-ink-soft: #7d82a3;
  --sidebar-border: rgba(255, 255, 255, 0.08);
  --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
  --sidebar-active-bg: rgba(79, 109, 245, 0.18);

  --bg: #f5f6fa;
  --surface: #ffffff;
  --surface-2: #fafafd;
  --ink: #161a2c;
  --ink-soft: #6a6f85;
  --border: #e6e8f0;
  --border-strong: #d7dae6;
  --danger: #c0392b;
  --danger-soft: #fbeceb;

  --zone-green: #16a34a;
  --zone-yellow: #c77700;
  --zone-red: #dc2626;
  --zone-black: #161a2c;

  /* акцент не привязан к теме — одно значение и в светлой, и в тёмной */
  --accent: #4f6df5;
  --accent-soft: rgba(79, 109, 245, 0.12);

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
  --shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.07);

  --font: "Inter", -apple-system, "Segoe UI", Arial, sans-serif;

  --radius: 14px;
  --radius-sm: 8px;
  --ease: 150ms ease;
  --sidebar-w: 248px;
}

/* Тёмная тема по умолчанию следует системной/браузерной настройке
   пользователя (M11/02 первоначально решил иначе -- только явный выбор,
   без auto -- но это оставляло пользователей с системной тёмной темой
   один на один с браузерным "форс-затемнением" сайта без объявленной
   поддержки тёмной схемы, а оно красит непредсказуемо и местами
   нечитаемо; см. `:not(.theme-light)` ниже и `.theme-dark` дальше --
   ручной переключатель (M11/03, .nav__theme в base.html) переопределяет в
   любую сторону явным классом на <html>, он всегда сильнее автоопределения). */
@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) {
    --bg: #0b0e1a;
    --surface: #131829;
    --surface-2: #171d33;
    --ink: #eef1fb;
    --ink-soft: #8991ad;
    --border: #232a45;
    --border-strong: #2c3454;
    --danger: #f87171;
    --danger-soft: rgba(248, 113, 113, 0.13);

    --zone-green: #34d399;
    --zone-yellow: #fbbf24;
    --zone-red: #f87171;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

/* Явный класс (ручной переключатель, M11/03) — сильнее системной настройки
   в обе стороны за счёт места в каскаде (после media-блока). */
.theme-dark {
  --bg: #0b0e1a;
  --surface: #131829;
  --surface-2: #171d33;
  --ink: #eef1fb;
  --ink-soft: #8991ad;
  --border: #232a45;
  --border-strong: #2c3454;
  --danger: #f87171;
  --danger-soft: rgba(248, 113, 113, 0.13);

  --zone-green: #34d399;
  --zone-yellow: #fbbf24;
  --zone-red: #f87171;
  /* --zone-black не используется в тёмной теме — см. .buffer-stat--black ниже */

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
}

* { box-sizing: border-box; }

html { background: var(--bg); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.45;
  min-height: 100vh;
  /* own block-formatting context so a child's top margin (main's
     "margin: 12vh auto 0" below login/scan) can't collapse through body
     and expose html's background above body's own -- harmless otherwise,
     since nothing here relies on margin collapsing. */
  display: flow-root;
}

h1, h2, h3 {
  font-family: var(--font);
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0 0 0.6em;
}

h1 { font-size: 1.5rem; }
h2 { font-size: 1.05rem; margin-top: 1.6em; padding-bottom: 0.4em; border-bottom: 1px solid var(--border); }
h3 { font-size: 0.95rem; }

p { margin: 0 0 0.8em; }

a {
  color: var(--accent);
  text-decoration: none;
  transition: opacity var(--ease);
}
a:hover { text-decoration: underline; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---------- иконки (inline SVG-спрайт из base.html) ---------- */

.icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  flex: none;
}

/* ---------- боковая навигация ---------- */

.sidebar-shell { position: relative; }

.nav-toggle {
  display: none;
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1001;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--sidebar-border);
  background: var(--sidebar-bg);
  cursor: pointer;
}
.nav-toggle__bar {
  display: block;
  width: 18px;
  height: 2px;
  margin: 4px auto;
  background: var(--sidebar-ink);
}

/* #primary-nav, not a bare `nav` selector -- production_groups/_nav.html
   also uses a semantic <nav> (.route-nav, the route picker panel) for its
   own unrelated purpose, and a bare element selector here was bleeding the
   sidebar's dark background onto it with no matching text color override
   (found 2026-08-21: .route-nav__item's own `color: var(--ink)` -- dark in
   the light theme -- landed on the inherited dark sidebar background,
   making every route name invisible). */
#primary-nav {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidebar-w);
  overflow-y: auto;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--sidebar-border);
  color: var(--sidebar-ink);
  padding: 18px 12px 24px;
  font-size: 0.85rem;
  z-index: 1000;
}

.nav__brand {
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  color: #ffffff;
  padding: 0 8px 14px;
  border-bottom: 1px solid var(--sidebar-border);
  margin-bottom: 10px;
}

.nav-group { margin-bottom: 10px; }

.nav-group__label {
  display: block;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--sidebar-ink-soft);
  padding: 8px 8px 2px;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  color: var(--sidebar-ink);
  transition: background var(--ease), color var(--ease);
}
.nav-link:hover {
  background: var(--sidebar-hover-bg);
  color: #ffffff;
  text-decoration: none;
}
.nav-link.nav-current {
  background: var(--sidebar-active-bg);
  color: #ffffff;
  font-weight: 600;
}
.nav-link .icon { color: var(--sidebar-ink-soft); }
.nav-link:hover .icon, .nav-link.nav-current .icon { color: var(--accent); }

.nav__theme {
  display: flex;
  gap: 4px;
  margin: 14px 4px 0;
  padding: 4px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--sidebar-border);
  border-radius: var(--radius-sm);
}
.nav__theme label {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px 0;
  border-radius: calc(var(--radius-sm) - 2px);
  cursor: pointer;
  color: var(--sidebar-ink-soft);
  transition: background var(--ease), color var(--ease);
}
.nav__theme label:hover { color: #ffffff; }
.nav__theme label:has(input:checked) { background: var(--sidebar-active-bg); color: #ffffff; }
.nav__theme label:has(input:checked) .icon { color: var(--accent); }

.nav__logout { margin: 14px 4px 0; }
.nav__logout button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  border: 1px solid var(--sidebar-border);
  color: var(--sidebar-ink);
  border-radius: var(--radius-sm);
}
.nav__logout button:hover { background: var(--sidebar-hover-bg); color: #ffffff; opacity: 1; }
.nav__logout .icon { color: var(--sidebar-ink-soft); }
.nav__version { margin: 10px 14px 0; font-size: 0.72rem; color: var(--sidebar-ink-soft); }

.badge-count {
  display: inline-block;
  min-width: 1.3em;
  padding: 0 0.35em;
  border-radius: 999px;
  background: var(--zone-red);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  text-align: center;
}

main {
  padding: 24px 32px 60px;
  max-width: 1440px;
}
.sidebar-shell ~ main { margin-left: var(--sidebar-w); }

@media (max-width: 900px) {
  .nav-toggle { display: block; }
  #primary-nav {
    transform: translateX(-100%);
    transition: transform var(--ease);
    box-shadow: 4px 0 16px rgba(0, 0, 0, 0.35);
  }
  #primary-nav.nav--open { transform: translateX(0); }
  .nav__brand { padding-left: 50px; }
  .sidebar-shell ~ main { margin-left: 0; }
  main { padding: 24px 16px 48px 16px; margin-top: 40px; }
}

/* login и прочие страницы без nav-блока */
body:not(:has(.sidebar-shell)) main {
  max-width: 380px;
  margin: 12vh auto 0;
}

/* экран входа: фон вне сайдбара всегда тёмный (--sidebar-bg), без темы --
   карточка формы сама на токенах текущей темы */
body:has(.login-screen) { background: var(--sidebar-bg); }

/* ---------- формы ---------- */

label {
  display: block;
  margin-bottom: 10px;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

input, select, textarea {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 7px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  transition: border-color var(--ease), box-shadow var(--ease);
}
/* Внутри узкой ячейки таблицы (например, инлайн-поле даты отгрузки в
   дашборде диспетчера) ширина колонки считается браузером по соседним
   ячейкам с обычным текстом ("21.08.2026"), а не по этому input -- без
   min-width значок открытия календаря обрезается/не помещается. */
input[type="date"] { min-width: 140px; }
/* Хромиум-сборки со своей темой поверх движка (Яндекс.Браузер, Opera и
   т.п.) нередко приглушают/обнуляют непрозрачность нативной иконки
   календаря по умолчанию -- сам пикер по клику всё равно открывается
   (иконка занимает область, просто визуально не видна), значок нужно
   явно вернуть. */
input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 1;
  cursor: pointer;
  filter: none;
}

/* date-picker-button.js: собственная, всегда видимая кнопка календаря
   рядом с input[type="date"] -- не зависит от того, рисует ли конкретная
   сборка браузера свою нативную иконку вообще. */
.date-input-wrap {
  display: inline-flex;
  align-items: stretch;
  gap: 4px;
  width: 100%;
  margin-top: 4px;
}
.date-input-wrap input[type="date"] {
  width: auto;
  flex: 1 1 auto;
  min-width: 0;
  margin-top: 0;
}
.date-input-wrap__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
}
.date-input-wrap__btn:hover { background: var(--surface-2); color: var(--accent); }
.date-input-wrap__btn .icon { width: 16px; height: 16px; }

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* флажки/радио — не растягиваем на всю ширину блочного input/label */
input[type="checkbox"], input[type="radio"] {
  display: inline-block;
  width: auto;
  margin: 0 6px 0 0;
  vertical-align: middle;
}
label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) {
  display: inline-flex;
  align-items: center;
  width: auto;
  margin: 0 16px 10px 0;
}

/* ---------- горизонтальная строка фильтров (заказы) ---------- */
/* Опционально: остальные страницы со своими <select>-фильтрами (Наряды,
   Брак и т.п.) сознательно не переведены на этот класс -- у них фильтров
   всего 1-2, вертикальный список по умолчанию не выглядит перегруженным. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 6px 20px;
  margin-bottom: 18px;
}
.filter-bar label { margin-bottom: 0; }
.filter-bar input, .filter-bar select { width: auto; min-width: 150px; }
.filter-bar label:has(> input[type="checkbox"]) { margin: 0 0 8px; }
.filter-bar button { margin-bottom: 1px; }

fieldset {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  margin: 0 0 10px;
}
legend {
  padding: 0 6px;
  color: var(--ink-soft);
  font-size: 0.8rem;
}

button, input[type="submit"] {
  display: inline-block;
  width: auto;
  padding: 7px 16px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  transition: background var(--ease), opacity var(--ease);
}
button:hover, input[type="submit"]:hover { opacity: 0.85; }
button:active, input[type="submit"]:active { opacity: 0.7; }

/* ---------- таблицы ---------- */

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 1.2em;
}

thead th {
  text-align: left;
  padding: 7px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border-strong);
  color: var(--ink-soft);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

tbody td {
  padding: 7px 12px;
  border-top: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}

tbody tr:hover { background: var(--surface-2); }

/* ---------- Гант CCR: план в штуках по дням ---------- */

.daily-schedule-table th,
.daily-schedule-table td { padding: 5px 8px; white-space: nowrap; text-align: right; }
.daily-schedule-table th:first-child, .daily-schedule-table th:nth-child(2),
.daily-schedule-table td:first-child, .daily-schedule-table td:nth-child(2) { text-align: left; }
.daily-schedule-table__press-row td {
  background: var(--surface-2);
  font-weight: 600;
  border-top: 1px solid var(--border-strong);
}
.daily-schedule-table tfoot td {
  font-weight: 600;
  border-top: 2px solid var(--border-strong);
}

/* ---------- общий хвост маршрута ---------- */
/* Клик-для-правки, как у таблицы "Маршрут" (переведено обратно
   2026-08-24 -- ради визуального единообразия со страницей группы, где
   обе таблицы теперь стоят рядом); min-width действует только на
   раскрытом select/input, чтобы при правке браузер не сжимал его до
   нечитаемой ширины. overflow-x -- теперь общее свойство .card ниже, не
   локальное для этой таблицы (2026-08-24, адаптивность под узкие экраны). */
.tail-steps-table td:nth-child(2) select { min-width: 150px; }
.tail-steps-table td:nth-child(3) input { min-width: 140px; }
.tail-steps-table td:nth-child(4) select { min-width: 160px; }
.tail-steps-table td:nth-child(5) input,
.tail-steps-table td:nth-child(6) input,
.tail-steps-table td:nth-child(9) input { min-width: 70px; }
.tail-steps-table td:nth-child(7) select { min-width: 140px; }
.tail-steps-table td:nth-child(8) select { min-width: 70px; }

/* ---------- заголовок страницы + основное действие ---------- */

.page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.page-header h1 { margin-bottom: 0.2em; }

/* ---------- карточка (общий контейнер редизайна M11/02) ---------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px 20px;
  margin-bottom: 18px;
  /* Защита от переполнения страницы на узких экранах: если содержимое
     (обычно таблица) не помещается, скроллится сама карточка, а не body. */
  overflow-x: auto;
}
.card table { border: none; margin: 10px 0 0; }
.card h2:first-child { margin-top: 0; }

/* ---------- карточки заказов (дашборд диспетчера) ---------- */

.order-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px 20px;
  margin-bottom: 18px;
  overflow-x: auto;
}
.order-card:has(.order-card__risk) { border-left-color: var(--zone-red); }
.order-card table { border: none; margin: 10px 0 0; }
.order-card h2 { margin-top: 0; }
.order-card > summary { cursor: pointer; }
.order-card > summary::-webkit-details-marker { color: var(--ink-soft); }

.order-summary {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 20px;
}
.order-summary__id { font-weight: 700; }
.order-summary__client { font-weight: 600; }

.order-card__risk { color: var(--zone-red); font-weight: 600; }

.text-alert { color: var(--zone-red); font-weight: 600; }
.text-muted { color: var(--ink-soft); }

/* Деньги (templating.py::money, M17 тикет 148). Разряды разведены отступом
   группы, а не символом: скопированное число «126260,50» Excel берёт числом.
   Отступ — поле, поэтому переживает печать в PDF; nowrap — чтобы группа не
   уехала на следующую строку, раз пробела, запрещающего перенос, нет. */
.money { font-variant-numeric: tabular-nums; white-space: nowrap; }
.money__group { margin-left: 0.25em; }

/* Внутренний номер Заказа/Наряда рядом с номером из 1С (macros.html::entity_no):
   всегда мельче и бледнее соседа, в каком бы заголовке или ячейке ни стоял --
   отсюда em, а не rem, и явный font-weight против жирного <h1>/<summary>. */
.entity-no__internal {
  color: var(--ink-soft);
  font-size: 0.78em;
  font-weight: 400;
  white-space: nowrap;
}

/* ---------- кнопки-ссылки (второстепенные действия) ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--ink-soft); text-decoration: none; }
.btn--accent { border-color: var(--accent); color: var(--accent); }
.btn--accent:hover { background: var(--accent-soft); }
.btn--danger-ghost { border-color: transparent; color: var(--ink-soft); }
.btn--danger-ghost:hover { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

/* ---------- статус-пилюли ---------- */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-soft);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}
.status-pill--info { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.status-pill--success { background: color-mix(in srgb, var(--zone-green) 16%, transparent); color: var(--zone-green); border-color: transparent; }
.status-pill--warning { background: color-mix(in srgb, var(--zone-yellow) 18%, transparent); color: var(--zone-yellow); border-color: transparent; }
.status-pill--danger { background: var(--danger-soft); color: var(--danger); border-color: transparent; }

/* ---------- числовые плитки (результат массовых операций) ---------- */

.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
  margin: 16px 0 24px;
  max-width: 640px;
}
.stat-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px;
}
.stat-tile__value {
  font-size: 1.8rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1.1;
}
.stat-tile__label {
  color: var(--ink-soft);
  font-size: 0.85rem;
  margin-top: 4px;
}
.stat-tile--success .stat-tile__value { color: var(--zone-green); }
.stat-tile--danger .stat-tile__value { color: var(--danger); }

/* ---------- пустые состояния ---------- */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 48px 20px;
  color: var(--ink-soft);
  text-align: center;
}
.empty-state .icon {
  width: 40px;
  height: 40px;
  padding: 10px;
  box-sizing: content-box;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-soft);
}
.empty-state p { margin: 0; }

/* ---------- индикатор буфера — крупный табличный числитель + тонкая шкала ---------- */

.buffer-stat {
  display: inline-flex;
  flex-direction: column;
  gap: 3px;
  min-width: 3.2rem;
}
.buffer-stat__value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 0.92rem;
  letter-spacing: -0.01em;
  color: var(--zone-color, var(--ink));
}
/* Число посчитано приблизительно: маршрут упирается в ресурс, на который
   никто не назначен, и обратный обход не проходит (M4 тикет 16). Причина --
   в title, поэтому курсор help и пунктир: значок кликать некуда, но навести
   стоит. */
.estimate-mark {
  margin-left: 3px;
  padding: 0 2px;
  border-bottom: 1px dotted currentColor;
  color: var(--ink-soft);
  font-weight: 600;
  cursor: help;
}

.buffer-stat__bar {
  --pct: 0;
  height: 3px;
  background: var(--border);
  position: relative;
}
.buffer-stat__bar::after {
  content: "";
  position: absolute;
  inset: 0;
  width: min(calc(var(--pct) * 1%), 100%);
  background: var(--zone-color, var(--ink));
}

.buffer-stat--green { --zone-color: var(--zone-green); }
.buffer-stat--yellow { --zone-color: var(--zone-yellow); }
.buffer-stat--red { --zone-color: var(--zone-red); }
.buffer-stat--black { --zone-color: var(--zone-black); }
.buffer-stat--black .buffer-stat__value::after {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  margin-left: 5px;
  background: var(--zone-black);
  animation: pulse 1.6s ease-in-out infinite;
}

/* тёмная тема: чёрная зона (буфер исчерпан) невидима на тёмном фоне —
   вместо сплошного индикатора показываем белую пульсирующую плашку.
   Шире, чем спрятанный "120%", поэтому сам .buffer-stat тоже расширяется --
   иначе плашка вылезает поверх соседних элементов в строке (inline-flex
   сосед не знает про абсолютно спозиционированный псевдоэлемент).
   Дублируется под системную тёмную (автоопределение) и под явный класс —
   тот же случай, что и с токенами выше. */
@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) .buffer-stat--black { min-width: 7rem; }
  :root:not(.theme-light) .buffer-stat--black .buffer-stat__value {
    visibility: hidden;
    position: relative;
  }
  :root:not(.theme-light) .buffer-stat--black .buffer-stat__value::before {
    content: "критично · 0%";
    visibility: visible;
    position: absolute;
    left: 0;
    top: 0;
    white-space: nowrap;
    color: #fff;
    background: rgba(255, 255, 255, 0.12);
    border: 1px solid #fff;
    border-radius: var(--radius-sm);
    padding: 1px 8px;
    font-size: 0.72rem;
    font-weight: 700;
    animation: pulse 1.6s ease-in-out infinite;
  }
  :root:not(.theme-light) .buffer-stat--black .buffer-stat__value::after { content: none; }
}
.theme-dark .buffer-stat--black { min-width: 7rem; }
.theme-dark .buffer-stat--black .buffer-stat__value {
  visibility: hidden;
  position: relative;
}
.theme-dark .buffer-stat--black .buffer-stat__value::before {
  content: "критично · 0%";
  visibility: visible;
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid #fff;
  border-radius: var(--radius-sm);
  padding: 1px 8px;
  font-size: 0.72rem;
  font-weight: 700;
  animation: pulse 1.6s ease-in-out infinite;
}
.theme-dark .buffer-stat--black .buffer-stat__value::after { content: none; }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

/* ---------- умный стол (интерактивные таблицы, M9 пилот) ---------- */

[x-cloak] { display: none !important; }

.smart-table-wrap { position: relative; overflow-x: auto; }

/* Only tables actually wired for client sort (data-smart-table) get the
   pointer affordance -- a table using smart_table_head just for its
   column-picker (e.g. the route workspace's operations table, where sorting
   would scramble node/operation grouping) keeps a plain header instead of
   promising a click that does nothing. */
.smart-table[data-smart-table] thead th[data-sort-key] { cursor: pointer; user-select: none; }
.smart-table[data-smart-table] thead th[data-sort-key]:hover { color: var(--ink); }
.smart-table thead th.smart-table__th--sorted::after {
  content: "▲";
  display: inline-block;
  margin-left: 5px;
  font-size: 0.65em;
}
.smart-table thead th.smart-table__th--sorted[data-sort-direction="desc"]::after { content: "▼"; }
/* Сортирует сервер (M17 тикет 65): заголовок -- ссылка, выглядит как прежний. */
.smart-table__sort-link { color: inherit; text-decoration: none; }
.smart-table__sort-link:hover { color: var(--ink); }

/* Строка продолжения длинного списка (M17 тикет 65): подменяется следующей
   порцией, когда показывается на экране. */
.list-more td { background: var(--surface-2); height: 40px; color: var(--ink-soft); }
.list-more--block {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  min-height: 40px;
  padding: 0 12px;
  background: var(--surface-2);
  color: var(--ink-soft);
}
.list-more__top { margin-left: 12px; }

.cell-editable { cursor: pointer; }
.cell-editable [data-cell-edit] { display: none; width: 100%; margin-top: 0; }
.cell-editable--active [data-cell-view] { display: none; }
.cell-editable--active [data-cell-edit] { display: inline-block; }

.smart-table__picker-cell { position: relative; width: 1%; text-align: right; }
.smart-table__picker-toggle {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink-soft);
  width: 26px;
  height: 26px;
  padding: 0;
  line-height: 1;
  font-size: 0.85rem;
}
.smart-table__col-picker {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  margin-top: 4px;
  min-width: 190px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 8px 12px;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
}
.smart-table__col-picker label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
}
.smart-table__col-picker input[type="checkbox"] {
  display: inline-block;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
}

/* ---------- рабочее пространство маршрута (Производственные группы) ---------- */

main:has(.route-workspace) { max-width: 1680px; }

.route-workspace {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 28px;
  align-items: start;
}

.route-nav {
  position: sticky;
  top: 20px;
  max-height: calc(100vh - 40px);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.route-nav__filter {
  margin: 0;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.route-nav__list { overflow-y: auto; display: flex; flex-direction: column; }
.route-nav__item {
  display: block;
  padding: 6px 12px;
  border-left: 2px solid transparent;
  color: var(--ink);
  font-size: 0.85rem;
}
.route-nav__item:hover { background: var(--surface-2); text-decoration: none; }
.route-nav__item--current {
  border-left-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
  background: var(--accent-soft);
}
.route-nav__section { border-top: 1px solid var(--border); }
.route-nav__section:first-child { border-top: none; }
.route-nav__section > summary { cursor: pointer; padding: 8px 12px; }
.route-nav__section > summary:hover { background: var(--surface-2); }
.route-nav__section-summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: calc(100% - 14px);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.route-nav__section-count { margin-left: auto; font-weight: 400; font-variant-numeric: tabular-nums; }
.route-nav__section .route-nav__item { padding-left: 28px; }

.route-nav__add {
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  background: var(--surface-2);
}

.route-main { min-width: 0; overflow-x: auto; }
.route-main__table-wrap { overflow-x: auto; }

.route-node-row > td {
  background: var(--surface-2);
  font-weight: 600;
  border-top: 2px solid var(--border-strong);
}
.route-op-row { cursor: grab; }
.route-op-row__handle { color: var(--ink-soft); text-align: center; cursor: grab; }
.route-node-row__add {
  display: inline-block;
  margin-left: 12px;
  font-weight: 400;
  font-size: 0.85rem;
}
.route-node-row__add summary { cursor: pointer; color: var(--accent); }
.route-node-row__add-forms {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.route-node-row__form {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  font-weight: 400;
}
.route-node-row__form-title {
  color: var(--ink-soft);
  font-size: 0.85rem;
  white-space: nowrap;
}
.route-node-row__form input,
.route-node-row__form select {
  display: inline-block;
  width: auto;
  margin: 0;
}

/* ---------- маршрут группы как конструктор (M17 тикет 10) ---------- */

.route-crumbs { display: flex; flex-wrap: wrap; gap: 6px; font-size: 0.85rem; color: var(--ink-soft); margin-bottom: 6px; }
.route-crumbs [aria-current] { color: var(--ink); font-weight: 600; }
.route-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.route-head h1 { margin: 0; }
.route-head__note { margin: 6px 0 0; font-size: 0.85rem; }

.route-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
  margin: 16px 0;
}
.route-summary > div {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
}
.route-summary dt { color: var(--ink-soft); font-size: 0.78rem; }
.route-summary dd { margin: 2px 0 0; font-size: 1.15rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.route-summary dd small { font-size: 0.78rem; font-weight: 400; color: var(--ink-soft); }
.route-summary__warn dd { color: var(--danger); }

.route-layout { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; align-items: start; }
@media (max-width: 1100px) { .route-layout { grid-template-columns: minmax(0, 1fr); } }

.route-tree__toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 10px; }
.route-tree__picker { position: relative; }
.route-tree__product { font-size: 0.82rem; color: var(--accent); }
.route-tree__add-root { margin: 6px 0 0; }

.route-node { border: 1px solid var(--border); border-radius: var(--radius); margin: 0 0 10px; background: var(--surface); }
.route-node .route-node { margin: 10px 0 10px 14px; border-left: 3px solid var(--accent-soft); }
.route-node__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
.route-node__toggle { border: none; background: none; padding: 0 4px; margin: 0; cursor: pointer; color: var(--ink-soft); font-size: 1rem; }
.route-node__name { font-weight: 600; }
.route-node__meta { color: var(--ink-soft); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.route-node__enter { margin-left: auto; font-size: 0.82rem; }
.route-node__body { padding: 8px 12px 12px; }
.route-node__tools { display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: flex-start; margin-bottom: 6px; }
.route-node__tools .route-node-row__add { margin-left: 0; }
.route-node__converge { margin: 10px 0 4px; font-size: 0.82rem; color: var(--ink-soft); }
.route-node__empty { margin: 4px 0; font-size: 0.85rem; }

.route-ops-wrap { overflow-x: auto; }
.route-ops { width: 100%; }
.route-op-row--constraint > td:first-child { box-shadow: inset 3px 0 0 var(--zone-yellow); }
.route-op-row__step { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.route-op-row__marks { white-space: normal; min-width: 180px; }
.route-op-row__marks .status-pill { margin: 0 4px 4px 0; }
.route-op__comp-toggle { border: none; background: none; padding: 0; margin: 0; color: var(--accent); cursor: pointer; font-size: 0.82rem; text-align: left; }

.route-comp { font-size: 0.84rem; margin: 4px 0 8px; }
.route-comp th, .route-comp td { padding: 3px 12px 3px 0; border: 0; }
.route-comp tr.is-mine > td,
.route-flat tr.is-mine > td { background: var(--accent-soft); }
.route-comp tr.is-dim > td,
.route-flat tr.is-dim > td { opacity: 0.4; }
.route-comp__add { margin-top: 6px; }
.route-comp__links { margin: 6px 0 0; font-size: 0.82rem; }

.route-products {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 40px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.route-products h2 { font-size: 0.95rem; margin: 0; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.route-products__filter { margin: 0; border: none; border-bottom: 1px solid var(--border); border-radius: 0; }
.route-products__hint { margin: 0; padding: 6px 12px; font-size: 0.78rem; color: var(--ink-soft); }
.route-products__list { list-style: none; margin: 0; padding: 0; overflow-y: auto; }
.route-products__item {
  display: flex;
  width: 100%;
  gap: 6px;
  align-items: baseline;
  margin: 0;
  padding: 6px 12px;
  border: none;
  border-left: 2px solid transparent;
  border-radius: 0;
  background: none;
  color: var(--ink);
  font-size: 0.84rem;
  text-align: left;
  cursor: pointer;
}
.route-products__item:hover { background: var(--surface-2); }
.route-products__item.is-selected { border-left-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.route-products__item .badge { margin-left: auto; }
.route-products__catalog { padding: 8px 12px; border-top: 1px solid var(--border); font-size: 0.82rem; background: var(--surface-2); }

.route-migration {
  margin: 12px 0;
  padding: 10px 14px;
  border: 1px solid var(--zone-yellow);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--zone-yellow) 8%, var(--surface));
}
.route-migration > summary { cursor: pointer; }
.route-migration--quiet { border-color: var(--border); background: var(--surface-2); color: var(--ink-soft); }
.route-migration__explain { font-size: 0.85rem; color: var(--ink-soft); }
.route-migration__list { max-height: 320px; overflow: auto; margin: 8px 0; }
.route-migration__blocked-title { font-size: 0.9rem; margin: 12px 0 4px; }
.route-migration__blocked { margin: 0; font-size: 0.85rem; }
.route-note {
  margin: 12px 0;
  padding: 10px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--zone-green) 10%, var(--surface));
}

.route-flat { margin-top: 24px; }
.route-flat > summary { cursor: pointer; }
.route-flat > summary h2 { display: inline; }

.route-matrix-wrap { overflow: auto; max-height: 70vh; margin: 12px 0; border: 1px solid var(--border); border-radius: var(--radius); }
.route-matrix { border-collapse: separate; border-spacing: 0; font-size: 0.82rem; }
.route-matrix th, .route-matrix td { border-bottom: 1px solid var(--border); padding: 2px 4px; }
.route-matrix thead th { position: sticky; top: 0; z-index: 2; background: var(--surface-2); vertical-align: bottom; min-width: 96px; }
.route-matrix tbody th { position: sticky; left: 0; z-index: 1; background: var(--surface); text-align: left; white-space: nowrap; }
.route-matrix input { width: 84px; margin: 0; padding: 2px 4px; text-align: right; font-variant-numeric: tabular-nums; }
.route-matrix input.is-dup { border-color: var(--danger); }
.route-matrix__own { white-space: nowrap; }
.route-matrix__own label { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin: 1px 0; font-size: 0.78rem; }

/* ---------- инлайн-добавление значения в справочник (Навыки/Партнёры/...) ---------- */

.quick-add-select {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.quick-add-select select { display: inline-block; width: auto; margin: 0; }
.quick-add-select__add { display: inline-block; }
.quick-add-select__add summary {
  cursor: pointer;
  list-style: none;
  display: inline-block;
  width: 1.6em;
  text-align: center;
  color: var(--accent);
  font-weight: 700;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  line-height: 1.5;
}
.quick-add-select__add summary::-webkit-details-marker { display: none; }
.quick-add-select__form {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  margin-top: 4px;
  padding: 10px;
  width: 260px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.quick-add-select__form label:last-of-type { margin-bottom: 10px; }

/* ---------- сканирование в цеху (мобильный экран, без сайдбара) ---------- */

main:has(.scan-screen) {
  max-width: 480px;
  margin: 24px auto 0;
}
.scan-screen input,
.scan-screen select,
.scan-screen button {
  min-height: 44px;
  font-size: 1rem;
}
.scan-screen button[type="submit"] {
  width: 100%;
  margin-top: 10px;
  font-size: 1.05rem;
}
.scan-screen fieldset { margin-bottom: 14px; }
.scan-screen label:has(> input[type="checkbox"]) { min-height: 44px; }

/* Цеховой экран v6 (M16): счёт смены, «на сегодня всё», блоки причин.
   Перенос из макета static/_snapshot/shell-mock-scan.html на токены темы
   этого проекта — собственного визуального языка тут не заводится. */

.scan-screen .tally {
  display: grid;
  grid-template-columns: auto 1fr 1fr;
  gap: 8px;
}
.scan-screen .tally__cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 7px 11px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.scan-screen .tally__cell--plan {
  background: var(--surface-2);
  border-style: dashed;
}
.scan-screen .tally__cell:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.scan-screen .tally__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}
.scan-screen .tally__plan,
.scan-screen .tally__input {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.scan-screen .tally__input {
  min-height: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
}
.scan-screen .tally__input:focus { outline: none; }
.scan-screen .tally__input--defect { color: var(--zone-red); }

.scan-screen .field-note {
  display: block;
  margin-top: 8px;
  color: var(--ink-soft);
  font-size: 12px;
}
.scan-screen .field-note--ok { color: var(--zone-green); font-weight: 600; }

/* «На сегодня по заданию всё» — кнопка, а не тумблер: её надо заметить и
   нажать (решение пользователя). Флажок внутри работает и без скрипта. */
.scan-screen .bigbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 52px;
  margin-top: 10px;
  border: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--ink);
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}
.scan-screen .bigbtn__mark {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border: 2px solid var(--accent);
  border-radius: 6px;
  background: var(--surface);
}
.scan-screen .bigbtn--on { background: var(--accent); color: #fff; }
.scan-screen .bigbtn--on .bigbtn__mark { background: #fff; border-color: #fff; }
.scan-screen .bigbtn--on .bigbtn__mark::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 1px;
  width: 5px;
  height: 11px;
  border: solid var(--accent);
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

.scan-screen .cause {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.scan-screen .cause__label { font-size: 13px; font-weight: 600; }
.scan-screen .cause--plan .cause__label { color: var(--zone-yellow); }
.scan-screen .cause--defect .cause__label { color: var(--zone-red); }

.scan-screen .picker {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.scan-screen .picker > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 12px;
  font-size: 15px;
  list-style: none;
  cursor: pointer;
}
.scan-screen .picker > summary::-webkit-details-marker { display: none; }
.scan-screen .picker__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-soft);
}
.scan-screen .picker__text--on { color: var(--ink); font-weight: 600; }
.scan-screen .picker__chev {
  flex: none;
  width: 8px;
  height: 8px;
  margin-left: auto;
  border: solid var(--ink-soft);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translateY(-3px);
}
.scan-screen .picker[open] .picker__chev { transform: rotate(-135deg) translateY(1px); }
.scan-screen .picker__list {
  max-height: 246px;
  overflow-y: auto;
  border-top: 1px solid var(--border);
}
.scan-screen .opt {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 12px;
  font-size: 15px;
  cursor: pointer;
}
.scan-screen .opt + .opt { border-top: 1px solid var(--border); }
.scan-screen .opt input { flex: none; width: 20px; height: 20px; min-height: 20px; margin: 0; }
.scan-screen .cause textarea {
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: var(--surface);
  resize: vertical;
  min-height: 58px;
}
.scan-screen .cause--plan textarea {
  border-color: var(--zone-yellow);
  background: color-mix(in srgb, var(--zone-yellow) 8%, var(--surface));
}
.scan-screen .cause--defect textarea {
  border-color: var(--zone-red);
  background: color-mix(in srgb, var(--zone-red) 8%, var(--surface));
}

.scan-screen .downtime-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--accent);
  font-size: 14px;
}
.scan-screen .submit__inner { display: flex; flex-direction: column; gap: 6px; }
.scan-screen .submit button[type="submit"]:disabled {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--ink-soft);
  box-shadow: none;
  cursor: not-allowed;
}
.scan-screen .submit__hint {
  text-align: center;
  color: var(--ink-soft);
  font-size: 12px;
}


/* ---------- печать: без сайдбара ---------- */

@media print {
  .nav-toggle, nav, .no-print { display: none !important; }
  body { background: #fff; }
  .sidebar-shell ~ main, main { margin-left: 0 !important; padding: 0 !important; max-width: none; }

  /* Сменный лист: после M17 тикета 141 в свёрнутой таблице десять колонок, и
     три из них — графы под почерк («Сделано», «Брак», «Подпись»), которые
     занимают около 60 мм ширины листа. Решение пользователя 2026-09-21:
     «Уменьшить кегль при печати».

     Мельче становится то, что ЧИТАЮТ, — названия операций и изделий. Графы
     под почерк не трогаем: в них пишут ручкой, и узкая графа неудобна
     сильнее, чем мелкий шрифт. */
  .print-sheet__lines { font-size: 0.82em; }
  .print-sheet__lines th,
  .print-sheet__lines td { padding: 4px 5px; }
  .print-sheet__lines th { font-size: 0.92em; }
  .print-sheet__blank { font-size: 1rem; }
}

/* Каталог номенклатуры (/products): 1218 изделий, два уровня сворачиваемых
   секций — семейство (Знак, ЗИП, МАСКА…) и производственная группа внутри
   него. Разметку строит services/product_nav.py. */
.page-header__count {
  margin-left: 8px;
  color: var(--ink-soft);
  font-size: 0.72em;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.catalog-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}
/* Поиск стал серверным (форма с кнопкой), поэтому растягивается сама форма,
   а поле внутри неё занимает всё, что осталось от кнопки «Найти». */
.catalog-toolbar__search-form {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 320px;
  min-width: 220px;
  margin: 0;
}
.catalog-toolbar__search {
  flex: 1 1 auto;
  min-width: 140px;
}
.catalog-toolbar__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0;
}
.catalog-toolbar__columns {
  position: relative;
  margin-left: auto;
}

.catalog-family {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 8px;
  overflow: hidden;
}
.catalog-family__summary,
.catalog-group__summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  cursor: pointer;
  user-select: none;
}
/* display:flex на <summary> убирает стандартный треугольник, а без него не
   видно, что секцию можно раскрыть -- рисуем свой. */
.catalog-family__summary::marker,
.catalog-group__summary::marker,
.catalog-family__summary::-webkit-details-marker,
.catalog-group__summary::-webkit-details-marker { display: none; }
.catalog-family__summary::before,
.catalog-group__summary::before {
  content: "";
  flex: none;
  align-self: center;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--ink-soft);
  border-bottom: 1.5px solid var(--ink-soft);
  transform: rotate(-45deg);
  transition: transform var(--ease);
}
.catalog-family[open] > .catalog-family__summary::before,
.catalog-group[open] > .catalog-group__summary::before {
  transform: rotate(45deg);
}
.catalog-family__summary {
  padding: 12px 16px;
  font-weight: 600;
}
.catalog-family[open] > .catalog-family__summary {
  border-bottom: 1px solid var(--border);
}
.catalog-family__label {
  font-size: 1.02rem;
}
.catalog-family__meta,
.catalog-group__meta {
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.catalog-group {
  border-top: 1px solid var(--border);
}
.catalog-family > .catalog-group:first-of-type {
  border-top: 0;
}
.catalog-group__summary {
  padding: 9px 16px 9px 28px;
}
.catalog-group__label {
  font-weight: 500;
}
/* Таблица группы уже внутри карточки семейства -- своя рамка ей не нужна,
   иначе на экране три вложенных прямоугольника подряд. */
.catalog-group__card {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  margin: 0 0 6px;
  padding: 0 16px 10px 28px;
  overflow-x: auto;
}

/* ---------- полки готовой продукции: полка, путь, обещано (M17 тикет 04) ---------- */

.shelf-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px;
  margin: 16px 0 24px;
}
.shelf-card {
  margin: 0;
  padding: 16px 18px;
}
.shelf-card__title {
  font-weight: 600;
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-bottom: 12px;
}
/* Три числа врозь, а не одной строкой: «на полке», «в пути» и «обещано» --
   разные вопросы, и слитая строка «160 +480 -320» читается как арифметика,
   которой здесь нет (обещать можно только то, что уже на полке). */
.shelf-card__numbers {
  display: flex;
  gap: 18px;
}
.shelf-card__value {
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.shelf-card__label {
  color: var(--ink-soft);
  font-size: 0.8rem;
  margin-top: 2px;
}
/* Прочерк вместо нуля -- у склада, на котором эту продукцию не держат
   (Ростов). Ноль в ряду чужих сотен читается как «кончилось», прочерк с
   подписью -- как «здесь этого нет и не должно быть». */
.shelf-card__dash {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ink-soft);
  line-height: 1.1;
}

.stock-cell {
  vertical-align: top;
  white-space: nowrap;
}
.stock-cell__value {
  font-size: 1.05rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.stock-cell__value--yellow { color: var(--zone-yellow); }
.stock-cell__value--red { color: var(--zone-red); }
.stock-cell__value--black { color: var(--zone-black); }
.stock-cell__note {
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}
.stock-cell__dash {
  color: var(--ink-soft);
  font-size: 1.05rem;
}

/* ---------- экран «Товар в пути» ---------- */

/* Опаздывающая машина стоит первой и подсвечена: экран отвечает на вопрос
   «что пошло не так», а идущая по сроку вопроса не задаёт. */
.transit-row--late > td {
  background: var(--danger-soft);
}
/* Ячейка действия вертикальная, кнопка фиксированной ширины: роли разной
   длины («кладовщик Крыма», «кладовщик Санкт-Петербурга») иначе сдвигают
   кнопки друг относительно друга -- замечание пользователя по макету
   2026-09-07. */
.transit-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.transit-action__btn {
  width: 176px;
}
.transit-action__role {
  color: var(--ink-soft);
  font-size: 0.78rem;
  text-align: center;
}

/* ---------- экраны буферов: шкала зон, сводка за сутки, лента и полки ---------- */

/* Шкала зон -- те же плитки, что везде, только шире (четыре зоны в ряд) и с
   цветной кромкой: словарь зон общий у экрана срока и экрана запаса, и
   выглядеть он обязан одинаково, хотя меряют они разное. */
.zone-scale { max-width: none; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.zone-tile { border-left: 4px solid var(--zone-color, var(--border)); }
.zone-tile--green { --zone-color: var(--zone-green); }
.zone-tile--yellow { --zone-color: var(--zone-yellow); }
.zone-tile--red { --zone-color: var(--zone-red); }
.zone-tile--black { --zone-color: var(--zone-black); }
.zone-tile .stat-tile__value { color: var(--zone-color, var(--ink)); }
.zone-tile__hint { color: var(--ink-soft); font-size: 0.78rem; margin-top: 6px; }
.zone-tile__delta {
  margin-left: 8px;
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-weight: 600;
}

.zone-word { font-weight: 700; color: var(--zone-color, var(--ink)); white-space: nowrap; }
.zone-word--green { --zone-color: var(--zone-green); }
.zone-word--yellow { --zone-color: var(--zone-yellow); }
.zone-word--red { --zone-color: var(--zone-red); }
.zone-word--black { --zone-color: var(--zone-black); }
@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) .zone-word--black,
  :root:not(.theme-light) .zone-tile--black { --zone-color: #fff; }
}
:root.theme-dark .zone-word--black,
:root.theme-dark .zone-tile--black { --zone-color: #fff; }

/* Заголовок половины экрана. Экран срока читают двое -- начальник
   производства утром и диспетчер весь день, -- и каждая половина подписана
   вслух, чтобы человек понимал, что именно он читает и почему у одной есть
   кнопки, а у другой нет (решение пользователя, M17 тикет 02). */
.section-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin: 28px 0 12px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.section-head h2 { margin: 0; }
.section-head__who { color: var(--ink-soft); font-size: 0.8rem; }

/* Честная пустота: «не смогли посчитать» выглядит иначе, чем «ничего не
   горит», и оба -- иначе, чем таблица. */
.honest-strip {
  border-left: 3px solid var(--zone-yellow);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  color: var(--ink-soft);
  font-size: 0.86rem;
  max-width: 78ch;
}
.honest-strip--calm { border-left-color: var(--zone-green); }

.day-change__list { margin: 8px 0 0; padding-left: 20px; }
.day-change__list li { margin-bottom: 4px; }

/* «Чего ждёт»: причин у наряда бывает несколько сразу, и показываются все
   (M17 тикет 07). Ширина -- как на макете: две-три причины должны помещаться
   в строку, не растягивая таблицу. */
.buffer-feed__waiting { max-width: 300px; }
.reason-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.reason-note {
  display: block;
  margin-top: 4px;
  color: var(--ink-soft);
  font-size: 0.76rem;
}
/* «Не смогли определить» -- ни тревога, ни спокойствие: вопрос к данным. */
.status-pill--unknown {
  background: var(--surface-2);
  color: var(--ink-soft);
  border-style: dashed;
}

.place__op { font-weight: 600; }
.place__where { display: block; color: var(--ink-soft); font-size: 0.8rem; }

.row-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.row-actions .btn { padding: 3px 10px; font-size: 0.78rem; }
/* «ещё» в строке ленты буферов: одна кнопка по причине на виду, остальные
   раскрываются рядом (M17 тикет 172). */
.row-actions__more { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.row-actions__more > summary { list-style: none; cursor: pointer; }
.row-actions__more > summary::-webkit-details-marker { display: none; }

.calm-fold { margin-top: 20px; }
.calm-fold > summary { cursor: pointer; color: var(--ink-soft); font-weight: 600; }

.shelf-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.screen-footnote { margin-top: 24px; max-width: 82ch; font-size: 0.82rem; }

/* ---------- Задания: широкая таблица с блоками (M17 тикет 05) ----------
   Макет `static/_snapshot/shell-mock-jobs.html`: слева то, что видно
   всегда, дальше три блока за серыми полосами -- прессование, расчёт,
   потребность в сырье. Уезжать вправо здесь нормально: это место, где
   сходятся данные наряда и заказа. */
.jobs-table { white-space: nowrap; }
.jobs-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.jobs-table .jobs-sep {
  width: 10px;
  min-width: 10px;
  padding: 0;
  background: var(--surface-2);
}
.jobs-table thead .jobs-groups th {
  text-align: center;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--surface-2);
  color: var(--ink-soft);
}
.jobs-table tr.jobs-day td {
  background: var(--accent-soft);
  font-weight: 600;
}
/* Предложение объединить -- строка над самим заданием, а не значок в
   ячейке: это предложение системы человеку, и оно должно читаться как
   фраза. */
.jobs-table tr.jobs-merge td {
  background: color-mix(in srgb, var(--zone-yellow) 12%, transparent);
}
.merge-offer { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.merge-offer__why { color: var(--ink-soft); white-space: normal; }
.jobs-corr { color: var(--zone-yellow); font-weight: 600; }
.jobs-needs { white-space: normal; min-width: 220px; }
.jobs-presses { color: var(--ink-soft); }
.view-tabs { display: flex; gap: 6px; }
.btn--muted { color: var(--ink-soft); border-style: dashed; cursor: default; }

/* ---------- строка таблицы, требующая действия ----------
   Заказы поставщикам (просрочена поставка) и партии у партнёров (просрочен
   возврат) подсвечиваются одинаково: человек читает «горит» одним и тем же
   цветом, где бы строка ни стояла. */
.row--alert > td { background: var(--danger-soft); }

/* ---------- строка, где действовать уже поздно ----------
   Материал, которого на складе ноль или минус при живом расходе: цеху брать
   нечего сегодня. Решение пользователя 2026-09-17 — «прям красную подсветку,
   чтобы бросалась в глаза», ноль и минус одинаково. Заливка сплошная, а текст
   цветом фона: в тёмной теме --danger светлый, и белым по нему не прочесть. */
.row--critical > td { background: var(--danger); color: var(--surface); }
.row--critical > td a,
.row--critical > td .reason-note,
.row--critical > td .text-alert { color: var(--surface); }
/* Ссылки в проекте без подчёркивания и держатся цветом; на заливке цвет у них
   общий с текстом, и без подчёркивания название материала перестало бы быть
   ссылкой на вид. */
.row--critical > td a { text-decoration: underline; }

/* ---------- Остатки на складах: таблица и колонка объяснений (M17 тикет 20) ----------
   Правая колонка на макете отвечает на два вопроса, которые иначе задают
   голосом: почему в одной таблице три РАЗНЫХ запаса и чего этот экран не
   решает. Ниже 1100 px колонка уходит под таблицу -- на узком экране
   объяснение важно, но не важнее самих сроков. */
.screen-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 24px;
  align-items: start;
}
.screen-split__aside { display: flex; flex-direction: column; gap: 16px; }
.screen-split__aside h3 { margin: 0 0 8px; font-size: 0.95rem; }
.screen-split__aside .card { padding: 14px 16px; }
.aside-links { margin: 0; padding-left: 18px; color: var(--ink-soft); font-size: 0.82rem; }
.aside-links li { margin-bottom: 6px; }
@media (max-width: 1100px) {
  .screen-split { grid-template-columns: 1fr; }
}

/* Предложение по уровню -- карточка, а не строка таблицы: у него есть
   обоснование фразой («риск срабатывал в 46 % проверок»), и в ячейку оно не
   помещается. */
.level-cards { max-width: none; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.level-card__title { font-weight: 600; }
.level-card__value { margin: 6px 0; font-variant-numeric: tabular-nums; }

/* ---------- Очередь ограничения (M17 тикет 25) ----------
   Экран, где действуют: слева порядок и панели действия, справа -- то, что
   объясняет порядок (барабан завода, мощность, чего в очереди нет). Правая
   колонка уезжает под таблицу на узком экране: она объясняет, а не
   управляет. */
.queue-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 20px;
  align-items: start;
}
.queue-layout__main { min-width: 0; }
.queue-layout__aside { display: flex; flex-direction: column; gap: 16px; }
@media (max-width: 1200px) {
  .queue-layout { grid-template-columns: minmax(0, 1fr); }
}

.queue-totals { max-width: none; }

.queue-table { white-space: nowrap; }
.queue-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.queue-table .btn { padding: 3px 10px; font-size: 0.78rem; }
/* «Почему здесь» -- фраза, а не ярлык: ей нужен перенос, иначе она растянет
   таблицу шире экрана и вытолкнет вправо всё остальное. */
.queue-table__why { white-space: normal; max-width: 260px; }
.queue-table__orders { white-space: normal; max-width: 180px; }
/* Замороженная строка приглушена: её порядок уже не решается. */
.queue-row--frozen td { background: var(--surface-2); }

.queue-panel { margin-top: 20px; }
.queue-panel h2 { margin-top: 0; }
.queue-panel__pick { font-weight: 600; }
.queue-panel__reason input { min-width: 260px; }
.queue-merge-table { white-space: normal; }

.queue-preview { margin-top: 14px; }
.queue-preview__head { margin-bottom: 6px; }
.queue-preview__group { margin: 12px 0 6px; color: var(--ink-soft); }
.queue-preview__table { font-size: 0.86rem; }
.queue-preview__table .num { text-align: right; font-variant-numeric: tabular-nums; }

.queue-capacity-table { font-size: 0.82rem; white-space: nowrap; }
.queue-capacity-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.queue-capacity--empty td { color: var(--ink-soft); }
.queue-note h2 { margin-top: 0; font-size: 1rem; }
.queue-note p { font-size: 0.86rem; }

/* ---------- Шаблон справочника (M17 тикет 27) ----------
   Один вид у сорока экранов: список, «где используется», правка в строке,
   добавление снизу. */
.reference-table .reference-usage { white-space: normal; max-width: 320px; color: var(--ink-soft); font-size: 0.86rem; }
/* Пустота, на которую опирается расчёт, -- ошибка, а не приглушённая
   подпись: без навыка в маршруте операции физически некому делать. */
.reference-usage__blocking {
  display: block;
  margin-top: 4px;
  color: var(--danger);
  font-weight: 600;
}
.reference-actions { white-space: nowrap; }
.reference-actions .btn { padding: 3px 10px; font-size: 0.78rem; }
.reference-actions .text-muted { font-size: 0.78rem; margin-left: 6px; }
.reference-row--editing td { background: var(--accent-soft); }
.reference-row-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; }
.reference-row-form label { display: flex; flex-direction: column; gap: 4px; }
.reference-add { margin-top: 16px; }
.reference-add > summary {
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  padding: 6px 0;
}
.reference-add__body {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-top: 8px;
  max-width: 640px;
}

/* ---------- Очередь пополнения: два способа в одной строке (M17 тикет 26) ----------
   Просевшая полка -- одна строка, и в ней рядом стоят «сделать самим» и
   «привезти» со своими сроками и своими формами. Ячейки переносят текст:
   недоступный способ объясняет себя фразой, а не прочерком. */
.queue-table td { vertical-align: top; white-space: normal; }
.queue-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.queue-table__consequence { color: var(--ink-soft); font-size: 0.82rem; max-width: 260px; }
.way-form { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-top: 6px; }
.way-form input[type="number"] { width: 5.5rem; }
.way-form input[type="text"] { min-width: 8rem; flex: 1 1 8rem; }
.way-form input[type="date"] { width: 9.5rem; }
.way-form .btn { padding: 3px 10px; font-size: 0.78rem; }
.reference-weekdays { display: flex; flex-direction: column; gap: 4px; font-size: 0.86rem; }
.reference-weekdays label { flex-direction: row; align-items: center; gap: 4px; }

/* ---------- Шаблон журнала по датам (M17 тикет 27) ----------
   Третий вид шаблона справочника: записи за период, а не сущности. Сверху
   фильтр периода, внутри строки с датами, вместо «где используется» --
   «на что влияет». Оформление намеренно то же, что у справочника: человек
   привыкает к одному экрану, а не к двум похожим. */
.journal-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 12px;
}
.journal-filter label { display: flex; flex-direction: column; gap: 4px; font-size: 0.86rem; }
.journal-effect { white-space: normal; max-width: 340px; color: var(--ink-soft); font-size: 0.86rem; }
/* Пустой журнал -- живой случай (в реальной базе отсутствий и расходов ноль),
   и он обязан объяснять, а не показывать пустую таблицу. */
.journal-empty { max-width: 720px; }
.journal-empty p { margin: 0 0 8px; }
.journal-empty p:last-child { margin-bottom: 0; }
.journal-empty__consequence { color: var(--danger); }
.screen-lead { max-width: 780px; color: var(--ink-soft); font-size: 0.9rem; }

/* ---------- Справочник со вложенным составом (M17 тикет 27) ----------
   Отличие этого вида справочника от простого списка ровно одно: состав
   раскрывается ПРЯМО В СТРОКЕ, не уводя на другую страницу. Поэтому всё
   ниже -- про то, чтобы раскрытый состав читался внутри ячейки таблицы, а
   не растягивал её. */
.route-composition-cell { white-space: normal; max-width: 480px; }
.route-composition > summary { cursor: pointer; color: var(--accent); font-size: 0.86rem; }
.route-composition__list { list-style: none; margin: 8px 0 4px; padding: 0; font-size: 0.86rem; }
.route-composition__node { margin-bottom: 6px; }
.route-composition__node-name { font-weight: 600; }
.route-composition__ops { margin: 2px 0 0; padding-left: 20px; color: var(--ink-soft); }
/* Состав операции отдельной строкой под ней самой: своя подложка, чтобы
   строка состава не читалась как ещё одна операция маршрута. */
.route-op-composition > td { background: var(--surface-2); padding-left: 32px; }
.route-op-composition summary { cursor: pointer; color: var(--accent); font-size: 0.82rem; }
.route-op-composition__table { font-size: 0.82rem; margin-top: 6px; }
.route-op-composition__table td { padding: 2px 12px 2px 0; border: 0; }
/* Ресурс: график смен и формы -- то, из чего он состоит для планировщика. */
.resource-composition { white-space: normal; max-width: 320px; font-size: 0.86rem; }
.resource-composition > summary { cursor: pointer; color: var(--accent); }
.resource-composition ul { list-style: none; margin: 6px 0 0; padding: 0; color: var(--ink-soft); }

/* ---------- Материалы и поставщики (M17 тикет 28) ----------
   Слева список материалов, справа поставщики выбранного. Две колонки, а не
   карточка на материал: снабженец спрашивает «у кого взять», и заходить ради
   ответа в карточку каждого материала -- мучение. Ниже 1100 px список уходит
   наверх, а поставщики под него: на узком экране порядок «сначала выбрал,
   потом смотрю» сохраняется сам собой. */
.mat-split {
  display: grid;
  grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
@media (max-width: 1100px) {
  .mat-split { grid-template-columns: minmax(0, 1fr); }
}
/* «Расходуем, а взять негде» (m17/87): короткий список над двумя колонками.
   Строка в одну линию -- код, название, причина, -- чтобы три-семь материалов
   читались одним взглядом, а не разворачивались в отдельную таблицу. */
.mat-unbuyable { margin: 8px 0 0; padding-left: 18px; }
.mat-unbuyable li { margin-bottom: 4px; }
.mat-unbuyable .reference-usage__blocking { display: inline; margin-left: 6px; }
.mat-list table { font-size: 0.86rem; }
.mat-list .num { text-align: right; font-variant-numeric: tabular-nums; }
/* Выбранная строка подсвечена той же подложкой, что и строка в правке: это
   одно и то же состояние «сейчас работаем вот с этой строкой». */
.mat-row--selected > td { background: var(--accent-soft); }
.mat-detail { display: flex; flex-direction: column; gap: 16px; }
.mat-detail .num { text-align: right; font-variant-numeric: tabular-nums; }
.mat-substitutes h3 { margin-top: 0; font-size: 0.95rem; }
/* Флажок «основной» в форме строки: остальные подписи в ней стоят колонкой
   (поле под названием), а у флажка название обязано стоять рядом. */
.reference-row-form label.checkbox { flex-direction: row; align-items: center; gap: 6px; }
/* Архив -- не удаление: строка остаётся видимой, но приглушённой, потому что
   в подсказках, расчётах и выборе поставщика её уже нет. */
.row--archived > td { color: var(--ink-soft); }
.badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-soft);
  font-size: 0.72rem;
  vertical-align: middle;
}

/* Цеховой экран: почему знакомой фамилии нет в списке (M17 тикет 38).
   Крупно, как всё на этом экране, -- его читают у станка. */
.scan-note {
  margin: 8px 0 0;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--surface-alt, var(--surface));
  border: 1px solid var(--border);
  color: var(--ink-soft);
  font-size: 1rem;
}

/* ---------- Буферы полуфабрикатов (M17 тикет 43) ----------
   Список позиций участка в форме заведения буфера: у оклейки их 547, поэтому
   он прокручивается внутри себя, а не растягивает страницу на три экрана.
   Сузить его помогает поиск над списком -- пользователь делит позиции на
   «основы» и «маски» именно по названию. */
.stock-buffer-picks { grid-column: 1 / -1; }
.stock-buffer-picks__list {
  list-style: none;
  margin: 8px 0 0;
  padding: 8px;
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
}
.stock-buffer-picks__list li { margin-bottom: 2px; }
.stock-buffer-picks__list label { display: flex; gap: 8px; align-items: baseline; }

/* ---------- Проверки целостности настроек (M17 тикет 86) ----------
   Полоса вверху затронутого экрана: «у 9 материалов нет поставщика». Стоит
   между заголовком и содержимым, читается за один взгляд и ничего не
   требует -- ни одна проверка не блокирует сохранение (решение пользователя
   2026-09-09, вопрос 2). */
.integrity-strip {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0 0 18px;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--zone-red);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.integrity-strip__icon .icon { color: var(--zone-red); }
.integrity-strip__body { flex: 1 1 auto; }
.integrity-strip__line { margin: 0; }
.integrity-strip__line + .integrity-strip__line { margin-top: 4px; }
.integrity-strip__all { white-space: nowrap; align-self: center; }

/* Экран общей картины. */
.integrity-lede { max-width: 78ch; color: var(--ink-soft); }
.integrity-check { border-left: 3px solid var(--zone-red); }
/* Зелёная секция нарисована намеренно (решение 4, «пусть видят, что всё
   хорошо»): без неё непонятно, проверка ничего не нашла или её вообще нет. */
.integrity-check--ok { border-left-color: var(--zone-green); }
.integrity-check__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 12px;
}
.integrity-check__mark .icon { color: var(--zone-red); }
.integrity-check--ok .integrity-check__mark .icon { color: var(--zone-green); }
.integrity-check__title { margin: 0; font-size: 1rem; }
.integrity-check__found { font-weight: 700; color: var(--zone-red); }
.integrity-check--ok .integrity-check__found { color: var(--zone-green); }
.integrity-check__area {
  margin-left: auto;
  color: var(--ink-soft);
  font-size: 0.78rem;
  white-space: nowrap;
}
.integrity-check__why { margin: 6px 0 0; max-width: 82ch; color: var(--ink-soft); }
.integrity-check__samples {
  margin: 8px 0 0;
  padding-left: 18px;
  color: var(--ink-soft);
  font-size: 0.86rem;
}
.integrity-check__fix { margin: 10px 0 0; }

/* ---------- Регламенты обслуживания и задания на ТО (M17 тикет 35) ----------
   Кнопки, которые что-то заводят или меняют, красятся акцентом (обычный
   <button>); белыми (.btn) остаются те, что ничего не меняют: листание
   календаря, «Показать», печать. Решение пользователя 2026-09-09. */
.maint-new { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.maint-new input[type="text"] { width: auto; min-width: 220px; margin: 0; }
.maint-new select { width: auto; margin: 0; }
.maint-count { margin-left: auto; color: var(--ink-soft); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.maint-row { cursor: pointer; }
.maint-row:hover > td { background: var(--surface-2); }

/* Полоса из шести полей — весь регламент одним взглядом. */
.maint-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px 24px; }
.maint-fact { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.maint-fact__label { color: var(--ink-soft); font-size: 0.72rem; letter-spacing: 0.03em; text-transform: uppercase; }
.maint-fact__value { font-weight: 600; }

.maint-qr { display: flex; align-items: center; gap: 12px; color: var(--ink-soft); font-size: 0.8rem; }
.maint-qr img { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 4px; }
.maint-qr code { font-size: 0.72rem; word-break: break-all; }

/* Четыре вида запуска плитками: выбранный отмечен, у каждого написано, что
   именно система считает. */
.maint-triggers { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin-bottom: 14px; }
.maint-trigger {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.maint-trigger--on { border-color: var(--accent); background: var(--accent-soft); }
.maint-trigger__title { font-weight: 600; }
.maint-trigger__count { color: var(--ink-soft); font-size: 0.75rem; }

.maint-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 4px 18px; align-items: end; }
.maint-grid label.checkbox { display: flex; flex-direction: row; align-items: center; gap: 6px; }
.maint-objects { max-height: 260px; overflow-y: auto; }
.maint-objects label.checkbox { display: flex; flex-direction: row; align-items: center; gap: 6px; margin: 0 0 2px; }

.maint-work-row__handle { cursor: grab; color: var(--ink-soft); user-select: none; }
.maint-work-form { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; }
.maint-work-form input[type="text"] { width: auto; min-width: 200px; flex: 1 1 220px; margin: 0; }
.maint-work-form input[type="number"] { width: 90px; margin: 0; }
.maint-work-form label.checkbox { display: flex; flex-direction: row; align-items: center; gap: 5px; margin: 0; white-space: nowrap; }
.maint-work-add summary { cursor: pointer; color: var(--accent); font-weight: 600; }
.maint-mark-form { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; }
.maint-mark-form input[type="number"] { width: 90px; margin: 0; }
.maint-mark-form input[type="text"] { width: auto; min-width: 120px; margin: 0; }
.maint-mark-form select { width: auto; margin: 0; }
.maint-mark-form label.checkbox { display: flex; flex-direction: row; align-items: center; gap: 5px; margin: 0; white-space: nowrap; }
.maint-work--done > td { background: color-mix(in srgb, var(--zone-green) 8%, transparent); }
.maint-plan-form { display: flex; align-items: center; gap: 6px; margin: 0; }
.maint-plan-form input[type="date"] { width: auto; margin: 0; }

/* Календарь обслуживаний: сетка месяца, в клетке дня плашки в четырёх
   состояниях. Пятого нет — отменить ТО нельзя. */
.maint-calnav { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.maint-calnav__form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; }
.maint-calnav__form input[type="date"], .maint-calnav__form select { width: auto; margin: 0; }
.maint-cal__title { margin: 0 0 8px; font-size: 1rem; }
.maint-cal { table-layout: fixed; }
.maint-cal th { text-align: center; }
.maint-cal__cell { vertical-align: top; height: 78px; padding: 4px 5px; }
.maint-cal__cell--outside { background: var(--surface-2); }
.maint-cal__cell--outside .maint-cal__day { opacity: 0.35; }
.maint-cal__cell--today { outline: 2px solid var(--accent); outline-offset: -2px; }
.maint-cal__day { display: block; color: var(--ink-soft); font-size: 0.72rem; font-variant-numeric: tabular-nums; }

.maint-chip {
  display: block;
  margin-top: 3px;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink-soft);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-decoration: none;
}
.maint-chip:hover { text-decoration: none; opacity: 0.85; }
.maint-chip--done { background: color-mix(in srgb, var(--zone-green) 18%, transparent); color: var(--zone-green); border-color: transparent; }
.maint-chip--partial { background: color-mix(in srgb, var(--zone-yellow) 22%, transparent); color: var(--zone-yellow); border-color: transparent; }
.maint-chip--missed { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.maint-chip--planned { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.maint-legend { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.maint-legend .maint-chip { display: inline-block; margin: 0; }
.maint-rebuild { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* ---------- переключатель площадки (M17 тикет 58) ----------
   Одно начертание на всю систему: сегменты с кодами площадок, «Все» первым,
   в шапке экрана справа. Разметка — templates/_plant/switcher.html. */

.plant-switch {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  align-self: center;
}
.plant-switch__seg {
  padding: 5px 12px;
  color: var(--ink-soft);
  text-decoration: none;
  background: var(--surface);
  border-right: 1px solid var(--border);
  transition: background var(--ease), color var(--ease);
}
.plant-switch__seg:last-child { border-right: 0; }
.plant-switch__seg:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.plant-switch__seg.is-current { background: var(--accent); color: #fff; }
.plant-switch__seg.is-current:hover { background: var(--accent); color: #fff; }

/* Состояние 3: у человека одна площадка — переключателя нет вовсе, а не
   «серый неактивный». Сегмент из одной кнопки это шум. */
.plant-switch--single {
  border-color: transparent;
  background: var(--surface-2);
  color: var(--ink-soft);
  padding: 5px 12px;
  font-weight: 500;
}
/* Состояние 4: площадок больше порога — сегменты становятся списком. */
.plant-switch--list { border: 0; }
.plant-switch--list select { width: auto; min-width: 170px; margin: 0; }

.plant-caption { white-space: nowrap; }

/* Пара «откуда → куда» на «Товаре в пути» — единственное место, где
   переключателей площадки два (M17 тикет 58). Обведена акцентом, чтобы
   читалась как один вопрос, а не два соседних фильтра. */
.transit-pair {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 14px 0 18px;
  padding: 10px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent-soft);
}
.transit-pair__side { display: inline-flex; align-items: center; gap: 8px; }
.transit-pair__label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.transit-pair__arrow { color: var(--accent); font-size: 1.1rem; }
.transit-pair__why { flex: 1 1 260px; min-width: 220px; font-size: 0.8rem; }

/* Кабинет закупок (M17 тикет 59). Плитка площадки без единой строки гасится:
   «закупок нет» — ответ, и он не должен спорить за внимание с настоящими
   числами. Подсказка «можно не покупать» живёт строкой под своей позицией. */
.stat-tile--muted { opacity: 0.55; }
.purchasing-hint > td {
  background: var(--accent-soft);
  font-size: 0.85rem;
  white-space: normal;
}

/* Плитка, на которую надо смотреть: главный ответ аналитики знаков — во
   сколько позиций сворачиваются полторы тысячи знаков (M17 тикет 55). */
.stat-tile--accent { border-color: var(--accent); }

/* ---------- заказы: список (M17 тикет 69) ---------- */

/* Плиток шесть, а не четыре -- полосе нужна вся ширина экрана. */
.stat-tiles--wide { max-width: none; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.stat-tile--link { display: block; text-decoration: none; }
.stat-tile--link:hover { border-color: var(--accent); text-decoration: none; }
.stat-tile--active { border-color: var(--accent); background: var(--accent-soft); }
.stat-tile__note { font-size: 0.78rem; }

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.85rem;
}
.filter-chip a { color: inherit; font-size: 1.05rem; line-height: 1; text-decoration: none; }

.orders-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin: 12px 0; }
.orders-sort { color: var(--ink-soft); font-size: 0.85rem; }
.orders-sort__item { margin-left: 8px; }
.orders-sort__item--active { font-weight: 600; color: var(--ink); text-decoration: none; cursor: default; }

.orders-table { width: 100%; }
.orders-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.orders-table tbody { border: 0; }
.order-row__pick { width: 28px; }
.order-row__no a { font-weight: 600; }
/* Внутренний номер уходит под номер счёта — колонка узкая, в строку не
   помещаются оба. */
.orders-table .entity-no__internal { display: block; font-size: 0.78rem; color: var(--ink-soft); }
.order-row__inner { display: block; font-size: 0.78rem; }
.order-row__state { display: table-cell; }
.order-row__state .status-pill { margin: 0 4px 2px 0; }
.order-row--hot td { background: color-mix(in srgb, var(--danger) 4%, transparent); }
.order-row__action { white-space: nowrap; }
.btn--small { padding: 4px 10px; font-size: 0.8rem; }
.text-danger { color: var(--danger); }

/* Форма продления живёт в строке под заказом -- отдельной страницы у неё
   нет: человек разгребает резервы подряд и не должен терять список. */
.order-fix td { background: var(--accent-soft); }
.order-fix__lead { margin: 0 0 8px; color: var(--ink-soft); font-size: 0.85rem; }
.order-fix__fields { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.order-fix__reason { flex: 1 1 320px; }
.order-fix__reason input { width: 100%; }
.order-fix__buttons { display: flex; gap: 8px; margin-top: 10px; }

.orders-bulk {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin-top: 16px;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.orders-bulk__count { align-self: center; }
.orders-bulk__reason { flex: 1 1 280px; }
.orders-bulk__reason input { width: 100%; }

/* Блок объяснений под списком: что означают слова экрана. Кадр называет
   его «СПРАВКА» -- нижняя часть страницы, читают её один раз. */
.screen-help { margin-top: 32px; border-top: 1px solid var(--border); padding-top: 16px; color: var(--ink-soft); }
.screen-help h2 { font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
.screen-help p { font-size: 0.85rem; max-width: 70ch; }

/* Карточка заказа (M17 тикет 70) ------------------------------------- */

/* Полоса разрыва срока -- первое, что видно на карточке. Жёлтая, когда
   план позже обещанного, красная -- когда обещание уже недостижимо: ответ
   клиенту в этих двух случаях разный. */
.deadline-gap {
  border-left: 3px solid var(--zone-yellow);
  background: var(--surface-2);
  border-radius: 0 10px 10px 0;
  padding: 12px 16px;
  margin: 16px 0;
}
.deadline-gap--unreachable { border-left-color: var(--danger); background: var(--danger-soft); }
.deadline-gap__lead { margin: 0; }
.deadline-gap__holder { margin: 8px 0 0; font-size: 0.9rem; color: var(--ink-soft); }

/* Строка состава, которой определяется срок всего заказа. Подсветка
   слабая намеренно: это не тревога, а ответ на вопрос «кто виноват». */
.line-row--holds-deadline > td { background: color-mix(in srgb, var(--zone-yellow) 8%, transparent); }
.line-row__holds { display: block; font-size: 0.75rem; color: var(--zone-yellow); }
.line-row__ready { display: block; font-weight: 600; }
.line-row__wo { margin-top: 4px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Форма отмены позиции красится в цвет своей строки и получает «↳» в
   заголовке: белая форма между строками читалась как вторая таблица
   («снизу ты добавил ещё одну таблицу… что это за таблица?»). */
.line-fix--holds-deadline td { background: color-mix(in srgb, var(--zone-yellow) 12%, transparent); }

/* Режим отгрузки: два сегмента вместо галочки. */
.segmented { display: inline-flex; border: 1px solid var(--border-strong); border-radius: 8px; overflow: hidden; }
.segmented__item {
  border: 0;
  background: var(--surface);
  color: var(--ink-soft);
  padding: 7px 16px;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.segmented__item + .segmented__item { border-left: 1px solid var(--border-strong); }
.segmented__item:hover { background: var(--surface-2); color: var(--ink); }
.segmented__item--on { background: var(--accent); color: #fff; }
.segmented__item--on:hover { background: var(--accent); color: #fff; }
.ship-mode .text-muted { margin-bottom: 0; }

/* Отгрузки и история срока -- рядом, а не одна под другой: обе короткие
   и обе про прошлое. */
.order-panes { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px; }
@media (min-width: 1100px) { .order-panes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.order-panes__pane h2 { margin-top: 24px; }

/* Шесть форм действий карточками, а не простынёй заголовков. Колонки заданы
   числом, а не auto-fit: на кадре тикета 70 это два ряда по три, и auto-fit
   на широком экране разложил бы их в четыре и три — шесть карточек в такой
   сетке дают ряд из четырёх и висящий ряд из двух. */
.action-cards { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 900px) { .action-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1280px) { .action-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.action-card h3 { margin-top: 0; }
.action-card .text-muted { font-size: 0.82rem; }
.action-card--danger { border-color: var(--danger); }
.action-card--danger h3 { color: var(--danger); }
.action-card--danger form + form { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }
.action-card button[disabled] { opacity: 0.5; cursor: not-allowed; }

/* Журнал отгрузок (M17 тикет 71) ------------------------------------- */

/* Фильтры двумя строками: период и даты сверху, склад с поиском снизу.
   Одной строкой они не помещаются, а в столбик занимают пол-экрана. */
.history-filters { display: flex; flex-direction: column; gap: 12px; margin: 16px 0; }
.history-filters__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }

/* Раскрытый состав красится в цвет своей строки: белая вставка между
   строками читается как вторая таблица (разбор кадра тикета 70). */
.shipment--open > tr > td { background: var(--accent-soft); }
.shipment__action { white-space: nowrap; }
.shipment-lines > td { background: var(--accent-soft); }
.shipment-lines__lead { margin: 0 0 8px; color: var(--ink-soft); font-size: 0.85rem; }
.shipment-lines__table { width: auto; min-width: 420px; background: var(--surface); }
.shipment-lines__table th { font-size: 0.75rem; }
.shipment-lines__tail > td { padding: 0; }
.shipment-lines__tail summary {
  cursor: pointer;
  padding: 8px 12px;
  color: var(--ink-soft);
  font-size: 0.85rem;
}
.shipment-lines__tail summary:hover { color: var(--ink); }
.shipment-lines__tail .shipment-lines__table { min-width: 0; width: 100%; }

/* ---------- инвентаризация: плитки листа пересчёта (M17 тикет 77) ---------- */

/* Рамкой, а не только цветом цифры: «расхождений» и «не считали» -- два разных
   повода вернуться к листу, и оба обведены на кадре. Цифру `--danger` красит
   и так, здесь к ней добавляется рамка того же цвета; «не считали» жёлтая
   целиком -- это не ошибка, а незаконченная работа. */
.stat-tile--framed-danger { border-color: var(--danger); }
.stat-tile--framed-warning { border-color: var(--zone-yellow); }
.stat-tile--framed-warning .stat-tile__value { color: var(--zone-yellow); }

/* ---------- пересчёт в руках: телефонный экран (M17 тикет 79) ---------- */

/* Узкую колонку и крупные цели нажатия даёт `.scan-screen` цехового
   терминала — своего мобильного языка тут не заводится. Ниже только то,
   чего у цехового экрана нет: список позиций строками-ссылками, прогресс
   обхода и крупное поле под факт. */

.count-phone__where { color: var(--ink-soft); font-size: 0.9rem; }
.count-phone__back { display: inline-block; margin-bottom: 8px; }
.count-phone__progress { margin: 12px 0; }
.count-phone__progress-line { font-weight: 600; }
.count-phone__progress-note { color: var(--ink-soft); font-size: 0.9rem; margin-top: 4px; }
.count-phone__bar {
  height: 8px;
  margin-top: 6px;
  border-radius: 4px;
  background: var(--surface-2);
  overflow: hidden;
}
.count-phone__bar-fill { height: 100%; background: var(--accent); }

.count-phone__search { display: flex; gap: 8px; margin-bottom: 8px; }
.count-phone__search input { flex: 1; min-width: 0; }
.count-phone__found { color: var(--ink-soft); font-size: 0.9rem; margin-bottom: 8px; }

.count-phone__row {
  display: block;
  padding: 10px 12px;
  margin-bottom: 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
}
.count-phone__row:focus-visible { outline: 2px solid var(--accent); }
.count-phone__row-title { font-weight: 600; line-height: 1.25; }
.count-phone__row-sub { color: var(--ink-soft); font-size: 0.82rem; margin-top: 2px; }
.count-phone__cells {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 8px;
  align-items: end;
  margin-top: 8px;
}
.count-phone__cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.count-phone__cell--status { align-items: flex-end; }
.count-phone__cell-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.count-phone__cell-value { font-size: 1.05rem; font-weight: 600; font-variant-numeric: tabular-nums; }

.count-phone__field { display: block; margin-bottom: 12px; }
.count-phone__qty { font-size: 26px; font-weight: 700; min-height: 58px; text-align: right; }
.count-phone__difference { margin: -4px 0 12px; min-height: 1.2em; }
.count-phone__cancel { display: inline-block; margin-top: 8px; }
.count-phone__hint { font-size: 0.85rem; margin-top: 10px; }
.count-phone__submit { margin-top: 16px; }
.count-phone__big-sheet { margin-top: 20px; font-size: 0.9rem; color: var(--ink-soft); }

/* Приглашение на телефон — на большом листе, не на телефоне. */
.count-phone-invite {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  max-width: 74ch;
}
.count-phone-invite__qr { flex: none; }

/* Сменное задание — лист станка (/print/{resource_id}, M17 тикет 97).
   Это документ, который цех сдаёт обратно с подписью и рукописными
   замечаниями, поэтому пустые графы должны быть достаточно высокими, чтобы
   в них поместился почерк, а границы — видимыми на бумаге. */
.print-sheet__head h1 { margin-bottom: 6px; }

.print-sheet__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 28px;
  margin: 0 0 18px;
}
.print-sheet__meta div { display: flex; gap: 6px; align-items: baseline; }
.print-sheet__meta dt { color: var(--ink-soft); font-size: 0.85em; }
.print-sheet__meta dd { margin: 0; font-weight: 600; }

.print-sheet__switch { margin: -10px 0 16px; }

.print-sheet__empty {
  padding: 18px;
  border: 1px dashed var(--line);
  color: var(--ink-soft);
}

.print-sheet__job {
  border: 1px solid var(--ink);
  padding: 12px 14px;
  margin-bottom: 14px;
  page-break-inside: avoid;
}
.print-sheet__job-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.print-sheet__job-head h2 { margin: 0; font-size: 1.05em; }
.print-sheet__job-head p { margin: 0; }

.print-sheet__lines { width: 100%; border-collapse: collapse; margin: 10px 0; }
.print-sheet__lines th,
.print-sheet__lines td {
  border: 1px solid var(--line);
  padding: 5px 8px;
  text-align: left;
}
.print-sheet__lines th { font-size: 0.8em; color: var(--ink-soft); font-weight: 600; }
.print-sheet__lines .num { text-align: right; font-variant-numeric: tabular-nums; }
/* Под почерк: графы «Сделано», «Брак» и «Подпись» рабочий заполняет ручкой. */
.print-sheet__blank { min-width: 90px; height: 30px; }
/* «Брак» уже соседей: цифра там короткая, а чаще графа остаётся пустой —
   полные 90 мм отнимали бы место у названия операции (M17 тикет 141). */
.print-sheet__blank--narrow { min-width: 52px; }

/* Номер однострочного задания стоит колонкой, а не заголовком: подряд идущие
   задания печатаются одной таблицей под одной шапкой. Номер не должен
   переноситься посреди цифр. */
.print-sheet__job-no { white-space: nowrap; font-weight: 600; }

.print-sheet__code { display: flex; align-items: center; gap: 10px; }
.print-sheet__url { font-size: 0.7em; color: var(--ink-soft); word-break: break-all; }

/* Один код на весь лист (M18 тикет 05): по нему открывается страница, где
   отмечают все строки разом. Стоит над блоком сдачи смены и на печать
   уходит -- это рабочий инструмент цеха, а не экранная подсказка. */
.print-sheet__scan {
  display: flex;
  gap: 14px;
  align-items: center;
  margin-top: 20px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  page-break-inside: avoid;
}
.print-sheet__scan-title { margin: 0 0 2px; font-weight: 600; }
.print-sheet__scan p { margin: 0 0 4px; }
.print-sheet__signoff { margin-top: 22px; page-break-inside: avoid; }
.print-sheet__signoff h2 { font-size: 1em; margin-bottom: 8px; }
.print-sheet__label { margin: 0 0 4px; color: var(--ink-soft); font-size: 0.85em; }
.print-sheet__ruled { border-bottom: 1px solid var(--line); height: 26px; }
.print-sheet__signatures { display: flex; gap: 36px; flex-wrap: wrap; margin-top: 18px; }

/* «Нет заготовки» в строке листа (M17 тикет 99, пункт 3). Решение
   пользователя: «Ставить, но помечать» — строка остаётся, но подписана.
   Рамкой, а не только цветом: лист печатают на чёрно-белом принтере. */
.print-sheet__starving {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border: 1px solid var(--ink-soft);
  border-radius: 3px;
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Все листы площадки одной кнопкой (M18 тикет 03).
   Каждый лист — со своей страницы: цех разбирает пачку по станкам, и два
   участка на одном листе пришлось бы разрезать ножницами. */
.print-all__sheet + .print-all__sheet { page-break-before: always; break-before: page; }
.print-all__head { margin-bottom: 20px; }
.print-all__filters { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; margin: 12px 0; }
.print-all__filters label { display: flex; flex-direction: column; gap: 4px; font-size: 0.85em; color: var(--ink-soft); }
.print-all__summary { margin: 8px 0; }
.print-all__list { margin: 0 0 12px; line-height: 1.8; }
.print-sheet__nav { margin-bottom: 12px; }


/* ---------- «считать не из чего» (M17 тикет 133) ---------- */

/* Фраза на месте числа: серая и не жирная, чтобы не спорить с посчитанными
   числами за внимание. Живёт и в ячейке таблицы, и в плитке. */
.uncounted { color: var(--ink-soft); font-weight: 400; font-size: 0.85rem; white-space: normal; }
/* Плитка без числа не красится: рамка пунктиром, фраза вместо числа, под ней
   словами -- чего не хватает. Правило стоит в конце файла, чтобы перебить
   цвет числа у зонных плиток (`.zone-tile .stat-tile__value`). */
.stat-tile--uncounted { border-style: dashed; box-shadow: none; }
.stat-tile--uncounted .stat-tile__value { font-size: 1.05rem; font-weight: 600; color: var(--ink-soft); }
.stat-tile__hint { color: var(--ink-soft); font-size: 0.78rem; margin-top: 6px; }
