/* АРМ настройки шасси 2.0 — стили страницы. Тема потребителя; общий
   график shared/ui-kit не перекрашивается.

   Раскладка рабочего экрана — сетка без наложений: шапка / (рельс · работа ·
   выдвижная панель), работа = лист (прокручивается) / подвал / график
   (дорожка заданной высоты). Ничто не стоит поверх содержимого, поэтому
   подвал не может забрать щелчок, а высота графика не пляшет от шага к шагу. */
/* Тема — общая с АРМ первичной настройки (макет lis02_maket): наладчик
   работает за обоими АРМ подряд, и они обязаны читаться как один прибор.
   Граница правки жёсткая: тема красит ХРОМ (текст, фон, рамки, шапку,
   главную кнопку), а цвета ВЕРДИКТОВ — свои и не трогаются. Иначе фирменный
   зелёный оформления перестанет отличаться от зелёного «годен», и наладчик
   потеряет единственный быстрый признак годности кадра.
   Геометрия здесь не меняется ни на пиксель: числа §5
   docs/ui-usability-2026-09-06.md держатся высотами и отступами, а не цветом. */
:root {
  /* Три ступени серого сдвинуты в тот же бирюзовый ряд, но НЕ приравнены к
     чужим: у первичной настройки приглушённый текст даёт на её же фоне 4,33 —
     ниже нормы 4,5. Наш --ink-2 темнее и держит 5,43; --ink-3 — их значение,
     оно приходится на третью ступень и норму держит. */
  --ink: #243a3d; --ink-2: #4e6669; --ink-3: #5f7578;
  --bg: #eef2f1; --paper: #ffffff; --line: #cbd7d5; --line-2: #e2e9e7;
  --accent: #1f4c53; --accent-bg: #dfeaec; --accent-hover: #164046;
  /* Шапка — тёмная плашка первичной настройки. Своя пара «фон — текст»:
     на ней стоят органы, и общие --ink/--paper на ней нечитаемы. */
  --head-bg: #2a5a60; --head-text: #ffffff; --head-muted: #b9cdcb; --head-line: #1b464b;
  /* Вердикты — не тема. Не перекрашивать вместе с хромом. */
  --ok: #1a7f37; --ok-bg: #e6f4ea; --bad: #b42318; --bad-bg: #fdecea; --warn: #9a6700; --warn-bg: #fff8e1;
  /* «Переснимите / сделайте действие» — оранжевый, как у мастера (`wizard.css`,
     решение tech lead 14.10.2026): цвет называет сервер (`Verdict.tone=act`). */
  --act: #8a4b00; --act-bg: #FDEBD3;
  /* Синий — третье состояние ПОЛЯ набора выстрела («применено и подтверждено
     прибором», 20.09.2026). Не вердикт и не хром темы: свой цвет, чтобы не
     спорить ни с зелёным «годен», ни с бирюзовым оформления. */
  --pset: #0b5cad; --pset-bg: #e7f0fb;
  /* Значок ГРУППЫ набора выстрела (22.09.2026): цветные — свой оттенок, не
     вердикт и не хром темы, чтобы «другой набор» не читалось как «годен»
     или «брак». Чёрные — серый, как «нет вердикта». */
  --group: #5b3a8a; --group-bg: #ece4f6;
  --hit: 40px; --radius: 8px; --fs: 15px;
  --rail-w: 300px; --chart-h: 344px; --canvas-h: 240px; --drawer-w: 380px; --help-w: 400px;
  /* Ширина колонки «делаем» в режиме настройки: текущий пункт читается
     строками по 40–45 знаков, кадры шага — одной строкой каждый. Пределы —
     для ручки на кромке: уже 300 px не помещается лестница сталей и сетка
     серии, шире 720 px спектру остаётся меньше половины поста 1366 px. */
  --do-w: 380px; --do-min: 300px; --do-max: 720px;
  /* Сколько в панели графика занято всем, кроме канвы: полоса органов и поля.
     Отсюда высота канвы считается от высоты панели, когда наладчик тянет её
     ручкой (`ctx.chartHeight`), — иначе панель росла бы вокруг канвы прежнего
     размера. Число не новое: это разница готовых размеров, 344 − 240 = 564 − 460. */
  --chart-chrome: 104px;
  /* Пределы натянутой ручкой высоты. Ниже 180 px панель перестаёт быть панелью:
     полоса органов 56 px и канва, на которой уже не видно формы линии. Выше
     60vh лист теряет работу — на 1366×768 это 460 px, и листу остаётся шапка
     шага с двумя строками чек-листа (столько же давал снятый 15.09.2026
     готовый размер «Крупнее» — 462 px).
     Пределы живут ЗДЕСЬ, а не в браузерном коде: в память поста уходит
     то, что реально встало. */
  --chart-min: 180px; --chart-max: 60vh;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; }
/* Колонка страницы (16.10.2026): общая шапка служб (#libs-header с полосой MES)
   стоит над #app, а #app был высотой в окно (100vh) — низ каждого экрана
   (на 1366×768 — 79 px: «Спектр ▸», «Служебное ▸», подвал) уезжал за край. Теперь
   #app берёт остаток высоты под шапкой. */
body { display: flex; flex-direction: column; }
#libs-header { flex: none; }
/* Семейство названо тем же первым именем, что у первичной настройки. На посту
   это ничего не двигает (там system-ui и есть Segoe UI), но убирает разнобой
   на других машинах. Междустрочие ОСТАЁТСЯ 1.4: 1.5 у первичной настройки —
   это геометрия, +1,5 px на строку и минус пункты чек-листа под сгибом. */
body { font: var(--fs)/1.4 "Segoe UI", system-ui, -apple-system, Roboto, sans-serif; color: var(--ink); background: var(--bg); }
button, input, select, textarea { font: inherit; }
button { position: relative; min-height: var(--hit); padding: 0 16px; border-radius: var(--radius); border: 1px solid var(--line); background: var(--paper); color: var(--ink); cursor: pointer; }
button:hover:not(:disabled) { border-color: var(--ink-3); }
/* Выключенная кнопка — своя заливка, а не прозрачность: белая подпись на
   осветлённом синем давала около 2:1, а её читают с полутора метров. */
button:disabled { cursor: default; }
button:disabled:not(.primary):not(.danger) { color: var(--ink-3); background: var(--bg); }
button.primary:disabled, button.danger:disabled { background: var(--line-2); border-color: var(--line);
  color: var(--ink-2); font-weight: 600; }
button[aria-busy="true"] { cursor: progress; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
button.primary:hover:not(:disabled) { background: var(--accent-hover); }
button.danger { color: var(--bad); border-color: var(--bad); }
button.ghost { border-color: transparent; background: transparent; color: var(--accent); padding: 0 8px; }
button.small { min-height: 36px; padding: 0 10px; font-size: 13px; }
button.big { min-height: 52px; font-size: 17px; padding: 0 28px; }
/* Кнопка в ожидании: подпись на месте (габарит не меняется), кольцо поверх.
   При `prefers-reduced-motion` кольцо не крутится, но видно. */
button .lab { display: inline; }
button.busy .lab { opacity: .3; }
button.busy::after { content: ""; position: absolute; left: 50%; top: 50%; width: 18px; height: 18px; margin: -9px 0 0 -9px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
input[type=text], input[type=number], input[type=password], select, textarea { min-height: var(--hit); padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); width: 100%; }
textarea { min-height: 80px; }
/* Флажки и радиокнопки рисует браузер, и по умолчанию он красит их СВОИМ синим
   — мимо темы. Габарит `accent-color` не меняет. Отмеченный пункт остаётся
   зелёным своей заливкой (`.item.done`): это вердикт, а не оформление. */
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
a { color: var(--accent); }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.boot { padding: 40px; color: var(--ink-2); }
.hidden { display: none !important; }
.muted { color: var(--ink-2); }
.small { font-size: 13px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.grow { flex: 1; }
.card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; }
.badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 13px; font-weight: 600; border: 1px solid transparent; }
.badge.ok { background: var(--ok-bg); color: var(--ok); }
.badge.bad { background: var(--bad-bg); color: var(--bad); }
.badge.none { background: var(--line-2); color: var(--ink-2); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.badge.group { margin-left: 8px; font-weight: 500; background: var(--line-2); color: var(--ink-2); }
.badge.group.ferrous { background: var(--line-2); color: var(--ink-2); }
.badge.group.nonferrous { background: var(--group-bg); color: var(--group); }

/* --- вход и старт --- */
.center { height: 100%; overflow: auto; display: grid; place-items: center; padding: 24px; }
.login { width: 420px; max-width: 100%; display: grid; gap: 12px; }
.login h1 { margin: 0 0 8px; font-size: 22px; }
.field-error { color: var(--bad); font-size: 13px; margin: -6px 0 0; }
input.invalid, textarea.invalid { border-color: var(--bad); }
.start { max-width: 1100px; margin: 0 auto; padding: 24px; display: grid; gap: 20px; }
.app { flex: 1 1 0; min-height: 0; }
.app > .start { height: 100%; overflow: auto; align-content: start; }
.start h1 { margin: 0; font-size: 22px; }
.start .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.list { display: grid; gap: 6px; }
.list label { display: flex; gap: 10px; align-items: center; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer; min-height: var(--hit); }
.list label:hover { border-color: var(--ink-3); }
.list label.selected { border-color: var(--accent); background: var(--accent-bg); }
.list input[type=radio] { width: 20px; height: 20px; margin: 0; }
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--ink-3); display: inline-block; flex: none; }
.dot.on { background: var(--ok); }
.dot.off { background: var(--bad); }
.progress { font-variant-numeric: tabular-nums; color: var(--ink-2); }

/* --- рабочий экран --- */
/* Колонка объявлена `minmax(0, 1fr)` НАРОЧНО: без этого ширину всей оболочки
   задаёт самое широкое содержимое, то есть шапка. Стоило добавить в неё одну
   кнопку — и лист, подвал и график становились шире окна и обрезались справа
   у самого края. Пол min-content снят: шапка обрезает себя сама. */
.shell { display: grid; grid-template-rows: auto 1fr; grid-template-columns: minmax(0, 1fr); height: 100%; }
/* Шапка — одна строка и на 1366: всё, кроме чипов и названия АРМ, держит свою
   ширину, а эти двое ужимаются. Переноса нет — иначе шапка съедала бы две
   строки рабочего места. */
/* Обрезка ТОЛЬКО по горизонтали: `overflow: hidden` резал кольцо
   `:focus-visible` у крайних элементов шапки — оно выходит на 5 px за габарит,
   а вертикального запаса у строки высотой в кнопку нет. `clip` по X оставляет
   `visible` по Y живым (с `hidden` браузер по стандарту заменил бы его на
   `auto` и добавил полосу прокрутки). */
/* Шапка тёмная, как у первичной настройки: рабочее место начинается ниже неё,
   и тёмная плашка отделяет «где я и чем занят» от самой работы. Рамка снизу
   ОСТАЁТСЯ (1 px входит в высоту шапки, снять её — сдвинуть весь лист вверх),
   меняется только её цвет. */
.top { display: flex; gap: 6px; align-items: center; padding: 8px 16px; background: var(--head-bg); color: var(--head-text); border-bottom: 1px solid var(--head-line); overflow-x: clip; overflow-y: visible; }
.top > * { flex: none; }
/* Нажатая кнопка ряда шапки — тёмная заливка, отжатая — без подсветки вовсе.
   Состояние берётся из `aria-expanded`/`aria-pressed`, а не из класса: один
   источник истины и для глаза, и для диктора (знак справки, значок темы). */
.top button[aria-expanded="true"], .top button[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #fff; }
/* Всё приглушённое в шапке берёт свой серый: общий --ink-2 рассчитан на белую
   бумагу и на тёмной плашке нечитаем. Сюда попадают модель прибора, счётчик
   шагов, имя наладчика и плашка сборки на экране входа. */
.top .muted, .top .counter, .top .build { color: var(--head-muted); }
/* Чипы телеметрии — светлые плашки на тёмном, как поля прибора у первичной
   настройки. Цвета точек (годен/нет) не трогаем: они читаются на белом. */
.top .chip { background: var(--paper); color: var(--ink); border-color: transparent; }
/* Кольцо фокуса в шапке — светлое. Общее кольцо цвета --accent тёмно-бирюзовое
   и на тёмно-бирюзовой плашке пропадает, а органы шапки обходят с клавиатуры. */
.top :focus-visible { outline-color: var(--head-text); }
/* Общая шапка служб (shared/ui-kit/libs-header.js, 15.10.2026) стоит над
   всеми экранами; ряд органов листа (.top) — своей строкой под ней (в одну
   строку не помещается на 1366 px, замер 15.10.2026). Здесь только своё.
   Название АРМ и имя наладчика не обрезаются: у поста на экране несколько АРМ,
   и «АРМ настройки…» рядом с «Разработ…» не говорит ни какой это АРМ, ни кто
   за ним. Ширину отдают чипы телеметрии — они и так тянутся (`.chips`) и
   прячут лишнее сами. Замер — `docs/ui-compare-initial-setup-2026-09-08.md` §3. */
.lh-top .lh-brand { min-width: 186px; }
.lh-top .lh-who { min-width: 124px; max-width: 200px; height: 30px; }
.lh-top .lh-exit { height: 30px; padding: 0 10px; }
/* Тёмная тема инвертирует оболочку — шапка над ней красится так же. */
body.dark #libs-header { filter: invert(1) hue-rotate(180deg); }
.top .device { font-weight: 600; white-space: nowrap; }
.top select { width: auto; min-width: 150px; max-width: 176px; }
.top .counter { font-variant-numeric: tabular-nums; color: var(--ink-2); white-space: nowrap; }
.top .build { font-size: 12px; color: var(--ink-3); white-space: nowrap; }
/* В подвале листа плашка сборки прижата к правому краю ряда кнопок: место там
   уже отведено, высоту ряда она не тянет. */
.footer .buttons .build { margin-left: auto; font-size: 12px; color: var(--ink-3); white-space: nowrap; }
/* Слово контура перед версией. Плашка живёт на ДВУХ фонах — тёмной шапке
   экрана старта и светлом подвале листа, — поэтому боевая метка рисуется
   рамкой по `currentColor` и подхватывает цвет места. Кричит только `dev`:
   перепутать опасно в эту сторону, а красный на этих экранах занят вердиктами. */
.build .contour { display: inline-block; margin-right: 6px; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; }
.build .contour-prod { border: 1px solid currentColor; }
.build .contour-dev { background: var(--warn); border: 1px solid var(--warn); color: #fff; }
/* Чипы занимают всю свободную ширину: подписи в них меняются сами (температура,
   съёмка), и растяжка не даёт им двигать соседей справа. */
.chips { flex: 1 1 auto; min-width: 0; display: flex; gap: 8px; overflow: hidden; }
.chip { display: inline-flex; gap: 6px; align-items: center; padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px; font-size: 13px; background: var(--bg); white-space: nowrap; font-variant-numeric: tabular-nums; flex: none; }
.chip .dot { width: 9px; height: 9px; }
/* Температура тикает: место под неё отведено заранее, иначе она двигала бы соседей. */
.chip.temp { min-width: 132px; }
.chip.wide { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; display: block; }
/* Слот съёмки занят всегда: пустой он невидим, но место держит, и подпись
   любой длины обрезается в своих границах, а не двигает шапку. */
.chip.shot { width: 150px; min-width: 150px; overflow: hidden; text-overflow: ellipsis; display: block; }
.chip.shot.empty { visibility: hidden; }
.main { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr) auto; min-height: 0; }
.rail { border-right: 1px solid var(--line); background: var(--paper); overflow: auto; padding: 8px 0; }
.rail .part { padding: 12px 16px 4px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); font-weight: 700; }
.rail .step { display: flex; gap: 10px; align-items: center; padding: 8px 16px; cursor: pointer; min-height: var(--hit); border-left: 4px solid transparent; }
.rail .step:hover { background: var(--bg); }
.rail .step:focus-visible { outline-offset: -3px; }
.rail .step.selected { background: var(--accent-bg); border-left-color: var(--accent); }
.rail .step .no { position: relative; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; background: var(--line-2); color: var(--ink-2); flex: none; }
.rail .step.done .no { background: var(--ok); color: #fff; }
.rail .step.skipped .no { background: var(--ink-3); color: #fff; }
.rail .step.stale .no { background: var(--warn); color: #fff; }
.rail .step.current .no { background: var(--accent); color: #fff; }
/* Глиф состояния — в правом нижнем углу кружка, поверх него. Номер при этом
   остаётся на месте: он центр кружка, а не альтернатива глифу. Пустой глиф
   шага в работе не рисуется вовсе, но его узел стоит всегда — так сверка
   `dom.js` не пересобирает кружок при закрытии шага. */
.rail .step .no .st { display: none; position: absolute; right: -3px; bottom: -3px;
  width: 12px; height: 12px; border-radius: 50%; place-items: center;
  font-size: 9px; line-height: 1; font-weight: 700; background: var(--paper); }
.rail .step .no .st.done { display: grid; color: var(--ok); }
.rail .step .no .st.stale { display: grid; color: var(--warn); }
.rail .step .no .st.skipped { display: grid; color: var(--ink-3); }
.rail .step .t { font-size: 14px; }
/* Подвал — отдельная дорожка, а не липкий блок внутри прокрутки: накрыть лист
   он не может физически. График — дорожка заданной высоты. */
/* Строк в сетке ровно столько, сколько узлов кладёт `app.js`: `grid-auto-flow`
   здесь строчный, и лишний узел уехал бы не туда. Узлов четыре — лист, подвал,
   ручка высоты, панель графика; ручка стоит ТОЛЬКО у развёрнутой панели,
   поэтому у свёрнутой и у листа без графика свои шаблоны строк. */
/* Подвал стоит ПОД панелью графика (14.09.2026): порядок дорожек — лист,
   ручка, график, подвал; узлы в `renderShell` идут в том же порядке. */
.work { display: grid; grid-template-rows: 1fr 8px var(--chart-h) auto; grid-template-columns: minmax(0, 1fr); min-height: 0; }
.work.nochart { grid-template-rows: 1fr auto; }
/* Готового размера `.work.tall` больше нет (решение tech lead 15.09.2026):
   кнопка «Крупнее» его ставила, но рабочий экран собирается веткой `tuning`,
   где этого класса не бывает, — правило было мёртвым вместе с кнопкой. */
/* Свёрнутая панель — полоса 56 px: ручке там места нет, узлов три. */
.work.collapsed { grid-template-rows: 1fr var(--chart-h) auto; --chart-h: 56px; }
.sheet { overflow: auto; padding: 20px 28px; min-height: 0; scroll-padding: 16px 0 24px; }
.sheet h1 { margin: 0; font-size: 22px; }
.sheet h1:focus { outline: none; }
/* Шапка листа — как в АРМ первичной настройки: номер шага квадратом слева,
   над названием надглавие «ЭТАП N ИЗ M». Квадрат стоит в СВОЕЙ колонке и в
   высоту не тянет: он ниже пары «надглавие + заголовок», поэтому высоту шапки
   задаёт текст, а не он. Номер при этом ушёл из текста заголовка, так что
   строка названия не изменилась ни на пиксель — выросло ровно надглавие. */
.sheet-head { display: flex; gap: 12px; align-items: flex-start; }
.sheet-head > .no { flex: none; width: 40px; height: 40px; border-radius: var(--radius);
  background: var(--line-2); color: var(--ink); display: grid; place-items: center;
  font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.sheet-head .head-text { flex: 1 1 auto; min-width: 0; }
.sheet-head .eyebrow { font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--accent); line-height: 1.2; }
/* Заголовок шага — своя строка. Знак вопроса справки здесь больше не стоит
   (D6, 14.09.2026): он переехал в ряд шапки АРМ, левее «Кадры», и красится
   вместе с остальными её кнопками (`.top button[aria-expanded="true"]`). */
.title-row { display: flex; align-items: center; gap: 12px; }
.sheet .goal { color: var(--ink-2); margin: 4px 0 16px; }
.note { padding: 10px 14px; border-radius: var(--radius); background: var(--warn-bg); color: #5c3d00; margin: 0 0 16px; }
.status-line { display: flex; gap: 10px; align-items: center; margin-bottom: 16px; }
/* У шага в работе состояния нет (`sheet.js::statusBadge`), и пустая строка не
   имеет права оставлять после себя отступ: это 16 px на каждом листе. */
.status-line:empty { display: none; }
/* Пропорция рисунков задана здесь: сервер отдаёт только имена файлов, а лента
   не должна переразбиваться по мере загрузки. */
.figs { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 16px; }
/* Рисунок — кнопка: щелчок открывает его во весь экран, значит до него надо
   доходить и с клавиатуры. Габарит и вид — у картинки внутри. */
.figs .figbtn { padding: 0; border: 0; background: none; min-height: 0; display: inline-flex; border-radius: 6px; }
.figs img { height: 88px; width: auto; object-fit: contain; border-radius: 6px; border: 1px solid var(--line); cursor: zoom-in; background: #fff; display: block; }
/* Пропорцию задают стороны, присланные сервером (атрибуты `width`/`height`):
   браузер держит место до загрузки сам. Запасная пропорция — только для
   рисунков, у которых сторон нет (каталог `--media` не настроен). */
.figs img:not([width]) { aspect-ratio: 4 / 3; }
.block { display: flex; gap: 10px; align-items: baseline; margin: 0 0 8px; }
.block .n { width: 26px; height: 26px; flex: none; border-radius: 50%; background: var(--ink); color: #fff;
  display: inline-grid; place-items: center; font-size: 13px; font-weight: 700; }
.block .h { font-weight: 600; }
.block .muted { font-size: 13px; }
/* Правый край строки заголовка блока (сегодня — подпись комплекта образцов шага
   шкалы). Строка выровнена по базовой линии, а этой ячейке так не подходит: она
   встаёт по центру и не тянет строку вниз. Выпадающего списка комплектов здесь
   нет с 14.09.2026 — комплект один, — поэтому и отведённой ширины органа нет:
   подпись хватает по содержимому. */
.block .kit { display: flex; gap: 6px; align-items: center; align-self: center; flex: none; }
/* Состав комплекта стоит ВПЛОТНУЮ к подписи и не переносится по словам
   (просьба наладчика 15.09.2026): в узкой колонке рабочего экрана «КО + д16»
   разрывалось на две строки и уезжало от своей подписи. */
.block .kit b { white-space: nowrap; }
/* Лист — ОДНА последовательность строк со сквозной нумерацией (просьба
   наладчика 14.09.2026): пункт, кадр и орган конвейера стоят там, где он их
   делает. Прежние два блока «1. Пункты / 2. Съёмка» (два заголовка по 26 px)
   уступили место одному, а номер уехал в узкую колонку слева — глаз идёт по
   ней, как по списку в инструкции. Колонка узкая нарочно: лист мерится по
   переполнению (`docs/ui-usability-2026-09-06.md` §5), и каждый пиксель
   ширины у него на счету. */
.flow { display: grid; gap: 8px; margin-bottom: 20px; }
.frow { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 0 6px; align-items: start; }
.frow > .n { padding-top: 11px; text-align: right; font-size: 13px; font-weight: 700;
  color: var(--ink-2); font-variant-numeric: tabular-nums; }
/* Эталон — не действие: он стоит над первым кадром шага, номера не занимает и
   своего отступа внутри последовательности не держит (его даёт `gap`). */
.flow > .refrow { margin: 0; }
/* Пункт — три ячейки в одну строку: флажок, текст, отметка времени. Колонки
   фотографий больше нет (08.09.2026): рисунки пункта ушли в справку шага, и
   освободившуюся ширину забрал текст — пункт из двух строк стал строкой.
   Ячейки заданы явно, поэтому пункт без «Пропустить…» не двигает правую
   колонку. */
.item { display: grid; grid-template-columns: 36px minmax(0, 1fr) 128px; gap: 4px 12px; align-items: start; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); }
.item > input[type=checkbox] { grid-column: 1; grid-row: 1; }
.item > .body { grid-column: 2; grid-row: 1; display: block; cursor: pointer; }
.item > .side { grid-column: 3; grid-row: 1; }
/* Вся строка пункта — цель щелчка (07.09.2026): курсор и подсветка говорят
   об этом до нажатия. Подсветка светлее, чем у отмеченного пункта, — «сюда
   можно», а не «уже сделано». */
.item.tappable { cursor: pointer; }
.item.tappable:hover { background: var(--bg); }
.item.tappable.done:hover { background: var(--ok-bg); }
.item.done { background: var(--ok-bg); border-color: #b7e2c2; }
.item.skipped { background: var(--line-2); }
.item input[type=checkbox] { width: 28px; height: 28px; margin: 4px; cursor: pointer; }
.item .text { padding-top: 6px; }
.item .meta { font-size: 13px; color: var(--ink-2); margin-top: 4px; }
/* Правая колонка пункта — фиксированной ширины: «Пропустить…» и отметка времени
   меняются местами, а список под пунктом не едет. */
.item .side { display: grid; gap: 4px; justify-items: end; align-content: start; }
.item .side .meta { margin-top: 6px; font-variant-numeric: tabular-nums; }
/* Обратный отсчёт выдержки — в той же правой колонке, под отметкой времени
   (14.09.2026): он идёт от отметки ЭТОГО пункта, и кадра, которому выдержка
   запирала съёмку, на шаге больше нет. */
.item .side .tick { font-size: 13px; color: var(--ink-2); text-align: right;
  font-variant-numeric: tabular-nums; }
/* Строка эталона шагов сличения: одна на лист, над кадрами. Ровно одна
   строка высотой — она отвечает на «с чем сличаем», а не рассказывает. */
.refrow { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; padding: 6px 10px;
  border: 1px dashed var(--line); border-radius: var(--radius); background: var(--paper); }
.refrow .body { min-width: 0; flex: 1; }
.refrow .lab { color: var(--ink-2); }
.refrow .what { font-weight: 600; }
.refrow.none { border-style: solid; border-color: #f0d9a8; }
/* Запасной путь (15.09.2026): эталонного кадра этого образца в артефактах
   модели нет, и сличение идёт с КО. Строку видно, но это не отказ — рамка
   та же, что у «Эталона нет», а высота строки прежняя: слово с сервера
   умещается в ту же строку, и числа §5 замера не меняются. */
.refrow.spare { border-color: #f0d9a8; }
/* Конвейер выстрелов (14.09.2026). Компонент один на лист шага и на панель
   «Прибор» (`web/js/conveyor.js`), поэтому и класс общий — разной остаётся
   только раскладка вокруг.

   На ЛИСТЕ он стоит СВОЕЙ строкой последовательности (14.09.2026): на шагах
   2–4 кадров нет вовсе, и прятать его в заголовок блока «Снимите кадр» больше
   негде — а порядок наладчик называет по строкам («сначала конвейер, потом
   посадка на клей»). Ряд кнопки и паузы выровнен по левому краю, как и всё
   остальное на листе; заголовок блока он больше не подпирает. Строка состояния
   под органом появляется только когда конвейеру есть что сказать — пустая она
   скрыта. */
.conveyor.sheet { display: grid; gap: 2px; justify-items: start; padding: 4px 0; }
.conveyor.sheet .small { text-align: left; }
.conveyor.device { display: grid; gap: 4px; margin: 6px 0 0; }
.conveyor button.conv-run.active { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
.conveyor .small:empty { display: none; }
/* Колонка действий фиксированной ширины и СЛЕВА (08.09.2026, просьба
   оператора): наладчик у поста ищет кнопку съёмки, а не заголовок кадра, и
   глаз идёт по левому краю листа. Ширина колонки та же, поэтому высота
   карточки от перестановки не изменилась. Подпись кнопки и тикающая выдержка
   по-прежнему не дышат и не двигают вердикт. */
.cap { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 12px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); }
.cap.ok { border-color: #b7e2c2; }
.cap.bad { border-color: #f3b4ad; }
/* Образец — первое, что читают в карточке: имя пишет сервер (`sample_title`),
   а не наладчик, вычитывая его из подсказки. Кегль не мельче заголовка блока
   листа, иначе строка теряется между заголовком кадра и подсказкой. */
.cap .sample { font-weight: 700; font-size: 16px; margin-bottom: 2px; }
.cap .title { font-weight: 600; }
.cap .hint { color: var(--ink-2); font-size: 13px; margin-top: 2px; }
.cap .tick { font-variant-numeric: tabular-nums; }
.cap .big-value { font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; margin-top: 8px; }
.cap .big-value .exp { font-size: 15px; font-weight: 400; color: var(--ink-2); margin-left: 8px; }
/* Рабочий экран: карточка `.tuning-verdict` без `.cap`, и по высоте она тесная —
   число мельче листа, допуск в той же строке, но своим шрифтом и с отступом
   (SN 30164, 23.09.2026: «18 2249 000–12 000»). Строк не добавляет. */
.tuning-verdict .big-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; margin-top: 4px; }
.tuning-verdict .big-value .exp { font-size: 13px; font-weight: 400; color: var(--ink-2); margin-left: 14px; }
.tuning-verdict .big-value.bad { color: var(--bad); }
.tuning-verdict .big-value.ok { color: var(--ok); }
/* Таблица строк вердикта с шапкой (`Verdict.lines_head`, 23.09.2026): рамка у
   каждой ячейки, подписи столбцов сверху — строки шагов сдвигов и пика
   углерода не сливаются в одну линию. */
table.vgrid { border-collapse: collapse; width: 100%; margin-top: 6px; }
table.vgrid th, table.vgrid td { border: 1px solid var(--line); padding: 4px 8px; text-align: left; }
table.vgrid th { background: var(--bg); color: var(--ink-2); font-size: 13px; font-weight: 600; white-space: nowrap; }
/* Переноса в ячейках не запрещаем: у составных вердиктов шагов 13–14 в
   «Измерено» стоит текст, и таблица без переноса вылезала за свою колонку. */
table.vgrid td.v { text-align: right; font-variant-numeric: tabular-nums; }
table.vgrid tr.bad td.v { color: var(--bad); font-weight: 600; }
.cap.bad .big-value { color: var(--bad); }
.cap.ok .big-value { color: var(--ok); }
/* Кат — ОДИН орган на весь АРМ (просьба наладчика 16.09.2026: «значок ката,
   тяжело попасть, закрыть трудно»). Нажимается ПОЛОСА, а не значок: нативный
   треугольник `::marker` — цель в восемь-десять точек, и попасть в неё мышью у
   поста наладчик не успевал. До правки орган был разный в разных местах:
   `cursor: pointer` стоял у прошлых попыток только на ЛИСТЕ (`.cap`), у
   разбора и панелей — местными правилами, а у ката вердикта не стоял нигде,
   и браузер честно показывал стрелку «сюда не нажимают». Правило одно на все
   шесть катов АРМ (вердикт на листе и на рабочем экране, прошлые попытки,
   заглушка панели «Разработчик», растр OIS панели «Прибор», справочные
   подсказки вердикта — 18.09.2026); своего правила
   под отдельный кат не заводить — два похожих органа разойдутся молча.
   Компонент ui-kit катов не имеет, его это не касается. */
details > summary { list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: 8px; min-height: 32px; padding: 0 6px; margin: 0 -6px; border-radius: 6px;
  user-select: none; }
details > summary::-webkit-details-marker { display: none; }
details > summary::before { content: "▸"; flex: none; width: 1em; text-align: center; color: var(--ink-2); }
details[open] > summary::before { content: "▾"; }
details > summary:hover { background: var(--accent-bg); }
details > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* «Свернуть» той же полосой внизу: у вердиктов шагов 13 и 14 под катом
   шестнадцать-двадцать строк, и заголовок уезжает за верхний край. */
.cutfoot { display: block; width: 100%; text-align: left; margin-top: 6px; padding: 0 6px 0 30px;
  min-height: 28px; border: 0; border-radius: 6px; background: none; color: var(--ink-2);
  font: inherit; font-size: 13px; cursor: pointer; }
.cutfoot:hover { background: var(--accent-bg); color: var(--ink); }
.cutfoot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cap .tries { margin-top: 8px; font-size: 13px; display: grid; gap: 2px; }
.cap .tries summary { color: var(--ink-2); }
.cap .tries .try { display: flex; gap: 8px; align-items: center; }
/* Вердикт стопа конвейера (15.09.2026) стоит СВОЕЙ строкой листа, а не в
   карточке кадра: на шагах 2–4 кадров нет вовсе. Вид у него тот же — правила
   не копируются под новый селектор, а расширяются, иначе две таблицы вердикта
   разошлись бы молча. */
.cap .verdict, .flow > .verdict { margin-top: 10px; }
.cap .verdict .head, .flow > .verdict .head { display: flex; gap: 10px; align-items: center; }
/* Таблица вердикта стоит РОВНО: имя занимает всю свободную ширину, а число,
   предел и галочка выстраиваются в колонки у правого края (просьба наладчика
   15.09.2026 — «сделать ровнее»). До этого ширину колонок задавало самое
   длинное слово строки, и числа соседних строк стояли лесенкой.
   Пустой предел (значение дословно повторяет его — `checks._line`) колонку не
   занимает, но и не сдвигает соседей. */
.cap table, .verdict table { border-collapse: collapse; margin-top: 8px; font-size: 13px; width: 100%; }
.cap td, .verdict td { padding: 3px 12px 3px 0; vertical-align: top; }
.cap td:first-child, .verdict td:first-child { width: 100%; }
.cap td.v, .verdict td.v { font-variant-numeric: tabular-nums; white-space: nowrap; }
.cap tr.bad td, .verdict tr.bad td { color: var(--bad); }
/* Строка, взятая жёлтым набором критериев (варианты R² «как в настольном ПО»,
   04.10.2026): та же подсветка, что у настолки, — жёлтая. */
.vt td.warn { background: var(--warn-bg); color: var(--warn); }
/* Сетка кадров мультискана (23.09.2026): колонки чисел одной ширины по
   центру, номер кадра не растягивается на всю ширину, как имя строки вердикта.
   Красным — ячейка вне допуска, а не вся строка: видно, что именно ушло. */
table.grid td, table.grid th { padding: 3px 8px; text-align: center; }
table.grid td:first-child, table.grid th:first-child { width: auto; text-align: left; padding-left: 0; }
table.grid th { font-weight: 600; white-space: nowrap; border-bottom: 1px solid var(--line); }
table.grid thead tr.muted th { font-weight: 400; color: var(--ink-2); }
table.grid tbody tr:nth-child(even) td { background: var(--bg); }
table.grid tr td.bad { color: var(--bad); font-weight: 600; }
/* Вердикт без галочек (просьба tech lead 24.09.2026): критерий пройден —
   заливка зелёным, не пройден — красным. В таблице строк критерий — строка
   (`tr.ok`/`tr.bad`), в сетке кадров — ячейка (`td.ok`/`td.bad`); справочное
   не заливается. Правило стоит ПОСЛЕ зебры сетки — иначе чётная строка
   перебила бы заливку (вес селектора — как у зебры). Отступ слева — чтобы текст не лип к краю заливки. */
table.vt tbody tr.ok > td, table.vt tbody tr td.ok { background: var(--ok-bg); }
table.vt tbody tr.bad > td, table.vt tbody tr td.bad { background: var(--bad-bg); }
table.vt td:first-child, table.vt th:first-child { padding-left: 8px; }
/* Справочные подсказки вердикта — под катом (просьба наладчика 18.09.2026).
   Отступ сверху держит сам кат: полоса `summary` стоит там, где раньше стояла
   первая строка списка, и закрытый кат занимает 32 px вместо всего блока.
   Цвет полосы — цвет подсказок: по нему видно, что под катом справочное, а не
   ещё одна таблица вердикта. */
.hintscut { margin-top: 8px; }
.hintscut > summary { color: #5c3d00; }
.hints { margin: 6px 0 0; padding-left: 18px; color: #5c3d00; }
.hints li { margin: 2px 0; }
.cap .actions { display: grid; gap: 6px; align-content: start; }
.cap .actions button { width: 100%; white-space: normal; padding: 4px 10px; }
.footer { background: var(--paper); border-top: 1px solid var(--line); padding: 12px 28px; display: grid; gap: 8px; }
/* Место под помехи закрытия отведено заранее и не схлопывается: список тает по
   мере отметок, и подвал иначе менял бы высоту, двигая панель графика. */
.footer .problems { color: var(--ink-2); font-size: 14px; height: 42px; overflow: auto; }
.footer .problems .fail { color: var(--bad); }
.footer .buttons { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.override { padding: 8px 12px; border-radius: var(--radius); background: var(--warn-bg); color: #5c3d00; font-size: 14px; }

/* --- график --- */
.chartbox { position: relative; overflow: hidden; min-height: 0; border-top: 1px solid var(--line); background: var(--paper); padding: 8px 16px 10px; }
/* Панель графика не переносится: перенос менял высоту дорожки от шага к шагу. */
.chartbox .bar { display: flex; gap: 10px; align-items: center; margin-bottom: 4px; font-size: 13px; flex-wrap: nowrap; overflow: hidden; }
.chartbox .bar .title { font-weight: 600; }
.chartbox .bar > * { flex: none; white-space: nowrap; }
/* Место под слова о шкале отведено заранее: они пропадают, когда шкала
   применена, и растяжимая полоса зон иначе сдвигала бы всё правее на 250 px. */
.chartbox .bar .note-scale { white-space: nowrap; width: 224px; text-align: right; overflow: hidden; text-overflow: ellipsis; }
/* Кнопки зон и панель ui-kit прокручиваются вбок, а не переносятся: перенос
   менял высоту дорожки графика от шага к шагу. Сжимать панель нельзя — ui-kit
   тогда складывает кнопки матриц в столбик, и высота снова растёт. */
.chartbox .zones, .chartbox .sp-tools { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.chartbox .zones::-webkit-scrollbar, .chartbox .sp-tools::-webkit-scrollbar { display: none; }
.chartbox .zones { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; }
.chartbox .zones > *, .chartbox .sp-tools > *, .chartbox .sp-view > * { flex: none; white-space: nowrap; }
.chartbox .sp-canvas { height: var(--canvas-h); }
/* Общий график не перекрашиваем и не копируем — только прячем на нашем листе
   ВТОРУЮ таблицу измеренных пиков: на экране наладчика и так есть таблица
   вердикта, и второй он не просил (решение tech lead 15.09.2026).
   Подсказка мыши, наоборот, видна ВСЕГДА: ею объясняется, чем меряют высоту
   пика (клик — точка уровня, второй клик — вершина). До 15.09.2026 обе
   открывались только классом `.work.tall`, а его не бывает с переездом на
   рабочий экран — то есть не было видно ни одной. */
.chartbox .sp-peaks { display: none; }
/* «Сбросить» у нас своя и стоит в полосе шапки: кнопка ui-kit скрыта точечно
   (`.sp-reset`), а не вместе со всей полосой — две одинаковые кнопки на одном
   экране читались бы как два действия. Полоса `.sp-tools` видна ВСЕГДА, даже
   на пустом графике и на 1366×768 без «Крупнее» (решение tech lead 15.09.2026:
   «функционал графика един на всех шагах и при пустом графике» — отменяет
   решение 06.09, болячку 59). */
.chartbox .sp-reset { display: none; }
.work.collapsed .sp-tools, .work.collapsed .sp-canvas, .work.collapsed .placeholder { display: none; }
/* Пустое состояние прячет канву ВИДИМОСТЬЮ, а не `display`: график меряет себя
   при каждой отрисовке, и на скрытой канве нулевой ширины кадр не рисуется. */
.chartbox.empty .sp-canvas { visibility: hidden; }
.chartbox .placeholder { display: none; }
.chartbox.empty .placeholder { display: block; position: absolute; left: 0; right: 0; top: 50%;
  text-align: center; color: var(--ink-2); }
/* Кадра у шага нет, но эталон наложен: канва остаётся ВИДИМОЙ — на ней лежит
   кривая сличения, ради которой наладчик и держит эталон включённым. Подпись
   «кадра ещё нет» идёт по полосе бумаги, чтобы не читаться сквозь кривую. */
.chartbox.empty.curves .sp-canvas { visibility: visible; }
.chartbox.empty.curves .placeholder { padding: 2px 0; background: var(--paper); }
/* А вот когда на графике лежат КАДРЫ, подписи нет вовсе: их положил туда сам
   наладчик кнопкой-тоглом «На график» (09.09.2026), и «кадров ещё не было»
   поверх его же кривых — неправда. Прячется наложение, габарит не меняется. */
.chartbox.empty.frames .placeholder { display: none; }
/* Тёмная тема — по значку в шапке, а не по шагу (просьба 14.09.2026; до этого
   её сам включал шаг 4 — проверка полосы светочувствительной бумажкой).
   Страница инвертируется целиком, включая общий график; фотографии
   возвращаются к норме. */
body.dark { background: #000; }
body.dark .shell { filter: invert(1) hue-rotate(180deg); }
body.dark .shell img { filter: invert(1) hue-rotate(180deg); }

/* --- сетки серий --- */
.grid { border-collapse: collapse; width: 100%; background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.grid th, .grid td { padding: 8px 10px; border-bottom: 1px solid var(--line-2); text-align: left; vertical-align: middle; }
.grid th { font-size: 13px; color: var(--ink-2); font-weight: 600; background: var(--bg); }
.grid tr.group td { background: var(--bg); font-weight: 600; font-size: 13px; color: var(--ink-2); }
/* Высота клетки задана: галочка «✓» приходит из запасного шрифта и иначе
   растит строку сетки прямо во время съёмки. */
.cellbtn { min-width: 92px; height: var(--hit); min-height: var(--hit); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Место под самую длинную подпись отведено заранее: «Снять серию (0/5)» и
   «5/5 ✓» — одна и та же кнопка, и она не должна менять габарит на ответе. */
.cellbtn.wide { min-width: 184px; }
/* Место под кнопку отмены отведено заранее: она появляется после первого кадра,
   и без запаса ряд клеток переносился бы на вторую строку. */
.cellwrap { display: inline-flex; align-items: center; gap: 2px; min-width: 126px; }
/* Клетка со списком измерений (`web/js/cell-shots.js`): кнопки в строку,
   список — под ними. Свёрнут по умолчанию: развёрнутые списки у всех клеток
   увели бы лист за экран, а числа «сколько работы видно» менять нельзя. */
.cellwrap.shots { flex-direction: column; align-items: flex-start; gap: 4px; }
.cellwrap.shots .cellbar { gap: 2px; }
/* Язычок списка — глиф с числом: он стоит в строке кнопок клетки, и текстом
   «Измерения (3)» ряд клеток уехал бы на вторую строку. Имя для диктора —
   в `aria-label`. */
.shots-toggle { padding: 0 4px; font-size: 12px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
/* Запас под «Снять серию (0/5)» ужат ровно на язычок: в двухколоночной
   лестнице термо 184 px кнопки плюс язычок не помещались в колонку, и КАЖДАЯ
   строка лестницы уезжала на две — минус три строки из видимых без прокрутки. */
.cellwrap.shots .cellbtn.wide { min-width: 156px; }
.cellwrap.shots.open { flex-basis: 100%; }
/* В лестнице термо клетка стоит не в гибкой строке, а в ячейке таблицы:
   `flex-basis` там ничего не значит, и раскрытый список сжимался до ширины
   кнопки. Ширину задаёт сама ячейка. */
.grid td > .cellwrap.shots.open { min-width: 100%; }
.shotlist { list-style: none; margin: 0; padding: 0; width: 100%; font-size: 13px; }
.shotrow { display: flex; align-items: center; gap: 6px; padding: 2px 0; border-top: 1px solid var(--line-2); }
.shotrow .no { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.shotrow .what { color: var(--ink-2); }
.shotrow.clipped .what { color: var(--warn); }
/* Кадр, лежащий на графике вторым рядом, виден в любом списке одинаково:
   квадратик цвета СВОЕЙ кривой на кнопке «На график» и подсветка строки. Цвет
   приходит из состояния наложения (`S.chartOverlays`) — тот самый, которым
   кривая легла на канву; общий график при этом не перекрашивается.
   Квадратик стоит в кнопке всегда и прячется, пока наложения нет: иначе сверка
   `dom.js` пересобирала бы строку на каждое нажатие. */
.ovl-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; display: inline-block;
  margin-right: 6px; vertical-align: baseline; }
.ovl-dot.off { display: none; }
/* Кнопка «На график» — тогл (09.09.2026), и зажатой её видно ЗАЛИВКОЙ, а не
   одной рамкой: у поста наладчик читает ряд кнопок боковым зрением, и разница
   в один тон границы там не различается. Габарит не меняется — только цвет:
   утопленную кнопку рисовать тенью нельзя, ряд поедет. */
button.overlaid { border-color: var(--ink-3); background: var(--accent-bg); color: var(--ink); }
.shotrow.overlaid, .frow.overlaid { background: var(--bg); }
.cellbtn.shot { background: var(--ok-bg); border-color: #b7e2c2; color: var(--ok); }
.cellbtn.clipped { background: var(--warn-bg); border-color: #e6cf8a; color: var(--warn); }
/* Четвёртое состояние клетки — «переснять» (08.09.2026): расчёт назвал её в
   `report.reshoot`. Рамка в 2 px вместо 1 px даётся ВНУТРЕННЕЙ тенью, а не
   толщиной границы: у клетки задана высота, и второй пиксель рамки увёл бы
   строку сетки. Цвет — свой цвет вердикта (`--warn`), тема красит хром. */
.cellbtn.redo { background: var(--warn-bg); border-color: var(--warn); color: var(--warn);
  box-shadow: inset 0 0 0 1px var(--warn); font-weight: 600; }
/* Кнопка «Переснять клетку…» у названной клетки — полноразмерная и своего
   цвета: за ней наладчик сюда и пришёл по вердикту «Шкала не принята». */
button.redo-reset { color: var(--warn); border-color: var(--warn); font-weight: 600; }
/* Подпись под кнопкой. Слово даёт сервер (`reshoot_mark`); место под него
   отведено высотой строки, чтобы появление подписи не двигало соседние ряды. */
.cellwrap .cellmark { font-size: 11px; line-height: 12px; color: var(--warn); padding-left: 2px; }
/* Ряд клеток серии шкалы: до четырёх клеток в строке, и у каждой теперь три
   органа — съёмка, «Переснять…» и язычок списка измерений. Зазоры ужаты ровно
   настолько, чтобы четыре клетки KO стояли ОДНОЙ строкой: перенос уносил ряд
   сетки и 72 px из видимого без прокрутки (замер 08.09.2026,
   `docs/ui-usability-2026-09-06.md` §5). Лестницы термо это не касается — там
   клетка в строке одна, и её зазоры остаются прежними. */
.grid.series .row { gap: 6px; }
.grid.series .cellbar button.small:not(.ghost) { padding: 0 6px; }
.grid.series .shots-toggle { padding: 0 2px; }
.warn-text { color: var(--warn); }
/* «Настроить шкалу» и «Настроить шкалу заново» — одна кнопка. */
.fitbtn { min-width: 296px; }
.result { margin-top: 16px; }
.result h3 { margin: 0 0 6px; font-size: 17px; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 14px; }
.point { padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); }
.point.fixed { border-color: #b7e2c2; background: var(--ok-bg); }
.point input { width: 120px; display: inline-block; }
.point button { min-width: 168px; }
/* Температурные точки идут друг под другом (просьба оператора 14.09.2026):
   блок точки — заголовок, карточка пирометра и горизонтальный ряд образцов. */
.pointsec { margin-bottom: 16px; }
.pointsec .block { margin-bottom: 6px; }
.pointsec .point { max-width: 560px; }
/* Секция, которая ждёт чужую лестницу, приглушена целиком: карточка точки и её
   ряд образцов. Признак и слова — с сервера (`waiting`). */
.pointsec.waiting { opacity: 0.55; }
/* Ряд образцов точки: кнопки-образцы слева направо; раскрытые измерения
   стоят КОЛОНКОЙ под своим образцом, соседние колонки живут бок о бок
   (просьба оператора 15.09.2026). */
.samplerow { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: flex-start; margin-top: 10px; }
.samplecell { display: inline-flex; flex-direction: column; align-items: flex-start; }
/* Раскрытая клетка в ряду образцов НЕ занимает всю строку — только свою колонку. */
.samplerow .cellwrap.shots.open { flex-basis: auto; }
.samplerow .shotlist { width: auto; min-width: 100%; }
/* Кнопки образцов термо НЕ ПРЫГАЮТ при съёмке (27.09.2026, A3): ширина
   кнопки фиксирована, подпись короткая постоянной длины («уг82 0/1»), а место
   под «↶» и «▸N» стоит всегда — пустое место прячется видимостью
   (`.slot-off`), а не убирается из ряда. */
.samplerow .cellwrap.shots .cellbtn.wide { width: 140px; min-width: 140px; overflow: hidden; text-overflow: ellipsis; }
.samplerow .shots-toggle { min-width: 30px; }
.slot-off { visibility: hidden; pointer-events: none; }
.shotrow.compact { gap: 8px; padding: 3px 0; }
.shotrow.compact .no { min-width: 32px; }
.reshoot-icon { min-width: 36px; padding: 0 8px; font-size: 16px; line-height: 1; }
/* Образец лестницы термо (30.09.2026): имя, кнопка «Снять/Переснять», значок
   серии на графике, язычок кадров. Под ним — кадры серии галочками в две
   колонки: тринадцать строк в одну колонку уносили бы ряд образцов вниз. */
.measurecell .sname { font-weight: 600; min-width: 64px; padding-right: 4px; }
.measurecell .cellbtn.measure { min-width: 96px; }
.series-icon { min-width: 36px; padding: 0 6px; font-size: 16px; line-height: 1; }
.framelist { display: grid; grid-template-columns: repeat(2, max-content); gap: 2px 14px;
  font-size: 13px; padding: 4px 2px; }
.framelist .framebox { display: inline-flex; gap: 6px; align-items: center; cursor: pointer; }
.framelist .framebox[data-k="all"] { grid-column: 1 / -1; font-weight: 600; }
.nofit { margin-left: 4px; }

/* Свернуть рельс — на выбор наладчика: числа говорят, что ширина здесь ничего
   не решает (видно 12 пунктов из 32 и с рельсом, и без него), а имена шагов
   слепой наладчик назвал самым ценным в интерфейсе. Поэтому не умолчание,
   а кнопка (docs/ui-usability-2026-09-06.md, §3). */
/* Свёрнутый рельс — 48 px, как у первичной настройки (было 56): восемь
   пикселей уходят листу, а язычок 26 px и кружок номера 26 px в 48 стоят
   свободно. */
.main.rail-narrow { grid-template-columns: 48px minmax(0, 1fr) auto; }
.rail.narrow .step .t, .rail.narrow .part { display: none; }
.rail.narrow .step { justify-content: center; padding: 6px 4px; }
/* Надпись видна только в свёрнутом рельсе; в развёрнутом её место занимают
   имена шагов, и она была бы шумом. */
.railword { display: none; }
.rail.narrow .railword { display: block; writing-mode: vertical-rl; transform: rotate(180deg);
  margin: 8px auto 0; font-size: 12px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-2); }
/* Кнопка сворачивания живёт в самом рельсе: шапка на 1366 занята целиком, и
   лишний орган в ней выдавливал «Выйти» и плашку версии за край экрана. */
/* Язычок сворачивания — один орган и у рельса, и у панели спектра: маленький
   квадрат на кромке, всегда на одном месте (07.09.2026). Габарит `--hit`
   держит палец, а рисунок остаётся мелким. */
.tab { width: 26px; height: 26px; min-height: 26px; padding: 0; display: grid; place-items: center;
  font-size: 14px; line-height: 1; color: var(--ink-2); background: var(--paper);
  border: 1px solid var(--line); border-radius: 6px; cursor: pointer; position: relative; flex: none; }
.tab:hover { background: var(--bg); color: var(--ink); }
.tab::after { content: ""; position: absolute; inset: -7px; }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Шапка рельса не уезжает при прокрутке списка шагов: кнопка нужна на любом
   шаге, а шагов шестнадцать. */
.railhead { position: sticky; top: -8px; z-index: 1; display: flex; justify-content: flex-end;
  padding: 0 8px 6px; background: var(--paper); }
.rail.narrow .railhead { justify-content: center; padding: 0 0 6px; }

/* --- порядок, шапочка этапа и простой экран (решение tech lead 07.10.2026) ---
   Шапочка над шагами рельса: этап и сколько его шагов пройдено. В свёрнутом
   рельсе — только «2/16». Запертый шаг (`.locked`) и пункт, который ждёт
   предыдущего (`.waits`), приглушены, но читаются: наладчик видит, что впереди. */
.stagecap { margin: 0 12px 8px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); }
.stagecap .st-title { font-weight: 700; font-size: 14px; }
.stagecap .st-text { font-size: 13px; color: var(--ink-2); margin-top: 2px; }
.stagecap .st-bar { height: 6px; border-radius: 3px; background: var(--line-2); margin-top: 6px; overflow: hidden; }
.stagecap .st-bar > span { display: block; height: 100%; width: calc(100% * var(--done) / var(--all)); background: var(--ok); }
.stagecap .st-short { display: none; }
.rail.narrow .stagecap { margin: 0 4px 6px; padding: 4px 2px; text-align: center; }
.rail.narrow .stagecap .st-title, .rail.narrow .stagecap .st-text { display: none; }
.rail.narrow .stagecap .st-short { display: block; font-size: 12px; font-weight: 700; }
.rail .step.locked { cursor: not-allowed; opacity: .55; }
.rail .step.locked:hover { background: none; }
.item.waits { opacity: .6; }
.item.waits input[type=checkbox] { cursor: not-allowed; }
.item.picked { box-shadow: inset 4px 0 0 var(--accent); }

/* --- справка по шагу ---
   Справка — своя колонка рабочего экрана, а не наложение и не часть листа:
   лист остаётся на месте и держит свою прокрутку, справка прокручивается
   внутри себя. Колонка листа по-прежнему `minmax(0, 1fr)` — иначе самое
   широкое содержимое листа задавало бы ширину всей строки и обрезало её край.
   С 14.09.2026 (D6) справка — панель ТОГО ЖЕ места, что «Кадры»/«Прибор»/
   «Протокол»/«Разработчик»: `app.js` держит их взаимоисключёнными
   (`S.drawerNode`), поэтому своей сетки у справки больше нет — она встаёт в
   те же колонки `.main.with-drawer`, что и остальные панели. Шаблонов у
   `.main` осталось два: `with-drawer` и `rail-narrow.with-drawer`. */
/* Колонок в сетке ровно столько, сколько узлов кладёт `app.js`: `grid-auto-flow`
   здесь строчный, и лишний узел уехал бы ВТОРОЙ СТРОКОЙ под лист, а не вправо. */
.main.with-drawer { grid-template-columns: var(--rail-w) minmax(0, 1fr) auto auto; }
.main.rail-narrow.with-drawer { grid-template-columns: 48px minmax(0, 1fr) auto auto; }
/* Ширину наладчик тянет ручкой; сохранённое им число приезжает переменной
   `--help-w` прямо на сетке. Пределы — здесь, а не в браузерном коде: узкая
   справка не показывает рисунок, широкая съедает лист. Верхний предел взят и
   от окна — на 1366 px иначе сохранённые 720 px выдавили бы лист. */
.help { width: var(--help-w); min-width: 300px; max-width: min(720px, 46vw);
  overflow: auto; background: var(--paper); border-left: 1px solid var(--line);
  padding: 14px 16px 24px; display: grid; gap: 8px; align-content: start; min-height: 0; }
/* Ручка: полоса захвата 8 px по левому краю панели, рисунок — короткая черта
   посередине. Шаг клавиатуры лежит здесь же переменной: `help.js` читает его
   со стиля и своих чисел не заводит. */
/* Ручка справки и ручка выдвижной панели — один орган, поэтому и один стиль.
   Полосу захвата справки задаёт её колонка сетки, у выдвижной панели колонка
   идёт «по содержимому» — там ширину держит сама ручка. */
/* Ручка панели графика — тот же орган, только по высоте: строка сетки на
   ВЕРХНЕЙ кромке панели (09.09.2026). Шаг клавиатуры и полоса захвата у всех
   трёх одни, отличается направление. */
.help-grip, .drawer-grip, .chart-grip, .do-grip { --step: 24px; position: relative; touch-action: none; }
.help-grip, .drawer-grip, .do-grip { cursor: col-resize; }
.chart-grip { cursor: row-resize; }
/* Обе ручки живут в одной и той же колонке сетки (`.main.with-drawer`,
   справка и остальные панели взаимоисключены) — ширина колонки `auto`,
   и её задаёт сама ручка, а не грид-трек. */
.help-grip, .drawer-grip, .do-grip { width: 8px; }
.help-grip::before, .drawer-grip::before, .do-grip::before { content: ""; position: absolute; top: 50%; left: 50%; width: 2px; height: 28px;
  margin: -14px 0 0 -1px; border-radius: 2px; background: var(--line); }
/* Черта ручки графика лежит поперёк — вдоль кромки, которую тянут. */
.chart-grip::before { content: ""; position: absolute; top: 50%; left: 50%; width: 28px; height: 2px;
  margin: -1px 0 0 -14px; border-radius: 2px; background: var(--line); }
.help-grip:hover::before, .drawer-grip:hover::before, .do-grip:hover::before { background: var(--ink-3); height: 44px; margin-top: -22px; }
.chart-grip:hover::before { background: var(--ink-3); width: 44px; margin-left: -22px; }
.help-grip:focus-visible, .drawer-grip:focus-visible, .chart-grip:focus-visible, .do-grip:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.help-grip:focus-visible::before, .drawer-grip:focus-visible::before, .chart-grip:focus-visible::before, .do-grip:focus-visible::before { background: var(--accent); }
/* Пока тянут, курсор один на весь экран, и текст не выделяется рывком мыши. */
body.resizing { cursor: col-resize; user-select: none; }
body.resizing.resizing-rows { cursor: row-resize; }
/* Действие текущего пункта: наладчик отметил пункт на листе или встал на его
   строку — справка показывает, где он. Подложка с полем вокруг, номер тем же
   цветом: полоса слева ушла бы под номер списка, он стоит вне текста. */
.help .acts li.current { background: var(--accent-bg); border-radius: 4px;
  box-shadow: 0 0 0 4px var(--accent-bg); }
.help .acts li.current::marker { color: var(--accent); font-weight: 700; }
.help h2 { margin: 0; font-size: 17px; }
/* Заголовок принимает фокус при открытии (диктор читает, чей это шаг), но
   кольцо на нём было бы шумом: это не орган, а место, куда встал курсор. */
.help h2:focus { outline: none; }
.help h3 { margin: 12px 0 0; font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-2); font-weight: 700; }
.help .why { margin: 0; }
.help-model-link { display: block; padding: 9px 12px; border: 1px solid var(--accent);
  border-radius: 6px; background: var(--accent-bg); color: var(--accent); font-weight: 700;
  text-decoration: none; text-align: center; }
.help-model-link:hover, .help-model-link:focus-visible { text-decoration: underline; }
.help .acts { margin: 0; padding-left: 20px; display: grid; gap: 10px; }
.help .list-plain { margin: 0; padding-left: 18px; display: grid; gap: 5px; }
/* Рисунок в справке крупнее, чем на листе: по 56-px превью пункта разъём не
   разобрать, оно работает как «на что нажать». Здесь его смотрят. */
.help .figs { margin: 6px 0 0; min-width: 0; }
/* Ширина колонки — потолок и для рисунка: у снимка спектра пропорция широкая,
   и заданная высота делала его шире колонки. Распирало не рисунок, а саму
   колонку сетки (`auto` не уже своего содержимого) — и текст справки уезжал за
   правый край экрана. Поэтому у рисунка два предела, а у ячеек — свой ноль. */
.help > * { min-width: 0; }
.help .figs .figbtn { max-width: 100%; min-width: 0; }
.help .figs img { height: auto; width: auto; max-height: 132px; max-width: 100%; }
.help .ref { margin-top: 12px; }

/* --- диалоги и уведомления --- */
/* Тост ничего не забирает: сквозь дорожку уведомлений щелчок проходит. */
/* Тосты стоят над панелью графика, у правого края подвала: в правом нижнем
   углу они садились на пик спектра, а под шапкой — на кнопку «Переснять».
   Здесь пусто на всех листах, и щелчки сквозь них проходят. */
.toasts { position: fixed; right: 24px; bottom: calc(var(--chart-h) + 10px); display: grid; gap: 8px;
  z-index: 50; pointer-events: none; justify-items: end; }
/* Две живые области — вежливая и настойчивая — стоят одна над другой в общей
   обёртке; обёртка нарочно без роли, вложенных живых областей быть не должно. */
.toasts > div { display: grid; gap: 8px; justify-items: end; }
.toast { padding: 10px 14px; border-radius: var(--radius); background: var(--ink); color: #fff; max-width: 480px; box-shadow: 0 6px 20px rgba(0,0,0,.2); pointer-events: auto; }
.toast.bad { background: var(--bad); }
.toast.ok { background: var(--ok); }
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,.35); display: grid; place-items: center; z-index: 40; }
/* Под рисунком во весь экран фон гасится сильнее: смотрят фотографию, а не лист. */
.overlay:has(.lightbox-box) { background: rgba(0,0,0,.85); z-index: 60; }
.modal { width: 520px; max-width: calc(100% - 32px); background: var(--paper); border-radius: 12px; padding: 20px; display: grid; gap: 12px; box-shadow: 0 20px 60px rgba(0,0,0,.3); }
.modal h2 { margin: 0; font-size: 18px; }
.modal .buttons { display: flex; gap: 10px; justify-content: flex-end; }
/* Список выбора в окне: строка — кнопка целиком, нижняя граница нажимаемого
   у поста та же, что у остальных окон (сенсорный экран и перчатки). */
.modal .choices { display: grid; gap: 6px; }
.modal .choice { display: grid; gap: 2px; justify-items: start; text-align: left; width: 100%;
  min-height: 44px; padding: 6px 12px; }
.modal .choice.current { opacity: .6; }
.modal .choice .lab { font-weight: 600; }
/* Запрос помощи (карта поддержки, решение tech lead 08.10.2026, `support.js`).
   Кнопка — у названия шага справа: ряд шапки АРМ занят до края. Цвет — акцент
   рамкой, как у прочих небоевых кнопок, без заливки: это не действие шага. */
.support-btn { flex: none; color: var(--accent); border-color: var(--accent); font-weight: 600; white-space: nowrap; }
.title-row .support-btn { margin-left: auto; }
/* Лист со своей формой в узкой колонке режима: кнопка стоит строкой выше, в
   шапке колонки (`tuning.js`), а здесь сжала бы заголовок в две строки. */
.tuning-do .ownform .title-row .support-btn { display: none; }
/* Полоса состояния запроса — под шапкой шага; работу с шагом не запирает. */
.support-strip { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; padding: 6px 12px;
  margin: 8px 0 4px; border-radius: var(--radius); border-left: 4px solid var(--accent);
  background: var(--accent-bg); color: var(--ink); font-size: 14px; }
.support-strip .sl { font-weight: 600; }
.support-strip .sv { min-width: 0; overflow-wrap: anywhere; }
.support-strip button.small { min-height: 30px; }
.support-strip.answered { border-left-color: var(--ok); background: var(--ok-bg); }
/* Свежий ответ поддержки — пульсирующая рамка, пока наладчик не коснулся полосы. */
.support-strip.fresh { box-shadow: 0 0 0 3px var(--ok); animation: support-fresh 1.2s ease-in-out infinite alternate; }
@keyframes support-fresh { from { box-shadow: 0 0 0 1px var(--ok); } to { box-shadow: 0 0 0 4px var(--ok); } }
/* Переписка с поддержкой — раскрывается из полосы (спека 10.10.2026 §8.5). */
.support-strip.history { border-left-color: var(--line, #c5cfd2); background: var(--panel, #f6f8f9); }
.support-unread { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 6px; border-radius: 9px;
  background: var(--bad, #c0392b); color: #fff; font-size: 12px; font-weight: 700; text-align: center; }
.support-box { margin: 8px 0 4px; border: 1px solid var(--line, #d5dde0); border-radius: var(--radius); }
.support-box .support-strip { margin: 0; border-radius: var(--radius) var(--radius) 0 0; }
.support-chat { padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; }
.support-thread { max-height: 300px; overflow: auto; display: flex; flex-direction: column; gap: 6px; padding-right: 4px; }
.support-sep { border-top: 1px dashed var(--line, #c5cfd2); padding-top: 6px; font-size: 13px; color: var(--muted, #5b6a72); }
.support-sep.end { color: var(--ok); }
.support-sep .kind { margin-left: 6px; font-weight: 600; color: var(--ink); }
.support-note { font-size: 13px; color: var(--muted, #5b6a72); }
.support-row { display: flex; flex-direction: column; max-width: 85%; }
.support-row.arm { align-self: flex-end; align-items: flex-end; }
.support-head { font-size: 12px; color: var(--muted, #5b6a72); display: flex; gap: 8px; }
.support-msg { white-space: pre-wrap; overflow-wrap: anywhere; padding: 6px 10px; border-radius: 10px; font-size: 14px; margin-top: 2px; }
.support-msg.support { background: var(--ok-bg); }
.support-msg.arm { background: var(--accent-bg); }
.support-field { width: 100%; min-height: 56px; box-sizing: border-box; resize: vertical; font: inherit; }
.support-send { display: flex; align-items: center; gap: 8px; }
.support-send .grow { flex: 1; }
.support-att { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.support-thumb { max-width: 160px; max-height: 120px; border-radius: 6px; border: 1px solid var(--line); cursor: zoom-in; object-fit: cover; }
.support-video { max-width: 260px; max-height: 180px; border-radius: 6px; background: #000; }
.support-pending { display: flex; flex-wrap: wrap; gap: 6px; }
.support-chip { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--line); border-radius: 12px;
  padding: 0 4px 0 10px; font-size: 13px; background: var(--paper); }
.support-chip.up { padding-right: 10px; color: var(--muted, #5b6a72); }
.support-chip button { min-height: 24px; padding: 0 6px; }
/* Полоса под шапкой шага — одна строка (макет tech lead 11.10.2026); ответ
   обрезается многоточием, целиком — в панели переписки. */
.support-strip.one { flex-wrap: nowrap; cursor: pointer; }
.support-strip.one .sv { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Номер запроса «30170-3» — его называют по телефону (решение tech lead 12.10.2026). */
.support-strip .sno { flex: none; font-size: 12.5px; font-weight: 700; padding: 1px 7px; border-radius: 9px;
  background: var(--paper); border: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.support-panel-state .sno-line { font-size: 12.5px; color: var(--muted, #5b6a72); }
/* Кнопка «Поддержка» в шапке: подсвечена, пока запрос открыт. */
.support-headbtn.live { border-color: var(--ok); color: var(--ok); font-weight: 600; }
/* Панель переписки — третья колонка, на всю высоту: шапка, состояние, лента
   (прокручивается сама), поле внизу. */
.drawer.support-panel { display: flex; flex-direction: column; gap: 0; padding: 0; overflow: hidden; }
.support-panel-head { padding: 12px 14px; border-bottom: 1px solid var(--line); gap: 8px; align-items: center; }
.support-panel-state { padding: 8px 14px; border-bottom: 1px solid var(--line); font-size: 14px; background: var(--accent-bg); }
.support-panel-state.answered { background: var(--ok-bg); }
.support-panel-state > div { overflow-wrap: anywhere; }
.support-panel .support-chat { flex: 1; min-height: 0; padding: 0; gap: 0; display: flex; flex-direction: column; }
.support-panel .support-thread { flex: 1; min-height: 0; max-height: none; padding: 12px 14px; gap: 8px; background: var(--bg); }
.support-panel .support-row { max-width: 84%; }
.support-panel .support-row.support { align-self: flex-start; align-items: flex-start; }
.support-panel .support-msg { font-size: 14.5px; line-height: 1.35; padding: 8px 11px; border-radius: 12px; }
.support-panel .support-msg.arm { background: var(--accent-bg); border-bottom-right-radius: 4px; }
.support-panel .support-msg.support { background: var(--paper); border: 1px solid var(--line); border-bottom-left-radius: 4px; }
.support-panel .support-head { font-size: 11.5px; flex-wrap: wrap; gap: 0 6px; }
.support-panel .support-note { text-align: center; }
.support-foot { border-top: 1px solid var(--line); padding: 10px 14px; display: grid; gap: 8px; background: var(--paper); }
.support-foot .support-field { min-height: 64px; }
.support-foot .support-send { flex-wrap: wrap; }
.support-modal .support-kinds { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
.support-modal .choice[aria-checked="true"] { border-color: var(--accent); background: var(--accent-bg);
  box-shadow: inset 0 0 0 1px var(--accent); }
.support-modal .support-label { font-weight: 600; font-size: 14px; margin-bottom: -6px; }
.support-modal .support-attached { margin: 0; }
/* Лайтбокс — такое же окно, как диалоги: своя роль, имя и ловушка Tab
   (`ui.js::lightbox` через `openModal`), фон под ним выключен `inert`. */
.lightbox-box { display: grid; gap: 10px; justify-items: center; max-width: 96vw; }
.lightbox-box img { max-width: 96vw; max-height: 68vh; cursor: zoom-out; background: #fff; border-radius: 8px; }
.lightbox-bar { display: flex; gap: 16px; align-items: center; color: #fff; }
/* Подпись рисунка — свой класс, а не `.cap` карточки кадра: тот приносил белый
   фон карточки, и белый текст подписи на нём был не виден — наладчик видел
   пустое поле под рисунком (замечание 30.09.2026). Кегль крупный по просьбе
   наладчика: подпись читают с расстояния от поста. */
.lightbox-bar .lb-cap { display: block; max-width: 80vw; color: #fff; background: none;
  font-size: clamp(22px, 2.6vw, 36px); font-weight: 600; line-height: 1.25; text-align: center; }
/* Текст подшага бывает в три-четыре строки — кегль чуть меньше заголовочного. */
.lightbox-bar .lb-lead { font-size: clamp(20px, 2vw, 30px); }
.lightbox-bar .lb-fig { margin-top: 6px; font-size: clamp(15px, 1.2vw, 18px); font-weight: 400; opacity: .8; }
.lightbox-bar button { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.4); color: #fff; }
/* Выдвижная панель — третья колонка сетки, а не наложение: накрыть лист и
   график она не может. */
/* Ширину панели наладчик тянет ручкой, натянутое число приезжает переменной
   `--drawer-w` на сетку. Пределы — здесь, а не в браузерном коде: уже 300 px
   органы панели «Прибор» (селекты 190 px с подписями) встают в две строки,
   шире 46 % окна панель съедает лист. */
.drawer { width: var(--drawer-w); min-width: 300px; max-width: min(720px, 46vw);
  overflow: auto; background: var(--paper); border-left: 1px solid var(--line); padding: 16px; display: grid; gap: 14px; align-content: start; }
.drawer h3 { margin: 0; font-size: 16px; }
.params { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.params label { display: grid; gap: 2px; font-size: 13px; color: var(--ink-2); }
/* Карточки базового набора серии шкалы (`.row.bases`) сняты 20.09.2026 вместе
   с самим понятием базового набора: набор правится одной формой, общей с
   панелью «Прибор». */
.toggle { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line-2); gap: 12px; }
/* Подпись подсистемы — цель щелчка вместе с флажком: целиться в квадратик
   18 px у поста неудобно (как в первом АРМ, `label for`). */
.toggle > label { display: flex; align-items: center; gap: 8px; cursor: pointer; flex: 1 1 auto; min-width: 0; }
.toggle input[type=checkbox] { width: 22px; height: 22px; flex: none; cursor: pointer; }

/* Панель «Прибор»: строка «подпись — орган» и ряд органов. */
.row.spread { justify-content: space-between; flex-wrap: nowrap; gap: 10px; }
.row.spread > label { color: var(--ink-2); font-size: 13px; }
.row.gap { gap: 8px; }
.row.gap > label { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ink-2); }
.row.gap input[type=number] { width: 76px; }
.drawer select, .drawer input[type=number] { max-width: 190px; }
/* Три состояния поля набора выстрела — решение tech lead 20.09.2026 (просьба
   наладчика 18.09.2026: «обозначать зелёным ячейку, если подтвердили, или
   красным, если нет»). Правило ОБЩЕЕ на панель «Прибор» и на форму шага:
   селектор без `.drawer`, класс ставит один механизм (`shot-sync.js`).

   Это состояние ОРГАНА, а не вердикт кадра, поэтому у синего своя пара цветов,
   а зелёный и красный берутся у вердиктов сознательно: наладчик читает их как
   «сошлось / не сошлось», и второй зелёный на экране был бы лишним словом.
   Рамка толще обычной на 1 px — цвет один не различим на посту при верхнем
   свете (замер не нужен: правило из первого АРМ). */
input.pok { border-color: var(--ok); background: var(--ok-bg); }
input.pdirty { border-color: var(--bad); background: var(--bad-bg); }
input.pset { border-color: var(--pset); background: var(--pset-bg); box-shadow: 0 0 0 1px var(--pset); }
/* Плашка «ставлю параметры съёмки…»: набор перед кадром ставит АРМ, и пока он
   это делает, у наладчика под руками кнопка съёмки. Пустая плашка спрятана
   атрибутом `hidden` и места не занимает. */
.shotplate { font-size: 13px; color: var(--accent); background: var(--accent-bg);
  border-radius: var(--radius); padding: 4px 8px; }
.shotplate[hidden] { display: none; }
.drawer button.active { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
.drawer .ok-text { color: var(--ok); }
.drawer details > summary { font-weight: 600; }
.raster-points { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px;
  font-variant-numeric: tabular-nums; }
.raster-points th, .raster-points td { text-align: right; padding: 2px 6px; border-bottom: 1px solid var(--line-2); }
.raster-points th { color: var(--ink-2); font-weight: 600; }

/* --- панель «Кадры» ---
   Та же третья колонка сетки, что и «Прибор»: лист она не накрывает, а её
   собственный список прокручивается внутри панели. Строка кадра — одна
   строка: время слева, действия справа, всё прочее ужимается и режется
   многоточием, иначе длинное имя клетки распирает колонку. */
.frames-panel { gap: 10px; }
.frames { display: grid; gap: 12px; min-width: 0; }
.fgroup { display: grid; gap: 4px; min-width: 0; }
.fhead { display: flex; gap: 8px; align-items: baseline; position: sticky; top: 0;
  background: var(--paper); padding: 4px 0 2px; border-bottom: 1px solid var(--line-2); }
/* Номер шага — жирнее и своим блоком (просьба наладчика 14.09.2026): у
   группы «Съёмка с панели „Прибор“» своего шага нет, и узла `.n` там не
   будет вовсе (frames-panel.js). */
.fhead .n { font-weight: 700; font-size: 15px; flex: none; color: var(--ink-2); }
.fhead .t { font-weight: 600; font-size: 15px; flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.flist { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
/* Строка кадра — две дорожки: сверху время и описание во всю ширину, снизу
   органы справа. В одну строку описание кадра в колонке 380 px ужималось до
   трёх слов, а по нему наладчик и узнаёт своё измерение. */
.frow { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; font-size: 13px;
  min-width: 0; padding-bottom: 4px; border-bottom: 1px solid var(--line-2); }
.frow .when { flex: none; font-variant-numeric: tabular-nums; }
.frow .what { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.frow .acts { flex: 1 1 100%; display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.frow.bad .what { color: var(--bad); }
/* Заменённый и убранный кадр читается тише действующего: он в списке ради
   ревизии, а не ради работы. */
.frow.past .what { color: var(--ink-3); }
/* Орган у строки один — кнопка-тогл «На график». Флажка «наложить» рядом
   больше нет (09.09.2026): он делал ровно то же. */
.frow button { flex: none; }
/* --- панель «Протокол» (09.09.2026) ---
   Та же третья колонка сетки, что у «Кадров» и «Прибора». Отличие одно: здесь
   прокручивается СПИСОК, а не вся панель. Протокол читается снизу — там
   последнее действие, — и панель доводит себя до конца при открытии; уехавшая
   вместе со списком кнопка «Закрыть» после этого оказалась бы за краем.
   Дорожек ровно три, и узлов у панели ровно три (шапка, счётчик, список):
   лишний узел в этой сетке уедет не туда, как в `.main` и `.work`. */
.protocol-panel { gap: 10px; overflow: hidden; grid-template-rows: auto auto minmax(0, 1fr);
  align-content: stretch; }
.protocol { display: grid; gap: 12px; min-width: 0; overflow: auto; align-content: start; }
.pgroup { display: grid; gap: 4px; min-width: 0; }
.phead { display: flex; gap: 8px; align-items: baseline; position: sticky; top: 0;
  background: var(--paper); padding: 4px 0 2px; border-bottom: 1px solid var(--line-2); }
.phead .t { font-weight: 600; font-size: 13px; flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plist { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
/* Строка записи — две дорожки, как у кадра: сверху номер, время и действие,
   снизу имя наладчика справа. В одну строку действие в колонке 380 px ужималось
   до двух слов, а действие — это и есть протокол. */
.prow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; font-size: 13px;
  min-width: 0; padding-bottom: 4px; border-bottom: 1px solid var(--line-2); }
.prow .no, .prow .when { flex: none; font-variant-numeric: tabular-nums; }
.prow .what { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prow .who { flex: 1 1 100%; text-align: right; }
/* Отказ и отступление в доказательном документе не имеют права выглядеть как
   обычное действие. Цвета вердиктов, а не темы. */
.prow.bad .what { color: var(--bad); }
.prow.mark .what { color: var(--warn); }
/* --- панель «Разработчик» (09.09.2026) ---
   Та же третья колонка сетки, что у «Кадров» и «Протокола». Дорожек ровно
   четыре, и узлов у панели ровно четыре (шапка, переключатель шагов, разбор,
   органы отладки): лишний узел в этой сетке уедет не туда, как в `.main` и
   `.work`. Прокручивается РАЗБОР, а не панель: уехавшие с ним «Закрыть» и
   переключатель шагов оказались бы за краем — а по переключателю и ходят.
   Таблицы разбора широкие (линия, точка, невязка, кадры, причина), поэтому
   каждая прокручивается вбок сама: перенос строки в таблице невязок делает
   её нечитаемой, а колонку панели растягивать нельзя — она отнимает лист. */
.dev-panel { gap: 10px; overflow: hidden; grid-template-rows: auto auto minmax(0, 1fr) auto;
  align-content: stretch; }
.devtabs { display: flex; flex-wrap: wrap; gap: 4px; max-height: 132px; overflow: auto; }
.devtabs button.on { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }
/* Шаг без разбора виден, но не нажимается: молчащий переключатель читается
   как поломка, а исчезнувшие шаги — как «их не бывает». */
.devtabs button:disabled { opacity: .45; }
.devbody { display: grid; gap: 14px; min-width: 0; overflow: auto; align-content: start; }
.devhead { font-weight: 600; display: flex; gap: 8px; align-items: baseline; }
.dsec { display: grid; gap: 4px; min-width: 0; }
.shead { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
  border-bottom: 1px solid var(--line-2); padding-bottom: 2px; }
.shead .t { font-weight: 600; font-size: 13px; }
.shead .n { flex: 1 1 100%; }
.dlist { margin: 2px 0 0; padding-left: 18px; font-size: 13px; }
.dtab { border-collapse: collapse; font-size: 12px; display: block; overflow-x: auto;
  white-space: nowrap; max-width: 100%; }
.dtab th { text-align: left; color: var(--ink-2); font-weight: 500; padding: 2px 10px 2px 0; }
.dtab td { padding: 2px 10px 2px 0; vertical-align: top; font-variant-numeric: tabular-nums; }
.dtab td:first-child { font-variant-numeric: normal; }
/* Состояние строки ставит сервер — здесь только цвета вердиктов, а не темы. */
.dtab tr.ok td:last-child { color: var(--ok); }
.dtab tr.bad td { color: var(--bad); }
.dtab tr.warn td:last-child { color: var(--warn); }
/* Разбор «ключ — значение» уже, чем таблица, и не прокручивается: в нём две
   колонки, и вторая переносится по словам. */
.dtab.kv { white-space: normal; }
.dtab.kv td:last-child { white-space: normal; }
.devtools { border-top: 1px solid var(--line); padding-top: 8px; display: grid; gap: 8px; font-size: 13px; }
.devtools summary { color: var(--ink-2); }

.summary { display: grid; gap: 14px; max-width: 760px; }
.summary ul { margin: 6px 0 0; padding-left: 18px; }

/* --- режим настройки (15.09.2026) ------------------------------------------
   Второй ВИД того же шага: работа делится не на строки, а на КОЛОНКИ —
   «делаем» и сцена (спектр во всю высоту, под ним разбор выбранного кадра).
   Обычный лист от этих правил не двигается ни на пиксель: они живут на
   `.work.tuning`, а он ставится только в режиме. */
.work.tuning { grid-template-columns: var(--do-w) auto minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
/* Спектр спрятан язычком — колонки сцены и ручки нет вовсе, «делаем» занимает
   всю ширину. Узлов в сетке ровно столько, сколько дорожек, и `app.js` кладёт
   их по тому же условию. */
.work.tuning.chart-off { grid-template-columns: minmax(0, 1fr); }
.work.tuning .tuning-do .head .titlerow { display: flex; gap: 10px; align-items: center; }
.work.tuning .tuning-do .head .titlerow .eyebrow { flex: 1 1 auto; min-width: 0; }

/* --- лист со своей формой в колонке (шкала, термоиспытания, «до сборки») ---
   Тот же лист, что на обычном экране, только колонка узкая: всё, что там
   стояло в РЯД, встаёт друг под друга и прокручивается. Своих слов и чисел
   правила не заводят — только направление. */
.tuning-do .ownform { display: grid; gap: 0; min-width: 0; }
.tuning-do .ownform .sheet-head > .no { width: 30px; height: 30px; font-size: 15px; }
.tuning-do .ownform h1 { font-size: 17px; }
.tuning-do .ownform .goal { margin: 4px 0 10px; font-size: 13px; }
/* Таблица серии и программы: строка становится карточкой, ячейки — строками.
   Подписи столбцов остаются видимыми (их даёт сервер) и встают заголовком
   над списком, а не прячутся: «Уровень затвора от базового набора серии
   (×1/2)» — это слово о комплекте, а не украшение шапки. */
.tuning-do .ownform .grid { display: block; border: 0; background: none; }
.tuning-do .ownform .grid thead { display: block; }
.tuning-do .ownform .grid thead tr { display: grid; gap: 0; }
.tuning-do .ownform .grid th { display: block; border: 0; background: none; padding: 0 0 2px; }
.tuning-do .ownform .grid tbody { display: grid; gap: 8px; }
.tuning-do .ownform .grid tr { display: grid; gap: 4px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); }
.tuning-do .ownform .grid td { display: block; border: 0; padding: 0; }
.tuning-do .ownform .grid tr.group { background: var(--bg); }
/* Заголовок блока: номер, название и пояснение встают друг под друга, а не
   делят одну строку на троих — иначе пояснение рвётся по слову в строке. */
.tuning-do .ownform .block { flex-wrap: wrap; gap: 6px 10px; }
.tuning-do .ownform .block .h { flex: 1 1 auto; min-width: 0; }
.tuning-do .ownform .block .muted { flex: 1 1 100%; }
/* Подпись комплекта — своей строкой во всю ширину: в узкой колонке она
   ужималась до двух слов в столбик. */
.tuning-do .ownform .block .kit { flex: 1 1 100%; justify-content: flex-start; }
/* Точки термоиспытаний и ряды образцов — тоже сверху вниз. */
.tuning-do .ownform .pointsec .point { max-width: none; }
.tuning-do .ownform .samplerow, .tuning-do .ownform .row { flex-wrap: wrap; }
.tuning-do .ownform .card.result { padding: 10px 12px; }
.tuning-do .ownform .fitbtn, .tuning-do .ownform button.big { width: 100%; }
.tuning-do { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; min-height: 0;
  border-right: 1px solid var(--line); background: var(--paper); }
.tuning-do .head { padding: 10px 14px; border-bottom: 1px solid var(--line); }
.tuning-do .head .eyebrow { font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--accent); }
.tuning-do .head h1 { margin: 2px 0 0; font-size: 18px; }
.tuning-do .body { overflow: auto; padding: 10px 12px; display: grid; gap: 8px; align-content: start; }
/* Свой класс, а не `.lab`: в `.lab` кнопка держит свою подпись (инвариант
   «кнопка в ожидании держит габарит»), и общий селектор написал бы каждую
   кнопку колонки капслоком. */
.tuning-do .tlab { font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2); }
/* Чек-лист шага стоит ЦЕЛИКОМ (просьба наладчика 15.09.2026), поэтому строки
   плотнее, чем на листе: на шаге 1 их десять, и колонку они делят с кадрами и
   подвалом. Подсвечен первый неотмеченный — «сейчас делаете». */
.tuning-do .nowhead { display: flex; gap: 8px; align-items: center; justify-content: space-between; }
.tuning-do .items { display: grid; gap: 4px; }
.tuning-do .item.now { border-color: var(--accent); background: var(--accent-bg); }
.tuning-do .item.now.done { border-color: #b7e2c2; }
/* Пункт в колонке шире, чем на листе: у листа справа своя колонка под отметку
   времени и подсказку (128 px), а здесь эти же 128 px оставляли тексту сотню
   пикселей, и длинный пункт («Затяжка винтов: головка, объектив, …») вставал
   по одному слову в строку. Отметка уходит СТРОКОЙ ниже, а не колонкой. */
.tuning-do .items, .tuning-do .item { min-width: 0; }
.tuning-do .item { grid-template-columns: 26px minmax(0, 1fr); padding: 5px 8px; gap: 0 8px;
  font-size: 14px; }
/* Отметка времени — мелкой строкой ПОД текстом и во всю ширину: колонкой
   справа она забирала у длинных пунктов треть места, и «Комплект на посту:
   шасси, эталонные образцы …» вставал в восемь строк. Пункта без отметки это
   не касается — пустой узел не рисуется. */
.tuning-do .item > .side { grid-column: 2; grid-row: 2; justify-items: start; }
.tuning-do .item > .side:empty { display: none; }
.tuning-do .item .side .tick, .tuning-do .item .side .meta { text-align: left; }
.tuning-do .item input[type=checkbox] { width: 22px; height: 22px; margin: 1px 0 0; }
.tuning-do .item .text { padding-top: 1px; }
/* Время отметки и подсказка — мелкой строкой под текстом, и только там, где
   они есть: у десяти пунктов это десять лишних строк на ровном месте. */
.tuning-do .item .side .tick, .tuning-do .item .side .meta { font-size: 12px; margin-top: 1px;
  white-space: nowrap; text-align: left; }
.tuning-do .item .text { overflow-wrap: anywhere; }
/* Строка кадра: точка вердикта, имя, кнопка съёмки. Ключ строки — `cap.id`,
   как в списках листа: сверка `dom.js` переставляет их, а не пересобирает. */
/* Строка кадра: сверху точка вердикта, имя и кнопка съёмки, под ними — образец
   и минимальная справка (просьба наладчика 15.09.2026: «не хватает информации
   слева над кнопкой съёмки, какой образец мы снимаем»). Текст под кнопкой, а
   не рядом с ней: рядом он ужал бы имя кадра до многоточия. */
.tuning-do .tframe { display: grid; gap: 2px; padding: 5px 8px;
  border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer; }
.tuning-do .tframe > .row { display: flex; gap: 8px; align-items: center; }
.tuning-do .tframe:hover { background: var(--bg); }
.tuning-do .tframe.sel { border-color: var(--accent); background: var(--accent-bg); }
.tuning-do .tframe .t { flex: 1 1 auto; min-width: 0; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tuning-do .tframe .dot { flex: none; }
.tuning-do .tframe .sample { font-weight: 600; }
/* Строки показа сдвигов (`.tshift`/`.tshift-strong`) сняты 20.09.2026 вместе с
   самой строкой: набор виден только на шаге, где его и крутят. */
/* Поля набора выстрела на самом шаге (17.09.2026): подпись над полем, поля в
   строку, кнопка «Задать» рядом. Поле узкое — числа набора четырёхзначные, а
   колонку режима отнимать у чек-листа нельзя. */
.tuning-do .tshot { display: grid; gap: 4px; padding: 2px 8px; }
.tuning-do .tshotf { display: grid; gap: 2px; font-size: 12px; color: var(--ink-2); }
/* 104 px и селектор с `[type=number]`, а не голый `input`: у общего правила
   `.row.gap input[type=number]` (76 px) вес выше, и поле формы набора всегда
   стояло 76 px, сколько бы ни было написано здесь. На 76 px пятизначная
   «Задержка, нс» (25000) резалась стрелками поля — замер CDP 20.09.2026:
   `getComputedStyle().width` 76 px при `scrollWidth` 81. Ширину колонки это не
   меняет: поля стоят рядами и переносятся. */
.tuning-do .tshotf input[type=number] { width: 104px; font-size: 16px; font-weight: 700;
  font-variant-numeric: tabular-nums; }
.tuning-do .tshot .row { align-items: end; }
/* Кнопка съёмки в строке не переносится: перенос делал строку вдвое выше и
   двигал соседние. Ужимается имя кадра — оно с многоточием и подсказкой. */
.tuning-do .tframe button { flex: none; white-space: nowrap; }
.tuning-do .foot { border-top: 1px solid var(--line); display: grid; gap: 6px; padding-bottom: 10px; }
.tuning-do .foot .footer { border-top: 0; padding: 10px 12px 4px; }
/* В колонке место под помехи закрытия не отведено заранее, как на листе: там
   оно бережёт от прыжков панель графика под подвалом, а здесь под подвалом
   ничего нет. Потолок — две строки, дальше прокрутка. */
.tuning-do .foot .footer .problems { height: auto; max-height: 2.6em; line-height: 1.3;
  font-size: 13px; }
.tuning-do .foot > button { justify-self: start; margin-left: 12px; }

/* Дорожка разбора — РОВНО 36 % сцены, а не «сколько попросит, но не больше
   36 %» (решение наладчика 17.09.2026). Замером разницы поймать не удалось:
   на всех 16 листах и на двух разрешениях обе записи дали одно и то же
   (алгоритм сетки доводит нефлексируемую дорожку до её границы роста раньше,
   чем `1fr` заберёт остаток). Оставлено как явное намерение: высота панели не
   имеет права идти за содержимым, а что не влезло — прокручивается внутри
   (`overflow: auto` у `.tuning-verdicts`). */
.tuning-stage { display: grid; grid-template-rows: minmax(0, 1fr) 36%; min-height: 0; }
/* Шаг без кадров: узел разбора не кладётся, и дорожка у сцены одна. Но график
   от этого не растёт на весь экран — он остаётся той же высоты, что на шагах
   с разбором (просьба наладчика 15.09.2026): на длинных листах, которые
   прокручивают (шкала, термоиспытания, «до сборки»), спектр во всю высоту
   читается как другой экран, а он тот же. */
.tuning-stage.nocaps { grid-template-rows: minmax(0, 64%); }
/* Простой экран: карточка пункта на месте спектра, вердикты кадров под ней.
   Карточка прокручивается сама — рисунки бывают высокими. */
.tuning-stage.simple { grid-template-rows: minmax(0, 1fr) auto; }
.tuning-stage.simple > .tuning-verdicts { max-height: 40vh; overflow: auto; }
/* Шаг шкалы на простом экране (пакет H, 15.10.2026): карточка образца и, у
   разработчика, отчёт расчёта под ней — одной прокручиваемой колонкой. */
.tuning-stage.simple > .scale-stage { overflow: auto; min-height: 0; display: flex; flex-direction: column; }
.tuning-stage.simple > .scale-stage > .item-card { overflow: visible; }
/* Свежие точки образца на карточке (как у мастера): строка сервера и «Образец переставлен». */
.item-card .card-spots { display: flex; align-items: center; gap: 10px; justify-content: space-between; max-width: 680px;
  background: var(--line-2); border-radius: var(--radius); padding: 8px 12px; margin: 0 0 12px; font-size: 14px; color: var(--ink-2); }
.item-card .card-spots.out { background: var(--act-bg); color: var(--act); font-weight: 600; }
.item-card { overflow: auto; padding: 18px 24px; background: var(--paper); min-height: 0; }
.item-card .eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); font-weight: 700; }
.item-card h2 { font-size: 22px; margin: 4px 0 8px; }
.item-card .state { display: inline-block; padding: 4px 10px; border-radius: var(--radius); font-size: 14px; margin-bottom: 12px; }
.item-card .state.now { background: var(--accent-bg); color: var(--accent); }
.item-card .state.done { background: var(--ok-bg); color: var(--ok); }
.item-card .state.waits { background: var(--line-2); color: var(--ink-2); }
.item-card .serial { margin: 4px 0 16px; display: grid; gap: 6px; max-width: 520px; }
.item-card .serial.ok { color: var(--ok); font-weight: 700; }
.item-card .serial .row { display: flex; gap: 10px; }
.item-card .serial input { flex: 1 1 auto; min-width: 0; font-size: 20px; padding: 8px 12px; font-variant-numeric: tabular-nums; }
/* Форма прожига окружности: подпись — поле — пределы сервера в строку. */
.item-card .serial.burn { max-width: 680px; gap: 10px; }
.item-card .serial.burn .row { flex-wrap: nowrap; }
.item-card .serial.burn input { flex: 1 1 0; }
.item-card .serial .burn-lab { flex: 0 0 220px; align-self: center; font-size: 15px; }
.item-card .serial .burn-range { flex: 0 0 150px; align-self: center; white-space: nowrap; }
.item-card .done-row { margin: 0 0 16px; }
.item-card .acts { display: grid; gap: 16px; }
.item-card .act p { font-size: 16px; line-height: 1.5; margin: 0 0 8px; }
.item-card .card-figs { display: flex; flex-wrap: wrap; gap: 12px; }
.item-card .card-figs .figbtn { max-width: 100%; min-width: 0; }
.item-card .card-figs img { width: auto; height: auto; max-width: min(100%, 560px); max-height: 360px; }
/* Карточка кадра простого экрана шагов после мастера (14.10.2026): вердикт
   ОДНОЙ фразой, заливка — тон сервера (`Verdict.tone`), служебное — под катом
   «Подробнее» (общий орган `details > summary`). */
.item-card .card-sample { font-size: 16px; font-weight: 600; margin: 0 0 6px; }
.item-card .vfill { padding: 10px 14px; border-radius: var(--radius); margin: 0 0 12px; max-width: 680px;
  background: var(--line-2); color: var(--ink); }
.item-card .vfill .vtitle { font-size: 18px; font-weight: 700; }
.item-card .vfill .rung { font-size: 16px; margin-top: 4px; }
.item-card .vfill > button { margin-top: 8px; }
.item-card .vfill.ok { background: var(--ok-bg); color: var(--ok); }
.item-card .vfill.act { background: var(--act-bg); color: var(--act); }
.item-card .vfill.bad { background: var(--bad-bg); color: var(--bad); }
/* Строки частей карточки шагов чека (пакет L): часть, её итог словом сервера и
   «К шагу N» у красной; заливка — тон сервера, как у строки вердикта. */
.item-card .vparts { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.item-card .vpart { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 4px 8px;
  border-radius: var(--radius); background: var(--paper); color: var(--ink); }
.item-card .vpart .pname { font-weight: 700; min-width: 120px; }
.item-card .vpart.ok { background: var(--ok-bg); color: var(--ok); }
.item-card .vpart.bad { background: var(--bad-bg); color: var(--bad); }
.item-card .vpart > button { margin-left: auto; }
.item-card .vfill .rung.note { font-weight: 700; }
.item-card .joint-more { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 6px; }
/* Части вердикта серии мультискана (пакет J): своя строка и свой цвет у каждой. */
.item-card .vfill .vparts { margin-top: 4px; font-size: 15px; }
.item-card .vfill .vpart.ok { color: var(--ok); }
.item-card .vfill .vpart.bad { color: var(--bad); }
.item-card .card-more { max-width: 820px; }
.tuning-do .tframe.pick { cursor: pointer; }
.tuning-do .tframe .dot.ok { background: var(--ok); }
.tuning-do .tframe .dot.bad { background: var(--bad); }
.tuning-do .tframe .dot.act { background: var(--act); }
/* Карточка-подсказка ступени `item:<id>` с галочкой «сделал». */
.item-card .ladder-card { margin-top: 8px; padding: 8px 10px; border-radius: var(--radius); background: var(--paper);
  color: var(--ink); border: 1px solid var(--line); }
.item-card .ladder-card .ltitle { font-weight: 700; }
.item-card .ladder-card p { margin: 4px 0; }
.item-card .ladder-card .ldone { display: inline-flex; gap: 6px; align-items: center; font-weight: 600; cursor: pointer; }
.item-card .ladder-card .lafter { margin-top: 4px; font-weight: 700; color: var(--act); }
.item-card .ladder-card .lmedia { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0; }
.item-card .ladder-card .lmove { display: flex; gap: 8px; align-items: center; margin: 4px 0; }
.item-card .ladder-card .lmedia figure { margin: 0; max-width: 220px; }
.item-card .ladder-card .lmedia img { max-width: 100%; border-radius: var(--radius); }
/* «Спектр ▸» простого экрана: панель последней серии. Эталон, «Сбросить» и
   подпись типовой шкалы — только учётке `developer` (решение tech lead
   14.10.2026); прячет их класс `lite`, компонент ui-kit не трогается. */
.tuning-stage.simple > .wiz-foot { padding: 0 24px 12px; }
.tuning-stage.simple > .wiz-foot > .wiz-spec { display: flex; flex-direction: column; gap: 6px; }
.tuning-stage.simple > .wiz-foot > .wiz-spec > button { align-self: flex-start; }
/* Канва — своей высоты: правило режима настройки тянет её на всю дорожку. */
.work.tuning .tuning-stage.simple > .wiz-foot > .wiz-spec .sp-canvas { flex: none; height: var(--canvas-h); }
.tuning-stage.simple > .wiz-foot > .wiz-spec { max-height: 60vh; overflow: auto; }
.chartbox.lite [data-k=chart-reset], .chartbox.lite [data-k=ref], .chartbox.lite .bar > label,
.chartbox.lite .note-scale { display: none; }
/* Панель спектра в режиме занимает свою дорожку целиком: канва тянется, а не
   стоит фиксированной высотой `--canvas-h`. Компонент ui-kit при этом не
   трогается — правится раскладка страницы, как и на листе. */
.work.tuning .chartbox { border-top: 0; display: flex; flex-direction: column; min-height: 0; }
.work.tuning .chartbox .bar, .work.tuning .chartbox .placeholder { flex: none; }
/* Тянется вся цепочка до канвы: канва — не ребёнок `.chartbox`, а внук
   (`.chartwrap` — дом ui-kit, внутрь которого сверка `dom.js` не заходит).
   Растяжка одного `.sp-canvas` давала высоту 0 — flex действует только на
   прямых детей. */
.work.tuning .chartbox > .chartwrap { flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; }
.work.tuning .chartbox > .chartwrap > .sp-tools { flex: none; }
.work.tuning .chartbox .sp-canvas { flex: 1 1 auto; height: auto; min-height: 120px; }
/* Разборы кадров шага: по одному на кадр, рядом. Больше двух рядом не ставим
   (`SIDE_BY_SIDE` в `tuning.js`) — четыре колонки на шагах 13–14 читались бы
   как «слишком много букв». Дорожка у сцены одна, прокручивается ОНА, а не
   каждый разбор по отдельности: две полосы прокрутки рядом наладчик крутит
   по очереди и теряет строку. */
/* Фон дорожки — бумага, а не цвет линии: под коротким разбором оставалась
   серая полоса до низа экрана (просьба тестировщика 23.09.2026). Раздел
   двух разборов рядом — своей линией у второго, а не просветом фона. */
.tuning-verdicts { border-top: 1px solid var(--line); background: var(--paper);
  overflow: auto; min-height: 0; display: grid; gap: 0; align-content: start;
  grid-template-columns: minmax(0, 1fr); }
.tuning-verdicts.pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tuning-verdicts.pair > :nth-child(2n) { border-left: 1px solid var(--line); }
/* Вердикт шага со своей формой (отчёт расчёта шкалы, D8 18.09.2026) стоит на
   той же дорожке, но СВОИМ узлом: сетка `.tuning-verdict` делит содержимое на
   две колонки — «таблица слева, справочное справа», — а карточка отчёта
   сложена сверху вниз и в такую сетку не ложится. */
.tuning-own { background: var(--paper); padding: 8px 14px; min-height: 0; }
.tuning-own .card { margin: 0; }
.tuning-verdict { background: var(--paper); padding: 8px 14px; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 4px 20px; align-content: start; }
/* Разбор выбранного кадра помечен так же, как его строка в колонке слева:
   на графике лежит именно он. */
.tuning-verdicts.pair .tuning-verdict.sel { box-shadow: inset 3px 0 0 var(--accent); }
.tuning-verdict .vhead, .tuning-verdict .who, .tuning-verdict .capnote { grid-column: 1 / -1; }
.tuning-verdict .capnote { display: flex; flex-wrap: wrap; gap: 2px 20px; }
.tuning-verdict .vhead { display: flex; gap: 8px; align-items: center; }
.tuning-verdict .nav { display: flex; gap: 6px; align-items: center; }
.tuning-verdict .nav button, .tuning-verdict .vhead button { min-height: 30px; padding: 2px 10px; }
.tuning-verdict .who { font-weight: 700; }
.tuning-verdict .who .when { font-weight: 400; }
.tuning-verdict .vbody { display: grid; gap: 6px; align-content: start; }
/* Справочное — второй колонкой, правее таблицы строк (просьба наладчика
   15.09.2026): под таблицей оно не влезало по высоте и давало прокрутку. */
.tuning-verdict .vside { display: grid; gap: 6px; align-content: start; min-width: 0; }
.tuning-verdict .vside .hintscut { margin-top: 0; }
/* Два разбора рядом — места вдвое меньше, и вторая колонка внутри разбора
   схлопывается: справочное встаёт под таблицу, как было. */
.tuning-verdicts.pair .tuning-verdict { grid-template-columns: minmax(0, 1fr); }

/* Переключатель АРМ поста (23.09.2026): «Настройка шасси» ↔ «Проверка сборки».
   Общий с /assembly-check/ — страница подключает этот файл. */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); }
.tabs a, .tabs .off { padding: 8px 16px; border: 1px solid transparent; border-bottom: none; border-radius: 6px 6px 0 0;
          color: var(--ink-2); text-decoration: none; }
.tabs a[aria-current="page"] { border-color: var(--line); background: var(--paper); color: var(--ink); font-weight: 600; }
.tabs a:hover:not([aria-current]) { color: var(--accent); }
.tabs .off { color: var(--ink-3); cursor: not-allowed; }

/* Переключатель языка (shared/ui-kit/i18n.js) — в шапке, рядом с «Кадры». */
.i18n-switch { display: inline-flex; gap: 2px; }
.i18n-switch button { min-height: 36px; padding: 0 8px; font-size: 13px; }
.i18n-switch button[aria-pressed="true"] { font-weight: 600; opacity: 1; }

/* --- узкий и низкий пост (1366×768): правила идут последними, иначе базовые
   объявления ниже по файлу перебивают их при равной силе. --- */
@media (max-width: 1400px) {
  :root { --rail-w: 240px; --help-w: 340px; --do-w: 330px; }
  .sheet { padding: 16px 18px; }
  .footer { padding: 12px 18px; }
  /* Натянутая на широком экране справка не имеет права приехать на пост с
     1366 px и выдавить лист: потолок здесь считается от окна. То же у
     выдвижной панели — её ширину тянут той же ручкой. */
  .help { max-width: 40vw; }
  .drawer { max-width: 40vw; }
}
/* На 1366×768 шапка листа не должна съедать четверть рабочего места. */
@media (max-height: 900px) {
  /* На низком экране у компактной панели полоса инструментов ui-kit видна
     всегда (решение tech lead 15.09.2026 отменяет прежнее «скрыта, болячка
     59»): «всё, кроме канвы» выросло на её высоту. Канва остаётся 170 —
     260 − 170 = 90 (было 220 − 170 = 50, полоса замерена в 40 px: headless-
     Chrome 1366×768, `.chartbox .sp-tools`
     `getBoundingClientRect().height`, способ и число — `changelog-U.md`). */
  :root { --chart-h: 260px; --canvas-h: 170px; --chart-chrome: 90px; }
  .chartbox { padding: 6px 16px; }
  .top { padding: 4px 16px; }
  .lh-top .lh-who { min-width: 96px; }
  .top select, .top button.small { min-height: 32px; }
  /* Все 16 шагов без прокрутки: рельс — карта маршрута, а не список для чтения. */
  .rail { padding: 4px 0; }
  .rail .step { min-height: 34px; padding: 4px 12px; gap: 8px; }
  .rail .step .no { width: 22px; height: 22px; font-size: 12px; }
  .rail .step .t { font-size: 13px; line-height: 1.25; }
  .rail .part { padding: 6px 12px 2px; font-size: 11px; }
  .sheet h1 { font-size: 19px; }
  /* На низком экране шапка листа тоже теснее: квадрат под кегль заголовка,
     надглавие мельче. Каждый пиксель здесь — доля пункта чек-листа. */
  .sheet-head { gap: 10px; }
  .sheet-head > .no { width: 34px; height: 34px; font-size: 15px; }
  .sheet-head .eyebrow { font-size: 11px; line-height: 1.1; }
  /* Надглавие стоит ровно одну строку (13 px на этом экране), и бесплатным оно
     быть не может. Строка выкуплена обратно у отступов ВОКРУГ шапки листа:
     верхнее поле листа, поле под целью шага и под плашкой состояния. Замер
     статическим стендом по восьми листам: без этого выкупа на листе «Фокус
     матриц» орган 38-й уходил под сгиб. */
  .sheet { padding-top: 10px; }
  .sheet .goal { margin: 0 0 6px; }
  .status-line { margin-bottom: 6px; }
  .sheet .goal { margin: 2px 0 8px; font-size: 14px; }
  .status-line { margin-bottom: 8px; }
  .note { padding: 7px 12px; margin-bottom: 10px; font-size: 14px; }
  .block { margin-bottom: 6px; }
  .flow { gap: 6px; margin-bottom: 12px; }
  .frow > .n { padding-top: 9px; }
  /* Карточка точки пирометра на низком экране складывается теснее: на листе
     термоиспытаний это ряд образцов, видимый без прокрутки. */
  .pointsec { margin-bottom: 10px; }
  .point { padding: 8px 10px; }
  .pointsec .block { margin-bottom: 4px; }
  .point .row { gap: 8px; }
  .point button { min-width: 140px; }
  .samplerow { margin-top: 6px; }
  .item, .cap { padding: 8px 12px; }
  .figs img { height: 64px; }
  .footer { padding: 8px 18px; gap: 6px; }
  .footer button.big { min-height: 44px; font-size: 15px; }
  .chartbox .bar button.small { min-height: 30px; }
  /* Справка на низком экране: те же разделы, теснее набор и рисунок ниже —
     но всё ещё вдвое крупнее 56-px превью на листе. */
  .help { padding: 10px 12px 18px; gap: 6px; }
  .help h2 { font-size: 16px; }
  .help h3 { margin-top: 8px; }
  .help .acts { gap: 8px; }
  .help .figs img { max-height: 108px; }
}
/* Правило модуля: медиазапросы — последними, иначе базовые объявления ниже
   по файлу перебивают их при равной силе. */
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
