/* Режим «Сборка» (assembly.js). Геометрия — как у оболочки и привязки (shell.css,
   bind.css, One UI): заголовок 28/700 у левого края, группы одной капсулой 26px с
   разделителями от начала текста, капсульные кнопки, без теней; главная кнопка —
   внизу экрана. Цвета — токены lab.css, тёмная тема приходит оттуда же.

   Экран стоит на фоне страницы (--bg), поэтому капсулы и кнопки на нём — --surface;
   внутри листа снизу (он сам --surface) группы — --surface-2.

   Нижняя панель — .rf-actbar (nav.css: стоит на меню, её высоту main учитывает сам через
   --rf-actbar-h), кнопки источника и слот камеры в ней рисует srcbar.js
   (docs/spec_scanner_app_modes.md §2.5, §3.3). Здесь — только лента и действие модуля. */

.rf-asm { max-width: 640px; margin: 0 auto; }
/* Зазор 8, а не 12: на экране «Сборки» таких зазоров три-четыре — это полстроки. */
.rf-asm-screen { display: grid; gap: 8px; min-width: 0; }
.rf-asm-screen > * { min-width: 0; }

/* ---------- заголовки */
.rf-asm-head { display: grid; gap: 4px; padding: 4px 8px 2px; }
.rf-asm-head__row { display: flex; align-items: flex-start; gap: 8px; }
.rf-asm-title {
  flex: 1; min-width: 0;
  font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2;
  margin: 0; overflow-wrap: anywhere;
}
/* Название мероприятия бывает в три строки — чуть мельче «Сборки», но той же породы. */
.rf-asm-title--event { font-size: 24px; line-height: 1.25; }
/* «Загружаю…» — тоже текст: --faint на фоне страницы давал 2,6:1. */
.rf-asm-skel { color: var(--muted); }
.rf-asm-lede { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.45; }
.rf-asm-refresh { margin: -6px -10px 0 0; color: var(--muted); }
.rf-asm-refresh.is-spin svg { animation: asm-spin .8s linear infinite; }
@keyframes asm-spin { to { transform: rotate(360deg); } }
.rf-asm-retry { width: auto; justify-self: start; margin-top: 10px; background: var(--surface); }

.rf-asm-notes {
  display: flex; gap: 10px; align-items: flex-start;
  margin-top: 6px; padding: 12px 16px;
  border-radius: 20px; background: var(--warn-soft);
  font-size: 14px; line-height: 1.45; white-space: pre-line; overflow-wrap: anywhere;
}
.rf-asm-notes svg { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--warn-ink); }

/* ---------- чей экран виден: «Сборка» и «Приёмка» — два пункта нижнего меню на одну
   вкладку. data-mode у неё ставит scanflow.js, второй режим спрятан целиком — вместе с
   нижней панелью и листом (они position: fixed, вне своего корня). */
#tab-asm[data-mode="returns"] :is(#asm-root, #asm-bottom, #asm-sheet),
#tab-asm:not([data-mode="returns"]) :is(#ret-root, #ret-bottom, #ret-sheet) { display: none !important; }

/* ---------- экран списков */
/* Корень «Сборки» и «Приёмки» — как в One UI: большой заголовок стоит внизу «зоны просмотра»
   (верхние ~38 % экрана), карточки списков начинаются ниже, под большим пальцем. Раньше они
   стояли в верхней трети, а до меню было пусто, и каждая смена начиналась с касания у самого
   верха экрана. svh, а не vh: высота не прыгает от панели браузера.

   ЗОНА ОТДАЁТ СВОЁ МЕСТО КАРТОЧКАМ, КОГДА ИХ НЕ ХВАТАЕТ НА ЭКРАН. Пустая полоса над
   заголовком съедала 1–2 карточки списка (замер: 360×640 — 110 px у сборки, 131 у приёмки),
   то есть больше, чем дало уплотнение самих карточек, а владелец просил ровно обратного —
   «слишком мало на экран вмещается». Поэтому зона теперь НЕ фиксированная: экран — колонка
   flex ростом во весь свободный экран, заголовок растёт в свободное место (flex-grow) и
   упирается в прежние 38 svh (max-height), а когда списки экран уже переполнили, свободного
   места нет и заголовок остаётся своей естественной высоты. Сжиматься ему нельзя
   (flex-shrink: 0), иначе при переполнении текст заголовка налез бы сам на себя.
   min-height колонки — ровно видимая часть main: 100 svh минус шапка (--bar-h), верхнее
   поле main и его нижнее поле (меню + панель экрана + баннеры + 24, lab.css). Промахнуться
   в меньшую сторону не страшно: зона просто схлопнется чуть раньше. */
#asm-lists,
#ret-lists {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--bar-h, 52px) - var(--gutter) - var(--rf-nav-h, 64px) - var(--rf-actbar-h, 0px) - var(--rf-banner-h, 0px) - 24px);
  min-height: calc(100svh - var(--bar-h, 52px) - var(--gutter) - var(--rf-nav-h, 64px) - var(--rf-actbar-h, 0px) - var(--rf-banner-h, 0px) - 24px);
}
#asm-lists > .rf-asm-head,
#ret-lists > .rf-asm-head {
  flex: 1 0 auto;
  max-height: calc(38vh - var(--bar-h, 52px));
  max-height: calc(38svh - var(--bar-h, 52px));
  align-content: end;
}
/* Карточкам сжиматься тоже нельзя: min-height: auto защищает только до содержимого, а
   ярусы карточки и сами тянутся. */
#asm-lists > #asm-lists-body,
#ret-lists > #ret-lists-body { flex: none; }
.rf-asm-cards { display: grid; gap: 6px; }
.rf-asm-card {
  /* Поля 12/16, а не 18/20: карточка списка — это имя (до двух строк), «5 позиций ·
     23 единицы» и полоса прогресса; 121 px из них держали воздух. */
  display: grid; gap: 6px;
  width: 100%; padding: 12px 16px;
  border: 0; border-radius: var(--oui-r);
  background: var(--surface); color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
  transition: background .1s var(--oui-ease);
}
.rf-asm-card:active { background: var(--surface-2); }
.rf-asm-card__top { display: flex; align-items: flex-start; gap: 12px; }
.rf-asm-card__name {
  flex: 1; min-width: 0;
  font-size: 17px; font-weight: 600; line-height: 1.3;
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Междустрочье 1.3 у всех ярусов карточки: у корпуса 1.45, и четыре яруса набирали
   лишние 8 px на каждой карточке списка. */
.rf-asm-card__meta { color: var(--muted); font-size: 14px; line-height: 1.3; }
.rf-asm-card__progress { display: flex; align-items: center; gap: 12px; }
.rf-asm-card__count { flex: none; color: var(--muted); font-size: 14px; line-height: 1.3; font-variant-numeric: tabular-nums; }
.rf-asm-card__count b { color: var(--text); font-weight: 700; }

.rf-asm-chip {
  flex: none; display: inline-flex; align-items: center;
  height: 26px; padding: 0 11px; border-radius: 999px;
  font-size: 12px; font-weight: 700; white-space: nowrap;
}
.rf-asm-chip--sent { background: var(--accent-soft); color: var(--accent-text); }
.rf-asm-chip--done { background: color-mix(in srgb, var(--good) 20%, var(--surface)); color: var(--good-ink); }

.rf-asm-bar { display: block; flex: 1 1 auto; height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
.rf-asm-bar > i { display: block; height: 100%; border-radius: inherit; background: var(--accent); transition: width .3s var(--oui-ease); }
.rf-asm-bar.is-full > i { background: var(--good); }
[data-theme="dark"] .rf-asm-bar { background: var(--border-strong); }

.rf-asm-empty {
  display: grid; justify-items: center; gap: 8px;
  padding: 36px 24px 32px; border-radius: var(--oui-r);
  background: var(--surface); text-align: center;
}
.rf-asm-empty__ico {
  width: 64px; height: 64px; border-radius: 999px; margin-bottom: 6px;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-text);
}
.rf-asm-empty__ico svg { width: 30px; height: 30px; }
.rf-asm-empty__title { margin: 0; font-size: 17px; font-weight: 600; }
.rf-asm-empty__text { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; max-width: 34ch; }

/* ---------- прогресс списка */
/* 28, а не 36: карточка прогресса стоит над списком строк, и каждый её пиксель — это
   пиксель, которого не хватает строкам (22.09). Число по-прежнему самое крупное на экране. */
.rf-asm-progress {
  display: grid; gap: 6px;
  padding: 10px 16px; border-radius: var(--oui-r);
  background: var(--surface);
}
.rf-asm-progress__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rf-asm-progress__nums { display: flex; align-items: baseline; gap: 6px; font-variant-numeric: tabular-nums; }
.rf-asm-progress__nums b { font-size: 28px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; }
.rf-asm-progress__nums span { font-size: 15px; color: var(--muted); }
.rf-asm-progress .rf-asm-bar { height: 8px; border-radius: 4px; }
.rf-asm-progress__sub { margin: 0; color: var(--muted); font-size: 14px; }
.rf-asm-progress.is-done .rf-asm-progress__sub { color: var(--good-ink); font-weight: 600; }

/* «Камера» (или камера, включённая из «Меню»): панель с видоискателем занимает треть экрана,
   и карточка прогресса в 120 px не оставляла при открытии ни одной строки единиц (U4).
   Прогресс идущей работы — одной строкой «0 из 23 · На сборке»: полосу и «Осталось N»
   повторяют сами числа, а сжатая рядом с ними полоса была бы точкой. assembly.js / returns.js
   при открытии прокручивают так, чтобы прогресс встал под шапку. */
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) {
  grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  column-gap: 12px; row-gap: 8px; padding: 10px 16px 10px 20px;
}
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) .rf-asm-progress__top { display: contents; }
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) .rf-asm-progress__nums { gap: 6px; }
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) .rf-asm-progress__nums b { font-size: 22px; }
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) .rf-asm-progress__nums span { font-size: 15px; }
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) > :is(.rf-asm-bar, .rf-asm-progress__sub) { display: none; }
/* Значки приёмки («повреждено», «потеряно») — второй строкой во всю ширину. */
:is(html[data-input="camera"] #tab-asm, #tab-asm:has(> .rf-actbar.has-cam)) .rf-asm-progress:not(.is-done) > .rf-ret-badges { grid-column: 1 / -1; }

/* ---------- строка «не отправлены» (scanflow.js placeHeld): коды, которые сами не уйдут —
   прочитаны до повторного входа, сотрудника нет в компании, другая компания. Стоит над
   карточками списков и над подсказкой экрана списка. Текст — --text, а не --muted: на
   красной подложке серый не дотягивал до 4,5:1. Кнопки 44 px — жмут одной рукой. */
.rf-asm-held { display: grid; gap: 8px; }
.rf-asm-held__item {
  display: grid; grid-template-columns: 28px minmax(0, 1fr); column-gap: 12px; row-gap: 10px;
  padding: 14px 16px; border-radius: var(--oui-r);
  background: var(--danger-soft); color: var(--text);
}
.rf-asm-held__ico {
  width: 28px; height: 28px; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--surface); color: var(--bad-ink);
}
.rf-asm-held__ico svg { width: 16px; height: 16px; }
.rf-asm-held__text { min-width: 0; display: grid; gap: 2px; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }
.rf-asm-held__text b { font-size: 15px; font-weight: 700; }
.rf-asm-held__acts { grid-column: 2; display: flex; flex-wrap: wrap; gap: 8px; }
.rf-asm-held__btn {
  min-height: 44px; padding: 0 18px;
  border: 0; border-radius: 999px;
  background: var(--surface); color: var(--text);
  font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
}
.rf-asm-held__btn.is-main { background: var(--accent-soft); color: var(--accent-text); }
.rf-asm-held__btn:active { filter: brightness(.95); }
.rf-asm-held__btn:focus-visible { outline: none; box-shadow: var(--ring); }

/* ---------- подсказка: что слушаем (по режиму ввода, scanflow.js ScanInputs.hint) */
.rf-asm-hint { margin: -4px 8px 0; color: var(--muted); font-size: 13px; line-height: 1.4; }
.rf-asm-hint.is-bad { color: var(--bad-ink); font-weight: 600; }
.rf-asm-hint.is-busy::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 8px;
  border-radius: 50%; background: var(--accent); vertical-align: 1px;
  animation: pulse 1s infinite;
}
/* Пока виден видоискатель (srcbar.js ставит панели .has-cam), подсказка повторяет его подпись
   («Код — в рамку») и полосы паузы, разрешения и ошибки — прячем её и отдаём две строки
   списку (U4). «Нет связи» (.is-bad) остаётся: о ней больше не говорит никто. Заодно список не
   прыгает под пальцем, когда подсказка на каждую пачку сменяется на «Отмечаю…» и обратно. */
#tab-asm:has(> .rf-actbar.has-cam) .rf-asm-hint:not(.is-bad) { display: none; }

/* ---------- подписи групп списка: у оболочки 16 сверху и 8 снизу — на экране строк таких
   подписей по одной на категорию, и каждая стоила полстроки. */
:is(#asm-groups, #ret-groups, #ven-groups) > .rf-list__label { margin: 12px 0 6px; }
:is(#asm-groups, #ret-groups, #ven-groups) > .rf-list__label:first-child { margin-top: 2px; }

/* ---------- строки и единицы

   ПЛОТНОСТЬ (запрос владельца 22.09: «строки с оборудованием слишком высокие»). Было: шапка
   строки — кольцо 36 и два яруса («Название» / «собрано 1 из 3»), под ней каждая единица
   двумя ярусами по 48 px; позиция из одной единицы занимала ≈110 px, из трёх — 225, и на
   экране 360×740 помещалось три позиции. Стало:
   - шапка — один ярус: кольцо 28, название, справа «1 из 3» (countHtml, scanflow.js);
     подпись под названием осталась только у строк без поштучного учёта (рядом степпер);
   - единица — один ярус 44 px: номер слева, кто и когда отметил — справа мелко;
     комментарии и пометки — своим ярусом, их многоточием не режем;
   - позиция из одной единицы — одна кнопка .is-solo: название, под ним номер (≈52 px).
   Колонка значков — 28 px: кольцо строки и кружок единицы стоят друг под другом, а
   разделители и отступы считаются от начала текста: 16 поле + 28 + 12 зазор = 56. */
.rf-asm-group .rf-asm-line { position: relative; padding-bottom: 2px; }
.rf-asm-group .rf-asm-line.is-solo { padding-bottom: 0; }
.rf-asm-line + .rf-asm-line::before {
  content: ""; position: absolute; top: 0; left: 56px; right: 16px;
  height: 1px; background: var(--border);
}
.rf-asm-line__head { display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 6px 12px 2px 16px; }
.rf-asm-line__main { flex: 1; min-width: 0; display: grid; gap: 1px; }
.rf-asm-line__name { font-size: 16px; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; }
.rf-asm-line__sub { font-size: 13px; line-height: 1.3; color: var(--muted); font-variant-numeric: tabular-nums; }
.rf-asm-line.is-done .rf-asm-line__sub { color: var(--good-ink); }
.rf-asm-line__note { font-size: 13px; line-height: 1.35; color: var(--muted); white-space: pre-line; overflow-wrap: anywhere; }
.rf-asm-line__note--comment { color: var(--warn-ink); font-weight: 600; }
/* Ярус степпера строки без поштучного учёта: подпись «собрано 0 из 3 · без поштучного
   учёта» слева, степпер справа, отступ — от начала текста. */
.rf-asm-line__steprow {
  display: flex; align-items: center; gap: 10px;
  margin: 0; padding: 0 12px 6px 56px;
}
.rf-asm-line__steprow > .rf-asm-line__sub { flex: 1; min-width: 0; }
.rf-asm-line__missing { margin: 0; padding: 0 16px 6px 56px; font-size: 13px; line-height: 1.35; color: var(--muted); }
/* «2 из 3» справа от названия: жирнее подписи — это главное число строки, но не крупнее
   названия. Готовая строка — зелёным, как прежняя подпись «собрано 3 из 3». */
.rf-asm-line__count {
  flex: none; align-self: center;
  font-size: 14px; font-weight: 600; line-height: 1.3; white-space: nowrap;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.rf-asm-line__count.is-done { color: var(--good-ink); }

.rf-asm-mark { width: 28px; height: 28px; flex: none; display: grid; place-items: center; border-radius: 999px; }
.rf-asm-mark svg { width: 28px; height: 28px; display: block; }
.rf-asm-mark__track { fill: none; stroke: var(--border-strong); stroke-width: 3.4; }
.rf-asm-mark__val { fill: none; stroke: var(--accent); stroke-width: 4; stroke-linecap: round; transition: stroke-dasharray .3s var(--oui-ease); }
.rf-asm-mark.is-done { background: var(--good); color: var(--accent-ink); }
.rf-asm-mark.is-done svg { width: 16px; height: 16px; }

.rf-asm-step {
  flex: none; align-self: center;
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; border-radius: 999px; background: var(--surface-2);
}
.rf-asm-step button {
  /* 44 × 44: во «Вручную» степпер — основной ввод строк без поштучного учёта. */
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 999px; background: var(--surface); color: var(--text);
  cursor: pointer;
}
.rf-asm-step button svg { width: 18px; height: 18px; }
.rf-asm-step button:active:not(:disabled) { background: var(--accent-soft); color: var(--accent-text); }
.rf-asm-step button:disabled { opacity: .35; cursor: default; }
[data-theme="dark"] .rf-asm-step button { background: var(--border-strong); }
.rf-asm-step b { min-width: 30px; text-align: center; font-size: 17px; font-variant-numeric: tabular-nums; }

.rf-asm-unit {
  /* 44: один ярус текста (15/1.3 ≈ 20) и кружок 22 — остальное цель касания (≥ 44).
     Во «Вручную», где касание и есть отметка, — 52 (ниже). */
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 44px; padding: 3px 12px 3px 16px;
  border: 0; background: none; color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
  transition: background .1s var(--oui-ease);
}
.rf-asm-unit:active:not(:disabled) { background: var(--surface-2); }
.rf-asm-unit:disabled { cursor: default; }
.rf-asm-check {
  width: 22px; height: 22px; margin: 0 3px; flex: none;
  display: grid; place-items: center;
  border-radius: 999px; border: 2px solid var(--border-strong);
  color: transparent;
  transition: background .2s var(--oui-ease), border-color .2s var(--oui-ease);
}
.rf-asm-check svg { width: 14px; height: 14px; }
.rf-asm-unit.is-picked .rf-asm-check { background: var(--good); border-color: var(--good); color: var(--accent-ink); }
.rf-asm-unit.is-busy .rf-asm-check { border-color: var(--accent); animation: pulse 1s infinite; }
/* Текст единицы — строка с переносом: номер слева, подпись справа. Подпись сжимается
   многоточием, а когда номер длинный и ей остаётся меньше 72 px — уходит вторым ярусом,
   а не исчезает. Пояснения (.is-long, пометки, примечания) всегда своим ярусом. */
.rf-asm-unit__main {
  flex: 1; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 10px; row-gap: 1px;
  line-height: 1.3;
}
.rf-asm-unit__inv { flex: 0 1 auto; min-width: 0; font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-asm-unit__sub {
  flex: 1 1 72px; min-width: 0; text-align: right;
  font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rf-asm-unit__sub.is-long { flex-basis: 100%; text-align: left; white-space: normal; overflow-wrap: anywhere; }
.rf-asm-unit__name, .rf-asm-unit__flags, .rf-asm-unit__main > .rf-asm-line__note { flex: 1 0 100%; min-width: 0; }
.rf-asm-unit__flags { display: flex; flex-wrap: wrap; gap: 4px; padding: 2px 0 1px; }
.rf-asm-unit.is-picked .rf-asm-unit__inv { color: var(--muted); }
.rf-asm-unit.is-picked .rf-asm-unit__sub { color: var(--good-ink); }

/* Позиция из одной единицы — одна строка (.rf-asm-line.is-solo): название сверху, номер
   единицы и кто отметил — под ним. Кружок — во всю колонку значков (28): он отвечает и за
   строку, и за единицу. «Найти» стоит рядом с кнопкой, а не внутри: кнопка в кнопке
   недопустима. */
.rf-asm-line.is-solo { display: flex; align-items: center; }
.rf-asm-line.is-solo > .rf-asm-unit { flex: 1; min-width: 0; }
.rf-asm-line.is-solo > :is(.rf-asm-line__find, .rf-ret-line__find, .rf-ven-line__find) { margin: 0 12px 0 -4px; }
.rf-asm-unit--solo { min-height: 52px; padding-top: 6px; padding-bottom: 6px; }
.rf-asm-unit--solo .rf-asm-check { width: 28px; height: 28px; margin: 0; }
.rf-asm-unit--solo .rf-asm-check svg { width: 16px; height: 16px; }
.rf-asm-unit__name { font-size: 16px; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; color: var(--text); }
.rf-asm-unit--solo .rf-asm-unit__inv { font-size: 14px; font-weight: 400; color: var(--muted); }
.rf-asm-unit--solo.is-picked .rf-asm-unit__inv { color: var(--muted); }

/* «Вручную» касание строки и есть отметка: строка выше, кружок крупнее — пальцем в
   перчатке по соседней не промахнуться. Поля кружка съедены размером: колонка текста не
   сдвигается. */
html[data-input="manual"] .rf-asm-unit { min-height: 52px; }
html[data-input="manual"] .rf-asm-unit--solo { min-height: 56px; }
html[data-input="manual"] .rf-asm-check { width: 26px; height: 26px; margin: 0 1px; }
html[data-input="manual"] .rf-asm-unit--solo .rf-asm-check { width: 28px; height: 28px; margin: 0; }
html[data-input="manual"] .rf-asm-check svg { width: 16px; height: 16px; }
.rf-asm-kinds { flex: none; align-self: center; display: flex; gap: 4px; }
.rf-asm-kind {
  display: inline-flex; align-items: center; height: 20px; padding: 0 7px;
  border-radius: 999px; background: var(--surface-2); color: var(--muted);
  font-size: 10px; font-weight: 700; letter-spacing: .03em;
}

/* ---------- нижняя панель: лента результатов и действие модуля. Геометрию панели
   (fixed над меню, поля, фон) задаёт .rf-actbar в nav.css. */
.rf-asm-feed { display: grid; gap: 3px; }
.rf-asm-feed__item {
  display: flex; align-items: center; gap: 8px;
  min-height: 40px; padding: 5px 8px;
  border-radius: 18px; background: var(--surface);
  font-size: 14px; line-height: 1.3;
  animation: asm-in .25s var(--oui-ease);
}
/* Старые строки ленты тише — значком и цветом заголовка, а не opacity всей строки: подпись
   (--muted) под прозрачностью .7 опускалась до 3:1. */
.rf-asm-feed__item.is-old .rf-asm-feed__ico { opacity: .6; }
.rf-asm-feed__item.is-old .rf-asm-feed__text > span:first-child { color: var(--muted); }
.rf-asm-feed__ico {
  width: 28px; height: 28px; flex: none; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--muted);
}
.rf-asm-feed__ico svg { width: 16px; height: 16px; }
.rf-asm-feed__item--ok .rf-asm-feed__ico { background: var(--good); color: var(--accent-ink); }
.rf-asm-feed__item--info .rf-asm-feed__ico { background: var(--info-soft); color: var(--text); }
.rf-asm-feed__item--warn .rf-asm-feed__ico { background: var(--warn-soft); color: var(--warn-ink); }
.rf-asm-feed__item--bad .rf-asm-feed__ico { background: var(--danger-soft); color: var(--bad-ink); }
.rf-asm-feed__text { flex: 1; min-width: 0; display: grid; }
.rf-asm-feed__text > span:first-child {
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Отказ по уровню доступа (§Б.6) — три строки вместо двух: фразу сочиняет сервер, и она
   длинная («Приёмка доступна с уровня «Проджект менеджер» — у сотрудника Иван Петров
   «Производство»»). Своей короче не напишешь: порог и подписи уровней знает только он. */
.rf-asm-feed__item--bad.has-act .rf-asm-feed__text > span:first-child { -webkit-line-clamp: 3; }
.rf-asm-feed__sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-asm-feed__sub--mono { font-family: var(--mono); }
/* Кнопка строки ленты — тональная: янтарная на экране одна (главная кнопка ряда, §3.3), а
   «Подменить», «Привязать», «Нашлась» — ответы на вопрос строки, не главное действие.
   В «Камере» с автостартом она — единственный способ подтвердить подмену, добор и «нашлась»
   (коды уходят с confirm: false), и жмут её одной рукой: кнопка 40 px, поле касания — 48
   (::before), как у небольших кнопок Material. Строка с кнопкой при этом не растёт: её поля
   4 px вместо 6. */
.rf-asm-feed__act {
  position: relative;
  flex: none; height: 40px; padding: 0 16px;
  border: 0; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-text);
  font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
}
.rf-asm-feed__act::before { content: ""; position: absolute; inset: -4px -4px; border-radius: inherit; }
.rf-asm-feed__item.has-act { padding-top: 4px; padding-bottom: 4px; }
.rf-asm-feed__act:active:not(:disabled) { filter: brightness(.95); }
/* Подтверждение ушло и ждёт ответа («Подменяю…») — кнопка гаснет, но остаётся на месте:
   строка не прыгает под пальцем. */
.rf-asm-feed__act:disabled { opacity: .55; cursor: default; }
/* Строка с вопросом стоит в ленте дольше и важнее итогов — чуть заметнее них. */
.rf-asm-feed__item.has-act { box-shadow: inset 0 0 0 1.5px var(--accent-soft); }
/* Пока виден слот камеры (srcbar.js ставит панели .has-cam), лента — в одну строку: иначе
   панель съела бы список (§2.5). Строка — первая С ДЕЙСТВИЕМ, а не просто первая: лента
   по свежести, и итог следующего же кадра спрятал бы «Подменить / Добавить / Нашлась» —
   а коды камеры автостарта именно так и отвечают. Действий нет — самая свежая. */
.rf-actbar.has-cam .rf-asm-feed:has(> .has-act) > :not(.has-act),
.rf-actbar.has-cam .rf-asm-feed:has(> .has-act) > .has-act ~ .has-act,
.rf-actbar.has-cam .rf-asm-feed:not(:has(> .has-act)) > :not(:first-child) { display: none; }
.rf-actbar.has-cam .rf-asm-feed > .is-old.has-act { opacity: 1; }
/* Под клавиатурой (nav.js, html.rf-kbd) — то же правило: окно сжато почти вдвое, и три строки
   ленты над полем ⌨ закрыли бы список, в котором ищут номер (решение исполнителя). */
html.rf-kbd .rf-actbar .rf-asm-feed:has(> .has-act) > :not(.has-act),
html.rf-kbd .rf-actbar .rf-asm-feed:has(> .has-act) > .has-act ~ .has-act,
html.rf-kbd .rf-actbar .rf-asm-feed:not(:has(> .has-act)) > :not(:first-child) { display: none; }
html.rf-kbd .rf-actbar .rf-asm-feed > .is-old.has-act { opacity: 1; }

/* Действие модуля — первым в ряду, ширину отдаёт ему srcbar (.rf-actbar__row в nav.css).
   Контейнер: подпись кнопки выбирается по ЕГО ширине, а не по окну — при 360 px «Сборка
   завершена» влезает рядом с «Штрихкодом», а рядом с «Подключить» уже нет (§3.3). */
.rf-asm-actions { container-type: inline-size; min-width: 0; }
.rf-asm-actions:empty { display: none; }
/* Высота — как у кнопок источника (44, srcbar.css): ряд ровный, а поля узкие — рядом с
   «Переподключаю…» при 360 px кнопке остаётся ≈115 px. */
.rf-asm-actions .rf-btn { min-height: 44px; padding: 0 11px; border-radius: 22px; }
.rf-asm-actions .rf-btn--ghost { background: var(--surface); }
.rf-asm-finish { white-space: nowrap; }
.rf-asm-finish > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Тональная, пока не всё отмечено: янтарной на экране остаётся главная кнопка источника. */
.rf-asm-finish:not(.rf-btn--primary) { background: var(--surface); color: var(--text); }
.rf-asm-finish__short { display: none; }
/* Порог — «Приёмка завершена» (≈158 px) с полями 11 + 11 и запасом на шрифт телефона.
   После сжатия ряда до 44 px поля стали уже — порог опущен со 188 на 178. */
@container (width < 178px) {
  .rf-asm-finish__full { display: none; }
  .rf-asm-finish__short { display: inline; }
}

/* ---------- «Найти (N)»: поиск оборудования сканером (§В.1)

   Стоит РЯДОМ с «Сборка завершена», а не в шапке списка: до шапки большой палец с рукоятью
   H103 не дотягивается. Свой ряд, а не сама .rf-asm-actions: её класс носят ещё «Приёмка» и
   «Площадка», и превращать его в flex значило бы править чужие экраны из этого файла.
   Класс is-with-find ставит assembly.js — ровно тогда, когда кнопка правда нарисована
   (право `search` и режим «Сканер»): CSS-правила `.rf-need-search`/`.rf-if-scanner` прячут
   её display'ем, а по скрытому потомку ширину ряда не посчитать. */
.rf-asm-acts { display: flex; align-items: center; gap: 6px; min-width: 0; }
.rf-asm-acts > .rf-btn { width: auto; min-width: 0; }
.rf-asm-acts > .rf-asm-finish { flex: 1 1 auto; }
.rf-asm-find {
  flex: 0 0 auto;
  gap: 6px; padding: 0 12px;
  background: var(--surface); color: var(--text);
  white-space: nowrap;
}
.rf-asm-find__ico { display: inline-flex; flex: none; }
.rf-asm-find__ico svg { width: 20px; height: 20px; }
.rf-asm-find__short { display: none; }
/* Вдвоём кнопки требуют больше места: «Найти (12)» ≈ 110 px + «Сборка завершена» ≈ 165 px,
   а ряд делит ширину с кнопками источника. Пороги свои, отдельные от прежнего 188 px: тот
   считался для одной кнопки в ряду. Сначала короче становится «Завершить» — оно длиннее, а
   «Найти (200)» ≈ 125 px держится дольше: число целей важнее второго слова кнопки. */
@container (width < 280px) {
  .rf-asm-acts.is-with-find .rf-asm-finish__full { display: none; }
  .rf-asm-acts.is-with-find .rf-asm-finish__short { display: inline; }
}
@container (width < 240px) {
  .rf-asm-acts.is-with-find .rf-asm-find__full { display: none; }
  .rf-asm-acts.is-with-find .rf-asm-find__short { display: inline; }
}
/* Совсем узко — в столбик. Так бывает не в теории: пока сканер не подключён, главная
   кнопка источника текстовая («Подключить», «Переподключаю…»), и при 360 px ряду действий
   остаётся ≈134 px. В строку там не влезает даже «Завершить» — её резало многоточием, и
   человек читал «Заверш…». В столбик места хватает обеим, и «Найти» снова с подписью:
   ширина у кнопки теперь полная. «Завершить» остаётся коротким — «Сборка завершена» в
   134 px не помещается и так. */
@container (width < 180px) {
  .rf-asm-acts.is-with-find { flex-wrap: wrap; }
  .rf-asm-acts.is-with-find > .rf-btn { flex: 1 0 100%; }
  .rf-asm-acts.is-with-find .rf-asm-find__full { display: inline; }
  .rf-asm-acts.is-with-find .rf-asm-find__short { display: none; }
}

/* ---------- плашка «список уже на площадке» (§А.5, list.afterDeparture)

   Не отказ, а объяснение: отмечать «собрано» по-прежнему можно (довоз второй машиной),
   закрыты только подмена и добор. Поэтому янтарная, как «Примечания», а не красная. */
.rf-asm-plate {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start;
  margin-top: 6px; padding: 12px 16px;
  border-radius: 20px; background: var(--warn-soft);
  font-size: 14px; line-height: 1.45; overflow-wrap: anywhere;
}
.rf-asm-plate__ico { display: inline-flex; flex: none; margin-top: 1px; color: var(--warn-ink); }
.rf-asm-plate__ico svg { width: 18px; height: 18px; }
.rf-asm-plate__text { flex: 1 1 200px; min-width: 0; }
/* Кнопка — во всю ширину под текстом: «Открыть «Площадку»» рядом с тремя строками текста
   при 360 px оставляла тексту ≈120 px. */
.rf-asm-plate__act {
  flex: 1 0 100%;
  min-height: 44px; padding: 0 16px;
  border: 0; border-radius: 22px;
  background: var(--warn-ink); color: var(--bg);
  font: inherit; font-size: 15px; font-weight: 600;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.rf-asm-plate__act:active { filter: brightness(1.08); }

/* ---------- лупа у заголовка строки (§В.1, точка входа 2)

   Ищем только то, чего не хватает в ЭТОЙ строке. Значок, а не подпись: в заголовке уже
   стоят название, «собрано N из M», примечания и степпер. 44 × 44 — как у степпера: это
   цель касания, а не украшение. */
.rf-asm-line__find {
  flex: none; align-self: center;
  width: 44px; height: 44px; padding: 0; margin: -4px -4px -4px 0;
  display: grid; place-items: center;
  border: 0; border-radius: 999px; background: none; color: var(--muted);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.rf-asm-line__find svg { width: 20px; height: 20px; }
/* Кнопки «Найти» приёмки и площадки (returns.css, venue.css) — те же 44 × 44, и в шапке
   строки им тоже не место её раздувать: шапка — 44 вместе с полями. */
.rf-asm-line__head > :is(.rf-ret-line__find, .rf-ven-line__find) { margin: -4px -4px -4px 0; }
.rf-asm-line__find:active { background: var(--accent-soft); color: var(--accent-text); }

/* «Найти сканером (N)» в листе «Не собрано N — завершить?»: отдельной строкой над
   «Отмена / Завершить» — это другой шаг, а не третий ответ на тот же вопрос. */
.rf-asm-sheet__find {
  justify-content: center; gap: 8px; width: 100%;
  margin-top: 4px; min-height: 48px;   /* строка листа, не ряда над меню: 48 остаётся */
  background: var(--surface-2); color: var(--text); font-size: 15px;
}
.rf-asm-sheet__find svg { width: 20px; height: 20px; }

/* ---------- лист снизу: «Код не привязан», подтверждения */
.rf-asm-sheet {
  position: fixed; inset: 0; z-index: 86;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, .45);
  animation: menu-fade .2s var(--oui-ease);
}
.rf-asm-sheet__panel {
  width: min(100%, 520px); max-height: 88vh; overflow: auto; overscroll-behavior: contain;
  display: grid; gap: 14px; align-content: start;
  padding: 24px 20px calc(16px + env(safe-area-inset-bottom));
  border-radius: var(--oui-r) var(--oui-r) 0 0;
  background: var(--surface);
  animation: menu-up .38s var(--oui-ease);
}
@media (min-width: 700px) {
  .rf-asm-sheet { align-items: center; }
  .rf-asm-sheet__panel { border-radius: var(--oui-r); }
}
html.rf-asm-sheet-open body { overflow: hidden; }
.rf-asm-sheet__top { display: flex; align-items: center; gap: 14px; }
.rf-asm-sheet__ico {
  width: 44px; height: 44px; flex: none; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--danger-soft); color: var(--bad-ink);
}
.rf-asm-sheet__ico svg { width: 24px; height: 24px; }
.rf-asm-sheet__title { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.3; }
.rf-asm-sheet__code { font-family: var(--mono); font-size: 15px; color: var(--muted); overflow-wrap: anywhere; }
.rf-asm-sheet__text { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.5; }
.rf-asm-sheet__q.rf-input { height: 48px; }
.rf-asm-sheet__list { display: grid; }
.rf-asm-sheet__list .rf-list__label { padding: 0 4px; margin: 10px 0 6px; }
.rf-asm-sheet__list .rf-list__label:first-child { margin-top: 0; }
.rf-asm-sheet .rf-group { background: var(--surface-2); border-radius: 22px; }
.rf-asm-sheet .rf-row { min-height: 60px; padding: 10px 16px 10px 20px; }
.rf-asm-sheet .rf-row:active { background: var(--border); }
.rf-asm-sheet .rf-row + .rf-row::before { left: 20px; }
.rf-asm-cand:disabled { cursor: default; opacity: .6; }
/* Перечень вопросов пачки («Неизвестные коды», «Взяли другие единицы?»): строка — не
   кнопка, действие у каждой своё справа. Разделитель — от начала текста, как у rf-row. */
.rf-asm-sheet .rf-asm-grow { cursor: default; gap: 12px; padding-right: 12px; }
.rf-asm-sheet .rf-asm-grow:active { background: none; }
.rf-asm-sheet .rf-asm-grow + .rf-asm-grow::before { left: 20px; }
.rf-asm-grow .rf-row__name { font-size: 16px; font-weight: 500; white-space: normal; overflow-wrap: anywhere; }
/* Номер метки — целиком, в две строки: EPC одной партии различаются хвостом, и
   обрезка многоточием делала все строки перечня одинаковыми. */
.rf-asm-grow .rf-asm-grow__code {
  font-family: var(--mono); font-size: 14px; line-height: 1.4; letter-spacing: .01em;
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
.rf-asm-sheet__count { color: var(--muted); font-size: 14px; }
.rf-asm-sheet__ico--ask { background: var(--accent-soft); color: var(--accent-text); }
.rf-asm-cand__busy { flex: none; color: var(--accent-text); font-size: 13px; font-weight: 600; }
.rf-asm-sheet__actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
.rf-asm-sheet__actions .rf-btn { flex: 1; width: auto; }
.rf-asm-sheet__actions .rf-btn--text { flex: 0 0 auto; min-width: 104px; }
.rf-asm-sheet__actions .rf-btn--ghost { background: var(--surface-2); }
.rf-asm-btn--danger { background: var(--danger-soft); color: var(--bad-ink); }

@keyframes asm-in { from { opacity: 0; transform: translateY(6px); } }

@media (prefers-reduced-motion: reduce) {
  .rf-asm-feed__item, .rf-asm-sheet, .rf-asm-sheet__panel { animation: none; }
  .rf-asm-refresh.is-spin svg { animation: none; }
}
