:root {
  --border: #e2e2e2;
  --muted: #666;
  --bg: #fafafa;
  font-family: -apple-system, Segoe UI, Arial, sans-serif;
}
* { box-sizing: border-box; }
/* BACKLOG.md §30 п.3: переход по якорю (#, из подсказки «?», из ссылки
   «Что нового») не должен прятать заголовок раздела под закреплённой
   шапкой - тот же --sticky-top, что уже сдвигает липкую панель таблиц
   и липкие оглавления (common.js updateStickyOffsets). */
html { scroll-padding-top: calc(var(--sticky-top, 0px) + 8px); }
body {
  margin: 0;
  background: var(--bg);
  color: #1a1a1a;
}
/* UX-TASK-5 п.9: раньше .header-left группировал h1+nav одним flex-блоком
   слева, из-за чего пункты меню начинались сразу за названием раздела и
   сдвигались при каждом переходе - у пяти страниц пять разных по длине
   заголовков ("Деньги" короче "Импорт заказов TaoBao"). Теперь навигация -
   отдельная строка ПОД заголовком, её положение не зависит от длины h1
   вообще.
   header остаётся flex-контейнером (weigh.html - единственная страница без
   .header-top и без nav - по-прежнему кладёт туда ровно h1 + правый блок и
   полагается на justify-content:space-between - тот же приём, что был
   всегда: у неё нет своей навигации, см. AUTH-TASK.md §5б). Для страниц с
   навигацией (менеджер: manager.html/shipments.html/import.html/money.html/
   settings.html; клиент: client.html/balance.html/client-settings.html,
   BACKLOG.md §21.11) header держит ДВА элемента - .header-top (сам растёт
   на flex:1, внутри у него своё space-between между h1 и "кто я/выйти") и
   nav#mainNav (flex-basis:100% принудительно переносит его на вторую строку
   независимо от ширины экрана, а не только когда не хватило места). */
header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  background: #fff;
  border-bottom: 1px solid var(--border);
  /* BACKLOG.md §30 п.3 (решение Игоря): шапка закреплена при прокрутке,
     НИЖЕ плашки «вы работаете как» (--banner-h, common.js
     updateStickyOffsets) - если плашки нет, --banner-h отсутствует и
     var(..., 0px) подставляет 0. z-index выше таблиц/панелей
     (.table-sticky-panel: 30, thead th: не задан) и ниже самой плашки
     (.acting-banner: 200) - шапка не обязана перекрывать предупреждение
     о чужом кабинете. position:sticky - здесь первая и единственная
     позиция у header, отдельного position:relative рядом нет (см.
     docs/rules/frontend.md про эту ловушку). */
  position: sticky;
  top: var(--banner-h, 0px);
  z-index: 100;
}
header h1 { font-size: 16px; margin: 0; }
.header-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; flex: 1 1 auto;
}
main { max-width: 1240px; margin: 0 auto; padding: 20px; }
/* BACKLOG.md §21.10, волна 1 - manager.html не тянется на всю ширину
   монитора отдельно от общего правила выше (менять его нельзя, оно на
   всех страницах обеих ролей). Одно число, которое §20.10 позже сделает
   настройкой в профиле пользователя на сервере. */
main.wide { max-width: 1600px; }
.card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
}
/* Таблицы стали широкими (вес, доставка, страховка, кнопки действий) и
   вылезали за пределы карточки - последняя колонка оказывалась снаружи.
   Обёртка даёт горизонтальную прокрутку. Именно обёртка, а не .card:
   overflow на самой карточке заодно обрезал бы поле комментария, которое
   разворачивается при вводе. */
.table-wrap { overflow-x: auto; max-width: 100%; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
/* cursor:pointer у ВСЕХ th здесь был враньём интерфейса: указатель обещал
   клик, а кликать было нечего (BACKLOG.md §20.10). Теперь указатель только
   там, где клик действительно сортирует, - класс .sortable вешает
   createTableLayout (common.js) ровно тем колонкам, у которых есть поле
   серверной сортировки. Держит cssSortCursorGuard в cssStickyColumnGuard.test.js. */
th { color: var(--muted); font-weight: 600; user-select: none; }
th.sortable { cursor: pointer; }
th.sortable:hover { color: #1a1a1a; }
.sort-arrow { margin-left: 4px; font-size: 10px; color: #2a7f2e; }
img.thumb { width: 44px; height: 44px; object-fit: cover; border-radius: 6px; background: #eee; }
.badge { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 12px; color: #fff; }
/* UX-TASK-5 п.5 - "Применено, без трека" (import.html): применённая строка
   без трека всё ещё требует внимания и не должна читаться как "всё хорошо".
   Тот же жёлтый, что у чипа "Выкуплено без трека" на manager.html
   (.no-track-chip) - две вещи про одно и то же и выглядят одинаково. */
.badge.badge-warning { background: #fff8e1; color: #7a5b00; border: 1px solid #d8a300; }
.balance-negative { color: #b3261e; font-weight: 600; }
/* Импорт заказов (import.html), задача 2026-08-15: рядом на строке может
   стоять до четырёх предупреждений - если все выглядят одинаково, важное
   тонет в неважном. Три уровня заметности. Выбраны Игорём по живому
   предпросмотру: он сравнивал варианты на одной строке рядом, а не по
   описанию словами - первая попытка "цвет только на значке у всех" его не
   устроила именно на экране.

   1. .balance-negative целиком (красный текст + полужирный) - деньги
      посчитаны не так, как надо: paymentMismatchNote (источники разошлись),
      mismatchNote в ветке "списано больше, чем цена выкупа + доставка"
      (менеджер недоберёт с клиента), orderTotalUnknownNote. Красный значок
      внутри - усиление, а не замена: текст тоже красный.
   2. .warn-badge-yellow - значок с заливкой, самый заметный из некрасных.
      Только ambiguousKeyNote: таких строк 4 из 148, они редкие и требуют
      действия глазами, поэтому громкость оправдана.
   3. .warn-icon-yellow - обычный текст с янтарным значком. fallbackNote:
      таких заказов 38 из 120, то есть треть - значок нужен (Игорь просил
      его прямо), но заливка на трети строк превратилась бы в фон.

   Янтарный #a35c00 вместо прежнего #7a5b00: тёмно-жёлтый глаз читает как
   обычный серый текст и внимания не привлекает - проверено Игорем на
   экране. Заливка чипа - тот же жёлтый, что у .badge-warning и
   .no-track-chip, чтобы "предупреждение" всюду выглядело одинаково. */
.warn-icon-red { color: #b3261e; }
/* §32 ТЗ A: значок сверки позиции на «Импорте». */
.recon-round { color: var(--muted); font-size: 11px; }
.recon-bad { color: #b3261e; font-size: 11px; font-weight: 600; }
.warn-icon-yellow { color: #a35c00; }
.warn-badge-yellow {
  display: inline-block;
  background: #ffe08a;
  color: #5c4200;
  border-radius: 4px;
  padding: 0 5px;
  font-weight: 700;
}
input, select, textarea, button {
  font: inherit;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
}
/* Б1 (живая проверка Игоря 2026-09-11): атрибут hidden НЕ прячет элемент,
   которому автор задал display. У браузера "[hidden] { display: none }"
   лежит в СВОЕЙ таблице стилей, а любое авторское правило её перебивает -
   .bulk-bar { display: flex } делал панель массовых действий вечно видимой
   и в manager.html, и на экране импорта, хотя код исправно выставлял
   element.hidden. Правило ниже возвращает атрибуту обещанный смысл для всей
   системы, чтобы следующая такая панель не выстрелила снова. */
[hidden] { display: none !important; }
button { cursor: pointer; background: #2a7f2e; color: #fff; border: none; }
button.secondary { background: #eee; color: #222; }
button.danger { background: #b3261e; }
button:disabled { cursor: not-allowed; opacity: .4; }
/* Б4 (живая проверка Игоря 2026-09-11): ни одна кнопка системы не
   показывала нажатие - правил :active не было вовсе. Один общий отклик
   вместо правила на каждый вид кнопки: filter темнит любой фон, какой бы
   класс его ни задал (.secondary, .danger, .chip, .unsaved), поэтому
   переопределять их по отдельности не нужно. Смещение на 1px даёт
   физическое ощущение нажатия там, где цвет почти не меняется. */
button:active:not(:disabled) { transform: translateY(1px); filter: brightness(0.9); }
/* UX-TASK-5 п.2 - подсветка несохранённых изменений (settings.html, форма
   ставок): дешёвая страховка от "ввёл и ушёл" - именно так один раз
   заморозились пять товаров импорта с нулевой комиссией. */
button.unsaved { background: #d8a300; }
button.unsaved:hover { background: #c29400; }
/* Браузер рисует внутри color-инпута ещё одну рамку с отступом - получается
   "прямоугольник в прямоугольнике". Убираем внутреннюю обёртку, чтобы
   плашка была залита цветом целиком. */
/* Стрелки-крутилки у числовых полей съедают ~20px ширины в каждой колонке
   и в таблице бесполезны: цену и количество вводят с клавиатуры, а не
   накликивают по единице. */
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

input[type="color"] { width: 46px; height: 30px; padding: 2px; cursor: pointer; }
input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"]::-webkit-color-swatch { border: none; border-radius: 4px; }
input[type="color"]::-moz-color-swatch { border: none; border-radius: 4px; }

/* Комментарий часто длинный. Держим поле компактным в таблице, но
   разворачиваем при вводе, чтобы текст можно было прочитать целиком. */
textarea.field {
  width: 170px; height: 32px; resize: vertical;
  font: inherit; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px;
  transition: height .12s ease;
}
textarea.field:focus { height: 110px; outline: 2px solid #2a7f2e33; }
td .actions { display: flex; gap: 6px; flex-wrap: nowrap; align-items: center; }
/* BACKLOG.md §33: ответ «В корзину TaoBao» и отметка «в корзине ЧЧ:ММ» под
   кнопками действий строки товара. Переносятся по словам - текст ответа
   обязан читаться целиком (это то, что человеку делать дальше). */
.cart-note, .cart-mark { font-size: 12px; line-height: 1.3; margin-top: 4px; white-space: normal; }
.cart-note-ok { color: #2a7f2e; }
.cart-note-warn { color: #8a5a00; }
.cart-note-error { color: #b3261e; }
.cart-note-busy { color: var(--muted); }
/* §33, шаг 2: ход и итог пачки «В корзину TaoBao» - под панелью выбранного,
   в той же липкой обёртке (высоту меряет updateStickyOffsets). */
.cart-batch {
  display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
  background: #fff8e6; border: 1px solid #e6c97a; border-radius: 8px;
  padding: 8px 12px; margin-bottom: 14px;
}
.cart-batch-line { flex: 1 1 100%; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }
.cart-batch > button { flex: 0 0 auto; }

/* BACKLOG.md §21.10, волна 1 - длинный текст в "Название"/"Комментарии" у
   менеджера (у TaoBao названия бывают в сотню символов) растягивал всю
   таблицу вширь и уводил колонку действий за экран. Обрезаем визуально в
   одну строку - полный текст остаётся в атрибуте title (см. manager.html,
   через escapeHtml) и в модалке правки (кнопка ✎), поэтому это не то
   "молчаливое выбрасывание введённого человеком значения", которое
   CLAUDE.md запрещает - значение никуда не делось, просто не показано
   сразу целиком. Флекс здесь не нужен и не должен появиться на <td> - см.
   cssTdFlexGuard.test.js. На телефоне (≤720px) обрезка снимается целиком -
   см. медиа-блок #mgrItemsTable ниже: title пальцем недостижим.

   §20.10 (волна B1) ЗАБРАЛ ОТСЮДА max-width: 220px. С table-layout: fixed
   у каждой колонки есть своя явная ширина, и жёсткие 220px либо ничего не
   значат, либо прямо конфликтуют с настройкой человека: растянул колонку до
   400px - а текст всё равно обрезается по 220. Теперь граница обрезки - это
   ширина СВОЕЙ колонки, а класс отвечает ровно за две вещи: не переносить
   строку и показать многоточие. */
.cell-clip {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Пара к .cell-clip - галочка «обрезать» снята (BACKLOG.md §24.10): текст
   виден целиком, строка таблицы растёт по высоте. pre-wrap - чтобы переносы
   строк в комментарии остались переносами; anywhere - чтобы длинная ссылка
   без пробелов не распирала колонку шире заданной (table-layout: fixed). */
.cell-wrap {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Колонки логистики: пока в списке нет ни одного отправленного товара,
   показывать нечего - прячем их целиком, а не рисуем столбцы прочерков. */
table.no-logistics th.log, table.no-logistics td.log { display: none; }

/* Колонка "Итого ≈$" (MONEY-MODEL.md §3.6) больше не прячется целиком
   (UX-TASK-4 п.1, отменяет прежнее правило): пустая колонка ничего не
   объясняет и выглядит как поломка. Пока итога нет - "?" в ячейке, причина
   в подсказке. Класс no-total и связанные с ним правила больше не
   используются нигде в JS - см. totalExplainTitle() в client.html. */

/* Селект статуса тянулся по самому длинному названию ("Отправлено из
   Китая") и раздувал таблицу. Ограничиваем ширину: выбранное значение
   всё равно читается, а полный список виден при раскрытии. */
td select.field, td select.m-field { max-width: 150px; }

/* Ссылка на товар - иконкой: слово "открыть" занимало заметно больше
   места, чем несёт смысла. */
a.ext {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 6px;
  color: #2563eb; text-decoration: none; font-size: 15px;
}
a.ext:hover { background: #eef4ff; }

.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
.tabs .tab {
  background: none; color: var(--muted); border: none; border-bottom: 2px solid transparent;
  border-radius: 0; padding: 8px 14px; font-weight: 600;
}
.tabs .tab.active { color: #1a1a1a; border-bottom-color: #2a7f2e; }
tr.unweighed td { background: #fff8f0; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.toolbar { display: flex; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; align-items: center; }
form.add-item { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 16px; }
/* 1/-1 вместо span 3: на телефоне сетка формы сжимается до одной колонки,
   и "во всю ширину" должно оставаться во всю ширину. */
form.add-item textarea, form.add-item .span-all { grid-column: 1 / -1; }
a.link { color: #2563eb; text-decoration: none; }
.muted { color: var(--muted); font-size: 12px; }
/* Запасная «Дата выкупа» (BACKLOG.md §27): дата перевода в статус с
   начислением, когда заказа TaoBao нет. Серый курсив - решение Игоря
   2026-10-15; размер шрифта как у обычной даты, чтобы колонка не прыгала. */
.date-fallback { color: var(--muted); font-style: italic; }
/* Пояснения к разделам стали длиннее одного абзаца (замечание Игоря
   2026-08-14 про описание корректировки баланса): сплошной текст в них не
   читается, а браузерный отступ у <ul> по умолчанию слишком большой и
   отрывает список от абзаца над ним. */
/* Блок "Установка по шагам" в справке (BACKLOG.md §21.7). Отдельным классом,
   а не инлайновыми стилями в help.html: замечание Игоря 2026-09-16 - список
   выглядел аляповато. Приём тот же, что у .explain-list выше - опора на
   существующие переменные темы, без новых цветов. */
.steps {
  margin: 14px 0;
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.steps-title { margin: 0 0 8px; font-weight: 600; }
.steps-list { margin: 0; padding-left: 20px; }
.steps-list li { margin-bottom: 10px; line-height: 1.45; }
.steps-list li:last-child { margin-bottom: 0; }
.steps-list code { white-space: nowrap; }
/* Кнопка "Установить для Firefox" в справке (help.html, BACKLOG.md §21.7,
   находка приёмки 2026-09-16): раньше была <a> без класса и с инлайновыми
   стилями, которые задавали только отступы и flex, но НЕ цвет - визуально
   не отличалась от обычного текста ни разу, хотя шаг 1 инструкции обещал
   "синюю кнопку" (которой не было и не будет - в системе вообще нет синих
   кнопок, все зелёные, см. правило button выше). Реализовано на <a>, а не
   <button>: это ссылка на файл (переход по href), а не обработчик клика.
   Цвет - тот же зелёный, что у обычной <button>: своего цвета для этой
   кнопки не заводим. */
a.btn-install {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  background: #2a7f2e;
  color: #fff;
  text-decoration: none;
  border-radius: 6px;
  font: inherit;
}
a.btn-install:hover { filter: brightness(0.95); }
a.btn-install:active { transform: translateY(1px); filter: brightness(0.9); }
/* Погашенное состояние (сборки ещё нет на сервере - loadExtensionInstallLink()
   в help.html снимает href и ставит aria-disabled) ОБЯЗАНО остаться видимым
   как кнопка, просто неактивная - находка приёмки: без href браузер рисует
   <a> как простой текст, и единственное действие страницы пропадало
   визуально совсем, хотя разметка на месте. */
a.btn-install[aria-disabled="true"] {
  background: var(--border);
  color: var(--muted);
  cursor: not-allowed;
  pointer-events: none;
}

.explain-list { margin: 0; padding-left: 18px; }
.explain-list li { margin-bottom: 4px; }
.explain-list li:last-child { margin-bottom: 0; }
.status-select { display: flex; align-items: center; gap: 6px; }

/* Photo drop zone: click to pick a file, drag an image onto it, or Ctrl+V */
.dropzone {
  position: relative;
  border: 2px dashed var(--border);
  border-radius: 8px;
  padding: 10px;
  min-height: 76px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  background: #fcfcfc;
}
.dropzone:hover, .dropzone:focus { border-color: #2a7f2e; outline: none; }
.dropzone.dragover { border-color: #2a7f2e; background: #f0f8f0; }
.dropzone.busy { opacity: .6; pointer-events: none; }
.dropzone img { max-height: 64px; max-width: 100%; border-radius: 6px; object-fit: contain; }
.dropzone .hint b { color: #2a7f2e; }
img.thumb.clickable { cursor: pointer; }
img.thumb.clickable:hover { outline: 2px solid #2a7f2e; }
td.photo-cell .placeholder {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 1px dashed var(--border); border-radius: 6px;
  color: var(--muted); font-size: 16px; cursor: pointer; background: #fcfcfc;
}

/* Large photo preview / replace modal (see openImagePreview in common.js) */
.img-modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000; padding: 20px;
}
.img-modal {
  position: relative;
  background: #fff; border-radius: 12px; padding: 20px;
  max-width: min(600px, 92vw); max-height: 90vh;
  display: flex; flex-direction: column; gap: 14px; align-items: stretch;
}
.img-modal-photo { max-width: 100%; max-height: 60vh; object-fit: contain; border-radius: 8px; margin: 0 auto; }
.img-modal-empty {
  padding: 40px; text-align: center; color: var(--muted);
  border: 1px dashed var(--border); border-radius: 8px;
}
.img-modal-close {
  position: absolute; top: 8px; right: 8px;
  width: 30px; height: 30px; border-radius: 50%;
  background: #eee; color: #222; border: none; font-size: 18px; line-height: 1;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.img-modal .dropzone { min-height: 60px; }

/* ===== UX-ревью 2026-08-06 ===== */

/* Тост "Сохранено" (showToast в common.js): единый сигнал, что правка в
   ячейке реально ушла на сервер. Внизу по центру, не перекрывает контент. */
#toast {
  position: fixed; left: 50%; bottom: 24px;
  transform: translateX(-50%) translateY(60px);
  background: #1a1a1a; color: #fff; padding: 10px 18px; border-radius: 8px;
  font-size: 14px; opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  z-index: 2000; max-width: 90vw; text-align: center;
}
#toast.show { opacity: .95; transform: translateX(-50%) translateY(0); }
#toast.error { background: #b3261e; }
/* Заметное уведомление (BACKLOG.md §26, З1): деньги сдвинулись как побочный
   эффект действия - не ошибка, но мимо глаз пройти не должно. */
#toast.warn { background: #8a4b00; }
/* Уведомление о деньгах с «Отменить» (BACKLOG.md §21.3, ревизия 2): кнопку
   надо нажимать, поэтому у этого вида toast принимает клики. */
#toast.with-action { pointer-events: auto; display: flex; align-items: center; gap: 12px; text-align: left; }
#toast .toast-undo {
  flex: none; background: transparent; color: #8fd18f; border: 1px solid #8fd18f;
  border-radius: 6px; padding: 4px 10px; font-size: 13px; font-weight: 600; cursor: pointer;
}
#toast .toast-undo:disabled { opacity: .5; cursor: default; }

/* Чипы статусов над таблицей менеджера: утренний обзор одним взглядом
   ("сколько в каком статусе") и фильтр в один клик вместо выпадающего
   списка. Цвет статуса - полоской слева. */
.chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
button.chip {
  border: 1px solid var(--border); background: #fff; color: #1a1a1a;
  border-radius: 14px; padding: 5px 12px; font-size: 12px; cursor: pointer;
}
button.chip.active { border-color: #2a7f2e; background: #2a7f2e14; font-weight: 600; }
button.chip .cnt { color: var(--muted); }

/* IMPORT-TASK.md §9.2 - чип "Выкуплено без трека" на manager.html. Отдельный
   от статусных чипов цвет по умолчанию (жёлтый, "требует внимания"), а не
   нейтральный - его вообще не должно быть видно в норме (скрыт, пока
   товаров с пустым треком нет). Красный - только когда есть товары старше
   7 дней, то есть трек уже в зоне риска пропасть из выгрузки TaoBao. */
button.chip.no-track-chip {
  margin-bottom: 10px; border-color: #d8a300; background: #fff8e1; color: #7a5b00; font-weight: 600;
}
button.chip.no-track-chip.active { border-color: #2a7f2e; background: #2a7f2e14; color: #1a1a1a; }
button.chip.no-track-chip.stale { border-color: #b3261e; background: #fdecea; color: #b3261e; }

input[type="search"] { min-width: 180px; }

/* Крупная строка "К оплате" у клиента: ответ на главный вопрос ("сколько я
   должен") - первым и самым заметным, а не выводом из двух чисел со знаком. */
.balance-summary { font-size: 20px; margin-bottom: 10px; }
.balance-summary .ok { color: #2a7f2e; font-weight: 600; }

/* Мобильная версия кабинета клиента: таблица товаров превращается в
   карточки (фото + название сверху, остальные поля строками с подписями
   через data-label). Управляющие страницы менеджера остаются десктопными,
   для телефона у него есть weigh.html. */
@media (max-width: 720px) {
  main { padding: 10px; }
  .card { padding: 10px; }
  form.add-item { grid-template-columns: 1fr; }
  header { padding: 10px 12px; }
  .header-top { gap: 6px; }

  #itemsTable, #itemsTable tbody { display: block; }
  #itemsTable thead { display: none; }
  #itemsTable tr {
    display: grid; grid-template-columns: 64px 1fr; gap: 6px 10px;
    align-items: center; padding: 12px 2px;
    border-bottom: 1px solid var(--border);
  }
  #itemsTable td { display: block; border: none; padding: 0; }
  #itemsTable td.photo-cell { grid-row: span 2; }
  #itemsTable td[data-label] {
    grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
  }
  #itemsTable td[data-label]::before {
    content: attr(data-label); color: var(--muted); font-size: 12px;
    min-width: 96px; flex-shrink: 0;
  }
  #itemsTable td:empty { display: none; }
  /* Служебные строки ("Пока нет товаров") - во всю ширину карточки. */
  #itemsTable td[colspan] { grid-column: 1 / -1; }
  /* Скрытые классом no-logistics колонки должны остаться скрытыми
     и в карточной вёрстке (display:flex у [data-label] сильнее). */
  table.no-logistics td.log { display: none !important; }
  #itemsTable td:last-child { grid-column: 1 / -1; }
  /* Инпуты растягиваются на всю строку карточки; !important нужен, чтобы
     перебить инлайновые width из шаблонов строк (десктопные ширины). */
  #itemsTable input.field, #itemsTable select.field, #itemsTable textarea.field {
    width: auto !important; flex: 1; min-width: 0;
  }
  /* Пальцем по 30-пиксельной кнопке попасть трудно (Apple HIG: >=44pt). */
  #itemsTable .actions button { padding: 10px 16px; font-size: 16px; }
  img.thumb, td.photo-cell .placeholder { width: 64px; height: 64px; }
}

/* ===== Второй заход по интерфейсу (UX-TASK-2.md, 2026-08-07) ===== */

/* П.1 — единая навигация по разделам менеджера (common.js renderManagerNav).
   Активный раздел - не ссылка, а "вы здесь": другой фон, не подчёркнут. */
/* flex-basis:100% - см. комментарий у header выше: заставляет nav занять
   всю ширину строки и перенестись под .header-top безусловно. */
.mainnav { display: flex; gap: 4px; flex-wrap: wrap; flex-basis: 100%; margin-top: 8px; }
.mainnav .nav-item {
  position: relative;
  display: inline-block;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 13px;
  text-decoration: none;
  color: var(--muted);
}
.mainnav a.nav-item:hover { background: #f0f0f0; color: #1a1a1a; }
.mainnav .nav-item.active { background: #2a7f2e14; color: #2a7f2e; font-weight: 600; }
/* П.2 — красная точка на "Деньги", когда есть заявки на рассмотрении. */
.nav-dot {
  position: absolute; top: 3px; right: 3px;
  width: 7px; height: 7px; border-radius: 50%; background: #b3261e;
}

/* П.5 — панель массовых действий над таблицей товаров менеджера: раньше
   9+ контролов (что вижу + что делаю с выбранным) жили в одной строке.
   Фильтры/поиск видны всегда (.toolbar), эта панель - только когда
   выбрана хотя бы одна строка (JS показывает/прячет через display). */
.bulk-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #2a7f2e0d; border: 1px solid #2a7f2e33; border-radius: 8px;
  padding: 8px 12px; margin-bottom: 14px;
}
.bulk-bar .count { font-weight: 600; }

/* П.4 — переименование клиента на месте, карандашом (money.html). */
.name-cell { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* П.7 — кнопки "Изъять" в "Что внутри" (shipments.html): раньше шли inline
   сразу после названия и плясали по горизонтали из-за разной длины текста.
   Грид с фиксированной второй колонкой строит их в ровный столбик справа
   независимо от длины названия слева. */
/* Состав посылки: фото | название и цена | кнопка. Фиксированные крайние
   колонки держат кнопки "Изъять" в ровный столбик независимо от длины
   названия (исходный смысл этого блока), а фото и цена добавлены
   2026-08-10 - по одному названию собранную коробку глазами не сверить. */
.item-remove-row {
  display: grid; grid-template-columns: 52px 1fr 84px; gap: 8px; align-items: center;
  padding: 5px 0; border-bottom: 1px solid var(--border);
}
.item-remove-row:last-child { border-bottom: none; }
.item-remove-row button { width: 100%; }
.content-item-photo { display: flex; }
.content-item-photo .placeholder {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 1px dashed var(--border); border-radius: 6px;
  color: var(--muted); background: #fcfcfc;
}
.content-item-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* П.8 — объяснение денежных цифр реальными числами: единый приём - обычный
   атрибут title (тот же, что уже использовался в проекте для подсказок у
   "Вес"/"Комис.%"), плюс маленькая иконка ⓘ там, где рядом нет своего
   number-поля, чтобы было видно, что подсказка вообще есть. */
.explain-hint {
  display: inline-block; margin-left: 4px; color: var(--muted);
  font-size: 11px; cursor: help; border: 1px solid var(--border);
  border-radius: 50%; width: 14px; height: 14px; line-height: 12px; text-align: center;
}
/* Та же «?», но кнопкой с панелью (initHelpTips в common.js, BACKLOG.md §27):
   сбрасываем оформление <button>, чтобы она выглядела как прежние «?». */
button.explain-hint.help-tip {
  padding: 0; background: transparent; min-height: 0; box-sizing: border-box;
  font-family: inherit; vertical-align: middle;
}
button.explain-hint.help-tip[aria-expanded="true"] { color: var(--text, #222); border-color: var(--muted); }
.help-pop {
  position: fixed; z-index: 50; box-sizing: border-box;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 6px 24px rgba(0,0,0,.14); padding: 10px 14px;
  font-size: 13px; line-height: 1.45; color: inherit;
}
.help-pop p { margin: 6px 0; }
.help-pop ol { margin: 6px 0; padding-left: 20px; }
.help-pop .help-pop-title { font-weight: 600; margin-top: 0; }

/* П.9 — кнопка "Сообщить об оплате" у клиента: крупная, в правой части
   карточки баланса. Клик раскрывает и подсвечивает форму заявки ниже. */
.balance-card-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
#reportPaymentBtn { font-size: 15px; padding: 10px 18px; }
/* .highlight — универсальный класс (не привязан к #claimsSection), чтобы
   тем же приёмом "прокрутка + подсветка" можно было пользоваться где угодно
   на странице. UX-TASK-4 п.7 переиспользует его для #historySection в
   money.html - тот же код, что уже был у "Сообщить об оплате". */
.highlight {
  outline: 2px solid #2a7f2e; outline-offset: 4px; border-radius: 10px;
  animation: highlight-fade 1.8s ease;
}
@keyframes highlight-fade {
  0% { background: #2a7f2e14; }
  100% { background: transparent; }
}

/* BACKLOG.md §21.10, волна 1 - колонка действий (✎/⧉/🗑) не должна уезжать
   за край экрана НИКОГДА: закрепляем последнюю ячейку каждой строки внутри
   уже существующей обёртки .table-wrap (overflow-x:auto, style.css выше).
   Непрозрачный фон обязателен - иначе под закреплённой ячейкой при
   горизонтальной прокрутке просвечивает уезжающий текст соседних колонок;
   левая граница - чтобы было видно, что это накладка, а не край таблицы.
   Только последняя ячейка каждой строки/шапки - th:last-child/td:last-child
   без своего класса в разметке, добавлять его в manager.html не пришлось.
   Это правило должно стоять В ИСХОДНОМ ТЕКСТЕ ФАЙЛА раньше мобильного
   медиа-блока #mgrItemsTable ниже: у обоих правил на td:last-child
   одинаковая специфичность, а на телефоне закрепление обязано сняться
   (position:static там) - при равной специфичности выигрывает то, что
   стоит в файле ПОЗЖЕ, и мобильный блок должен идти последним. */
#mgrItemsTable th:last-child,
#mgrItemsTable td:last-child {
  position: sticky;
  right: 0;
  background: #fff;
  border-left: 1px solid var(--border);
}
/* Ловушка, из-за которой это чинят дважды: общее правило `table` выше
   (border-collapse: collapse) у sticky-ячейки в конце строки убирает
   нижнюю border - строки визуально слипаются. Меняем режим границ ТОЛЬКО
   для #mgrItemsTable, не трогая общее правило для остальных таблиц;
   border-spacing:0 не даёт separate-режиму раздвинуть ячейки, а свои
   border-bottom th/td получают из общего правила "th, td { border-bottom
   ... }" выше и в separate-режиме не теряются. */
#mgrItemsTable {
  border-collapse: separate;
  border-spacing: 0;
}

/* ===== BACKLOG.md §20.10, волна B1 — настройка колонок и закрепление =====
   Весь блок обязан стоять В ФАЙЛЕ РАНЬШЕ мобильного медиа-блока
   #mgrItemsTable ниже: у правил одинаковая специфичность, и при равенстве
   выигрывает стоящее позже (та же ловушка, что оплачена волной 1).

   ПОРЯДОК НАЛОЖЕНИЯ (z-index), одной строкой: панель(30) > угол шапки(25) >
   строка заголовков(20) > закреплённая колонка действий(10) > обычные
   ячейки(без z-index) — потому что при прокрутке вправо угол обязан
   накрывать и шапку, и колонку действий, а панель — вообще всё.

   Горизонтальная прокрутка у СТРАНИЦЫ, а не у обёртки: см. комментарий в
   manager.html и docs/rules/frontend.md. */
/* Класс, а не :has(): признак «прокрутка у страницы» должен быть виден
   прямо в разметке и не зависеть от поддержки :has() браузером. */
.table-wrap.page-scroll { overflow-x: visible; overflow-y: visible; }

/* Таблица с ЯВНЫМИ ширинами колонок - иначе перетаскивание границы
   заголовка не работает: без fixed браузер раскладывает колонки по
   содержимому и заданная ширина остаётся пожеланием. Ширину самой таблицы
   и каждого <col> считает JS (applyLayout в common.js). */
#mgrItemsTable { table-layout: fixed; background: #fff; }
/* Обрезка по ширине СВОЕЙ колонки для всех ячеек, а не только для
   .cell-clip: при фиксированной раскладке текст, которому не хватило
   места, иначе вылезал бы поверх соседней колонки. Полный текст в title -
   у «Название» и «Комментария» (.cell-clip): это поля, которые заполняет
   человек. */
#mgrItemsTable th, #mgrItemsTable td { overflow: hidden; text-overflow: ellipsis; }
/* Поля ввода тянутся по своей колонке, а не по инлайновым width из шаблона
   строки: ширину теперь задаёт колонка, и второе число рядом с ней
   разъехалось бы при первом же перетаскивании. */
#mgrItemsTable td input.m-field, #mgrItemsTable td select.m-field { width: 100%; max-width: 100%; }

/* Липкая панель управления: фильтры/поиск/«Колонки» + панель массовых
   действий в одной обёртке. Высота НЕ зашита числом - её меряет
   updateStickyOffsets() (common.js) и кладёт в --table-panel-h: панель
   переносится на две строки в узком окне и вырастает при появлении
   .bulk-bar. --sticky-top - высота красной плашки «вы работаете в кабинете
   менеджера X» (.acting-banner тоже sticky top:0), 0 когда её нет. */
.table-sticky-panel {
  position: sticky;
  top: var(--sticky-top, 0px);
  z-index: 30;
  background: #fff;
  padding-top: 8px;
}
#mgrItemsTable thead th {
  position: sticky;
  top: calc(var(--sticky-top, 0px) + var(--table-panel-h, 0px));
  z-index: 20;
  background: #fff;
}
/* Угловая ячейка - закреплена по ОБЕИМ осям сразу: она же последняя в
   шапке (значит sticky right:0 из правила выше) и одновременно её верх. */
#mgrItemsTable thead th:last-child { z-index: 25; }
#mgrItemsTable tbody td:last-child { z-index: 10; }

/* ===== BACKLOG.md §20.10, волна B2 — тот же механизм на #reconcileTable
   («Импорт»). Задача просит закрепление ПАНЕЛИ И СТРОКИ ЗАГОЛОВКОВ, но не
   просит закреплённую последнюю колонку действий (её на этой таблице и
   нет) - поэтому здесь НЕТ аналога th:last-child/td:last-child sticky
   right:0 и связанной ловушки border-collapse из блока B1 выше: эта
   таблица использует общий border-collapse:collapse без последствий.
   Мобильный режим - через класс .mobile-simple-table (уже в разметке,
   уже не привязан к ID), поэтому и порядок правил в файле относительно
   мобильного медиа-блока здесь не имеет значения, в отличие от
   #mgrItemsTable выше. */
#reconcileTable { table-layout: fixed; background: #fff; }
#reconcileTable th, #reconcileTable td { overflow: hidden; text-overflow: ellipsis; }
#reconcileTable thead th {
  position: sticky;
  top: calc(var(--sticky-top, 0px) + var(--table-panel-h, 0px));
  z-index: 20;
  background: #fff;
}

/* Ручка перетаскивания границы: узкая полоса у правого края заголовка.
   Свой курсор (col-resize), а не указатель сортировки - на границе клик
   делает другое.
   ОТДЕЛЬНОГО `position: relative` заголовку здесь НЕ ставить, хотя ручка
   и абсолютная: position:sticky сам по себе задаёт систему координат для
   абсолютных потомков, а правило `#mgrItemsTable thead th{position:relative}`
   с той же специфичностью, стоящее НИЖЕ, перебило бы sticky - шапка
   перестала бы липнуть молча, и ни один текстовый тест этого бы не увидел
   (оба правила существуют). Поймано измерением в браузере: шапка после
   прокрутки на 1200px уезжала на -790px. Держит сторожевой тест
   «ничто ниже по файлу не перебивает position у шапки». */
.col-resizer {
  position: absolute; top: 0; right: 0; width: 7px; height: 100%;
  cursor: col-resize; user-select: none;
}
.col-resizer:hover, .col-resizer.dragging { background: #2a7f2e55; }
/* Видимый разделитель на границе колонок (просьба Игоря 2026-09-25): тонкая
   черта на половину высоты, чуть темнее линий таблицы, - намёк «здесь можно
   тянуть», не спорящий с текстом заголовка. Над шапкой темнеет, над самой
   ручкой и во время перетаскивания - зелёная на всю высоту. pointer-events
   none: черта не перехватывает мышь у ручки. */
.col-resizer::after {
  content: ''; position: absolute; top: 25%; bottom: 25%; right: 0; width: 1px;
  background: #d4d4d4; pointer-events: none;
}
thead:hover .col-resizer::after { background: #a8a8a8; }
.col-resizer:hover::after, .col-resizer.dragging::after { top: 0; bottom: 0; width: 2px; background: #2a7f2e; }

/* Список колонок с галочками. Абсолютный, чтобы раскрытие не двигало
   панель и не меняло её высоту под липкой шапкой. */
.col-menu { position: relative; }
.col-menu > summary {
  cursor: pointer; list-style: none; user-select: none;
  background: #eee; color: #222; border-radius: 6px; padding: 6px 10px; font-size: inherit;
}
.col-menu > summary::-webkit-details-marker { display: none; }
.col-menu-list {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  min-width: 210px; max-height: 60vh; overflow-y: auto;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
}
.col-menu-item { display: flex; align-items: center; gap: 8px; padding: 4px 2px; cursor: pointer; }
/* Строка списка «Колонки»: показ колонки и, у текстовых, «обрезать» справа
   (BACKLOG.md §24.10). */
.col-menu-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.col-menu-clip { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--muted); cursor: pointer; white-space: nowrap; }
.col-menu-item.locked { color: var(--muted); cursor: not-allowed; }

/* П.6 — таблица товаров менеджера на телефоне: те же карточки, что у
   клиента (data-label на td), но с полностью рабочей правкой полей.
   #mgrItemsTable - id таблицы менеджера, отдельный от клиентского
   #itemsTable, чтобы не задеть существующую вёрстку клиента. */
@media (max-width: 720px) {
  #mgrItemsTable, #mgrItemsTable tbody { display: block; }
  #mgrItemsTable thead { display: none; }
  /* §20.10, волна B1: на телефоне таблица - карточки, и всё закрепление с
     фиксированной раскладкой снимается ЦЕЛИКОМ. Липкая панель тоже: на
     экране в 360px она съела бы половину высоты. JS сюда не лезет -
     applyLayout() (common.js) на этой ширине чистит инлайновые ширины сам. */
  #mgrItemsTable { table-layout: auto; }
  #mgrItemsTable thead th { position: static; }
  .table-sticky-panel { position: static; padding-top: 0; }
  #mgrItemsTable td { overflow: visible; text-overflow: clip; }
  #mgrItemsTable tr {
    display: grid; grid-template-columns: 64px 1fr; gap: 6px 10px;
    align-items: center; padding: 12px 2px;
    border-bottom: 1px solid var(--border);
  }
  #mgrItemsTable td { display: block; border: none; padding: 0; }
  #mgrItemsTable td.photo-cell { grid-row: span 2; }
  #mgrItemsTable td[data-label] {
    grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
  }
  #mgrItemsTable td[data-label]::before {
    content: attr(data-label); color: var(--muted); font-size: 12px;
    min-width: 96px; flex-shrink: 0;
  }
  #mgrItemsTable td:empty { display: none; }
  #mgrItemsTable td[colspan] { grid-column: 1 / -1; }
  table.no-logistics td.log { display: none !important; }
  /* Инпуты и селект статуса - на всю ширину строки карточки, !important
     перебивает инлайновые width/max-width из десктопного шаблона. */
  #mgrItemsTable input.m-field, #mgrItemsTable select.m-field {
    width: auto !important; max-width: none !important; flex: 1; min-width: 0;
  }
  /* Чекбокс выбора строки остаётся доступным (первая колонка, рядом с
     фото), только крупнее, чтобы палец не промахивался (Apple HIG >=44pt). */
  #mgrItemsTable input[type="checkbox"] { width: 22px; height: 22px; }
  /* Колонка действий (✎/⧉/🗑, §5) - последней строкой во всю ширину
     карточки и с пальцевыми кнопками, как у клиента. На десктопе она же
     закреплена (position:sticky, правило выше у #mgrItemsTable th/td:last-child) -
     здесь, внутри карточек, закрепление не нужно и снимается явно. */
  #mgrItemsTable td:last-child { grid-column: 1 / -1; position: static; }
  #mgrItemsTable .actions button { padding: 10px 16px; font-size: 16px; }
  /* BACKLOG.md §21.10, волна 1 - обрезка "Название"/"Комментария" снимается
     целиком: в карточках title пальцем недостижим, значит текст должен
     быть виден полностью, а не подсказкой, до которой не дотянуться. */
  #mgrItemsTable td.cell-clip {
    max-width: none;
    overflow: visible;
    white-space: normal;
    text-overflow: clip;
  }
  /* ТЗ 34: .bulk-bar больше НЕ sticky bottom — режим выбора стоит на месте
     панели фильтров внутри .list-panel (наезжал на фильтры, 2026-10-29). */
  .bulk-bar button { padding: 10px 16px; font-size: 15px; }
}

/* ===== Третий заход по интерфейсу, часть A (UX-TASK-3.md, 2026-08-09) ===== */

/* .tap-target даёт радио/чекбоксу (таблица статусов) реальную область
   касания (нативный чекбокс/радио - около 16-18px, пальцем не попасть) без
   переписывания их разметки — просто оборачивает в label с паддингом, клик
   по паддингу тоже переключает input (родная семантика label+input). */
.tap-target {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 13px; cursor: pointer;
}

/* ===== Четвёртый заход (UX-TASK-4.md, 2026-08-10) — единый приём мобильных
   карточек для "простых" таблиц =====
   Третий раунд ревью подряд находил очередную таблицу без адаптации
   (UX-TASK-4 п.2) - каждая была своим отдельным CSS-блоком на конкретные
   #id, и про новую таблицу было легко забыть. Теперь один класс
   .mobile-simple-table: в разметке таблице достаточно дать этот класс +
   id + data-label на td в JS, ничего не добавляя в CSS. Смысл тот же, что
   был у прежних блоков A1/A2/части B (tr становится блоком, td - строкой
   "подпись: значение"), просто в одном месте. Не подходит для таблиц с
   фото и сложной сеткой (#itemsTable, #mgrItemsTable - у них свой блок
   ниже, с сеткой 64px+1fr под фото).
   Таблицы на этом классе сейчас: #statusesTable, #clientsTable,
   #claimsTable, #historyTable, #claimsHistoryTable, #shipmentsTable,
   #insurancesTable, #contentsTable, #candidatesTable, #parcelsTable,
   #balanceHistoryTable, #trashTable (корзина удалённых, common.js). Проверку "класс не забыли" ловит guard-тест в
   smoke.browser.mjs (UX-TASK-4 п.2.3): для каждой таблицы на странице
   проверяется id и data-label в первой заполненной строке. */
@media (max-width: 720px) {
  .mobile-simple-table, .mobile-simple-table tbody { display: block; }
  .mobile-simple-table thead { display: none; }
  .mobile-simple-table tr { display: block; padding: 10px 2px; border-bottom: 1px solid var(--border); }
  .mobile-simple-table td { display: flex; align-items: center; gap: 8px; border: none; padding: 4px 0; }
  .mobile-simple-table td[data-label]::before {
    content: attr(data-label); color: var(--muted); font-size: 12px;
    min-width: 100px; flex-shrink: 0;
  }
  .mobile-simple-table td:empty { display: none; }
  .mobile-simple-table input:not([type="color"]):not([type="checkbox"]):not([type="radio"]) {
    width: auto !important; flex: 1; min-width: 0;
  }
  .mobile-simple-table button { padding: 10px 14px; font-size: 15px; }
  .mobile-simple-table input[type="checkbox"], .mobile-simple-table input[type="radio"] { width: 20px; height: 20px; }
}

/* П.1 — предупреждение менеджеру, что курс ¥/$ не задан (renderRateWarningBanner
   в common.js, вызывается из initManagerNav на всех страницах менеджера).
   Проблема настройки, а не рабочего процесса - клиент видит "?" в колонке
   "Итого ≈$", но исправить это может только менеджер, поэтому баннер живёт
   на его страницах, а не в подсказке клиенту (см. totalExplainTitle в
   client.html - то же сообщение с точки зрения клиента, там оно не даёт
   ссылки на настройки, т.к. клиент их не видит). */
.rate-warning {
  background: #fff3cd; border-bottom: 1px solid #ffe08a; color: #7a5b00;
  padding: 10px 20px; font-size: 13px; text-align: center;
}
.rate-warning a { color: #7a5b00; font-weight: 600; }

/* Плашка «локальная копия, не боевая» (DEPLOY-TASK.md §Р3.1, common.js
   renderLocalCopyBanner). Видна на КАЖДОЙ странице кабинета, любой роли -
   поэтому не sticky (в отличие от .acting-banner ниже): двум sticky-плашкам
   с top:0 пришлось бы делить одно и то же место при прокрутке, а здесь это
   не критично - предупреждение про окружение, не про то, чьи деньги
   считаются прямо сейчас. Цвет намеренно НЕ красный (.acting-banner) и НЕ
   жёлтый (.rate-warning) - это третий, отдельный смысл, с ними не спутать. */
.local-copy-banner {
  text-align: center;
  background: #37474f; color: #fff;
  padding: 8px 20px; font-size: 13px; font-weight: 600;
}

/* П.3 — кнопка отправки формы (form.add-item) уезжала в середину трёхколоночной
   сетки. Переносим её в конец разметки, рядом с зоной фото: та и так во всю
   ширину и высокая, места достаточно, а высота формы не растёт. */
.form-footer { display: flex; gap: 10px; align-items: stretch; }
.form-footer .dropzone { flex: 1; margin: 0; }
.form-footer button[type="submit"] { flex-shrink: 0; align-self: center; padding: 12px 22px; font-size: 15px; }
@media (max-width: 720px) {
  .form-footer { flex-direction: column; }
  .form-footer button[type="submit"] { width: 100%; padding: 14px; font-size: 16px; }
}

/* ===== Импорт заказов TaoBao (import.html, IMPORT-TASK.md §6) ===== */

/* Кандидаты на сопоставление строки заказа с товаром в системе - список
   чекбоксов внутри одной ячейки (несколько кандидатов = возможность
   разделить позицию между товарами, §6.3). Подсвеченный (suggested) -
   то, что предложила система (necessarily не то же самое, что "отмечено
   галочкой" - см. IMPORT-TASK.md §6.3, таблицу режимов). */
.cand-row {
  display: flex; align-items: flex-start; gap: 6px; padding: 3px 0;
  cursor: pointer; font-size: 12px;
}
.cand-row.suggested { background: #fff8e1; border-radius: 4px; padding-left: 4px; }
.cand-row input[type="checkbox"] { margin-top: 2px; flex-shrink: 0; }
#reconcileTable .new-client-select { margin-bottom: 4px; }
#reconcileTable button.secondary { font-size: 11px; padding: 4px 8px; margin-top: 2px; }
/* Получатели строки импорта (BACKLOG.md §20.2, переделка 2026-09-11):
   "клиент - количество - убрать" в одну строчку. Флекс живёт во ВЛОЖЕННОЙ
   обёртке, а не на <td> (CLAUDE.md: display:flex на ячейке вырывает её из
   табличной раскладки; держит сторожевой тест cssTdFlexGuard.test.js). */
.recipients { margin-top: 4px; }
.recip-row { display: flex; align-items: center; gap: 4px; margin-bottom: 3px; flex-wrap: wrap; }
.recip-remainder { margin-bottom: 3px; }
.recip-problem { margin-top: 3px; }
#reconcileTable td { vertical-align: top; }
#reconcileTable img.thumb { width: 36px; height: 36px; }
#reconcileTable .placeholder {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border: 1px dashed var(--border); border-radius: 6px; color: var(--muted);
}

/* ===== §5 — модалка полного редактирования товара (openItemEditor в
   common.js) =====
   Переиспользует .img-modal-overlay / .img-modal (тот же затемнённый фон,
   тот же крестик, тот же Escape), но панель шире и внутри - двухколоночная
   форма. Отдельный класс, а не правка .img-modal: превью фото должно
   оставаться узким, оно про картинку, а не про форму. */
.item-modal { max-width: min(640px, 94vw); overflow-y: auto; }
.item-modal-title { margin: 0 24px 0 0; font-size: 16px; }
.item-edit-form { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.item-edit-form .span-all { grid-column: 1 / -1; }
.item-edit-form label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.item-edit-form input, .item-edit-form select, .item-edit-form textarea {
  width: 100%; font-size: 14px; color: #1a1a1a;
}
.item-edit-form textarea { resize: vertical; }
/* Заблокированные поля должны читаться как "нельзя", а не как "сломалось":
   у клиента после выкупа так выглядит вся форма, кроме комментария. */
.item-edit-form :disabled { background: #f6f6f6; color: #777; cursor: not-allowed; }
.item-modal-note { margin: 0; }
.item-modal-photo { margin: 0; min-height: 70px; }
.item-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.item-modal-actions button { padding: 10px 20px; font-size: 14px; }

/* ===== Правки по итогам проверки §5 (2026-08-10) ===== */

/* Имя клиента в таблице менеджера - точка входа в его деньги. Кнопка, а не
   ссылка: переход собирается в JS (money.html?client=...), а <button>
   доступен с клавиатуры и не мозолит глаз подчёркиванием в каждой строке. */
button.linklike {
  background: none; border: none; padding: 0; margin: 0;
  color: #2563eb; font: inherit; text-align: left; cursor: pointer;
}
button.linklike:hover { text-decoration: underline; }

/* Колонка "Начислено, ¥" у клиента: фактическое списание важнее исходной
   цены, поэтому не приглушаем его серым, как соседние справочные поля. */
#itemsTable td.charged { white-space: nowrap; font-weight: 600; }

/* Таблица клиента: колонок стало 13, и "Название" занимало место, которое
   нужнее комментарию. Сужаем название и даём комментарию расти вверх -
   поле разворачивается при фокусе (textarea.field), но теперь и в покое
   показывает две строки вместо одной. */
/* Только на десктопе: на ≤720px таблица - карточки, и там поля тянутся во
   всю ширину правилом с тем же весом (#itemsTable input.field). Оно стоит
   ВЫШЕ по файлу, поэтому при равной специфичности выиграло бы наше, и
   карточки сломались бы. Ограничиваем медиазапросом, а не !important. */
@media (min-width: 721px) {
  #itemsTable input[data-field="title"] { width: 110px !important; }
}
#itemsTable textarea.field { height: 46px; }
#itemsTable textarea.field:focus { height: 120px; }
/* Посылка в таблице менеджера - ссылка не должна ломать строку пополам. */
#mgrItemsTable td a.link { white-space: nowrap; }
@media (max-width: 720px) {
  .item-edit-form { grid-template-columns: 1fr; }
  .item-modal-actions { flex-direction: column-reverse; }
  .item-modal-actions button { width: 100%; padding: 13px; font-size: 16px; }
}

/* Модалка с готовой ссылкой-приглашением (AUTH-TASK.md §3). Переиспользует
   .img-modal-overlay / .img-modal, как и .item-modal: тот же затемнённый фон,
   крестик и Escape. Своё здесь только одно - поле со ссылкой должно быть
   широким и целиком выделяемым: ссылку копируют, а не читают. */
.link-modal { max-width: min(560px, 94vw); }
.link-modal-url {
  width: 100%; box-sizing: border-box;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
}
.link-modal-note { margin: 0; }

/* Вопрос «Да / Нет» (askYesNo в common.js, BACKLOG.md §24.2): та же модалка
   .img-modal, текст - обычным абзацем с переносами строк. */
.yes-no-modal { max-width: min(520px, 94vw); }
.yes-no-message { line-height: 1.45; }

/* Ячейка «Вход» в списке клиентов (money.html). Колонкой, а не строкой:
   состояние, срок выданной ссылки и кнопка читаются сверху вниз и не
   разъезжаются, когда текста в одной из строк больше. */
.login-cell-inner { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
/* Индивидуальная ставка клиента (money.html, BACKLOG.md §20.7): поле и
   подпись «= общая (N %)» под ним; флекс во вложенной обёртке, не на <td>. */
.commission-cell-inner { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.commission-cell-inner .c-commission-note { font-size: 12px; white-space: nowrap; }
.login-cell-inner button { padding: 4px 10px; font-size: 12px; }
.login-waiting { line-height: 1.3; }

/* ===== Плашка «вы работаете как менеджер X» (AUTH-TASK.md §5б) =====

   Требование ТЗ дословно: «постоянная заметная плашка на КАЖДОЙ странице
   кабинета». Отсюда всё оформление:
   - sticky, а не обычный блок: шапка при прокрутке уезжает, а плашка обязана
     оставаться на глазах - иначе через полминуты работы про неё забудут;
   - цвет тревожный, а не «информационный» (у нас уже есть мягко-жёлтая
     .rate-warning про курс — их нельзя путать: та про настройку, эта про то,
     что все действия уходят другому человеку);
   - крестика нет: закрыть плашку = вернуться к себе, других способов нет. */
.acting-banner {
  position: sticky; top: 0; z-index: 200;
  display: flex; gap: 12px; align-items: center; justify-content: center;
  flex-wrap: wrap;
  background: #b3261e; color: #fff;
  padding: 10px 20px; font-size: 14px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .25);
}
.acting-banner b { font-weight: 700; }
.acting-banner button {
  background: #fff; color: #b3261e; border: none; border-radius: 6px;
  padding: 6px 14px; font-weight: 600; cursor: pointer; font-size: 14px;
}
.acting-banner button:hover { background: #ffe8e6; }
@media (max-width: 720px) {
  .acting-banner { padding: 8px 12px; font-size: 13px; }
  .acting-banner-text { text-align: center; }
}

/* Строка истории, сделанная владельцем из чужого кабинета. Тихая пометка
   рядом с комментарием, а не отдельная колонка: строк с ней единицы, а
   колонка была бы пустой у всех остальных. */
.by-owner {
  display: inline-block; margin-left: 6px;
  background: #fde7e6; color: #8c1d18; border-radius: 4px;
  padding: 1px 6px; font-size: 11px; font-weight: 600; white-space: nowrap;
}

/* ===== «Справка» по ролям (BACKLOG.md §28.1, вид - артефакт «Справка:
   черновик», одобрен Игорем). Широкий экран: оглавление слева, липкое;
   телефон: оглавление сверху, обычным блоком. Оболочка .help-main
   центрируется (1120px), текст заполняет колонку целиком - ~830px, около
   105 знаков в строке (было 74ch = 700px с пустотой справа; Игорь
   2026-10-17 - узко). Шире оболочку не делать: строка в 120+ знаков
   читается тяжело, глаз теряет начало следующей. ===== */
.help-main { max-width: 1120px; }
.help-layout { display: grid; grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 900px) {
  .help-layout { grid-template-columns: 230px minmax(0, 1fr); }
  /* BACKLOG.md §30 п.3: раньше top/max-height считались от верха окна,
     теперь - от низа закреплённой шапки (--sticky-top), иначе верх
     оглавления уезжает под шапку при прокрутке. */
  .help-toc-box {
    position: sticky;
    top: calc(var(--sticky-top, 0px) + 12px);
    align-self: start;
    max-height: calc(100vh - var(--sticky-top, 0px) - 24px);
    overflow: auto;
  }
  .help-toc-toggle { display: none; }
}
/* Телефон: оглавление свёрнуто в кнопку «Содержание», раскрывается по нажатию. */
@media (max-width: 899px) {
  .help-toc:not(.open) { display: none; }
  .help-toc.open { margin-top: 8px; }
  .help-toc-toggle::after { content: " ▾"; }
  .help-toc-toggle[aria-expanded="true"]::after { content: " ▴"; }
}
.help-toc { display: flex; flex-direction: column; gap: 1px; font-size: 14px; }
.help-toc a { color: #1a1a1a; text-decoration: none; padding: 3px 8px; border-radius: 6px; }
.help-toc a:hover { background: #2a7f2e14; }
.help-toc-group {
  margin: 12px 0 2px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: #2a7f2e; font-weight: 700;
}
.help-toc-group:first-child { margin-top: 0; }
.help-content { min-width: 0; font-size: 15px; line-height: 1.6; }
/* BACKLOG.md §30 п.2: «Настройки» делят с «Справкой» сетку и боковое
   оглавление (.help-layout/.help-toc-box/.help-toc выше), но НЕ ширину/
   кегль текста .help-content - тот подогнан под чтение прозы (~105 знаков,
   15px), а тут карточки с формами и таблицами, которым это только мешало
   бы. Из сетки нужна только min-width:0 (иначе grid-колонка не сжимается
   у длинных таблиц - тот же приём, что у .help-content). */
.settings-content { min-width: 0; }
/* Страницы настроек с боковым меню (settings.html, client-settings.html;
   замечание Игоря 2026-10-17): main не центрируется колонкой 1240px -
   иначе на широком экране меню висит посреди пустоты, далеко от прижатых
   к левому краю шапки и пунктов меню, а карточки сжаты. Меню - у левого
   края, как шапка; карточки занимают остальное (до 1400px).
   «Справку» сюда НЕ переводить: Игорь попробовал (выкладка №22) и вернул
   центрированную колонку - для чтения она смотрится симметричнее. */
main.side-main { max-width: none; }
@media (min-width: 900px) {
  main.side-main .help-layout { grid-template-columns: 210px minmax(0, 1fr); gap: 28px; }
  main.side-main .settings-content { max-width: 1400px; }
}
.help-section { border-top: 1px solid var(--border); padding-top: 14px; margin-top: 24px; scroll-margin-top: 12px; }
.help-section:first-child { border-top: none; margin-top: 0; padding-top: 0; }
.help-section h2 { font-size: 22px; margin: 2px 0 8px; }
.help-section h3 { font-size: 17px; margin: 18px 0 6px; scroll-margin-top: 12px; }
.help-section h4 { font-size: 15px; margin: 14px 0 4px; }
.help-eyebrow {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #2a7f2e;
  font-weight: 700; margin: 0;
}
.help-table { overflow-x: auto; max-width: 100%; margin: 8px 0; }
.help-table table { border-collapse: collapse; font-size: 14px; }
.help-table th, .help-table td {
  border-bottom: 1px solid var(--border); padding: 6px 12px 6px 0; text-align: left; vertical-align: top;
}
.help-table th { color: var(--muted); font-weight: 600; }
.help-section details { background: #2a7f2e0d; border-radius: 8px; padding: 6px 12px; margin: 6px 0 14px; }
.help-section summary { cursor: pointer; color: #2a7f2e; font-weight: 600; }
.help-section p[id^="term-"] { scroll-margin-top: 12px; }
.help-section p[id^="term-"]:target { background: #fff6cc; border-radius: 6px; }
a.help-term { color: inherit; text-decoration: underline dotted #2a7f2e; text-underline-offset: 3px; cursor: help; }
a.help-page, a.help-ref { color: #2a7f2e; }
.wn-entry { padding: 10px 0 4px; border-bottom: 1px dashed var(--border); }
.wn-entry:last-child { border-bottom: none; }
.wn-entry h3 { margin: 4px 0 2px; }
.wn-meta { margin: 0 0 6px; font-size: 13px; }
.wn-new, .nav-new {
  display: inline-block; vertical-align: 2px; margin-left: 6px;
  background: #2a7f2e; color: #fff; border-radius: 9px;
  padding: 0 7px; font-size: 10px; font-weight: 700; line-height: 16px; letter-spacing: .02em;
}
.help-fragment ol, .help-fragment ul { margin: 6px 0; padding-left: 20px; }

.ticket-dl { font-size: 12px; margin-left: 6px; }
.ticket-link-btn { font-size: 12px; margin-left: 6px; background: none; border: none; color: inherit; text-decoration: underline; cursor: pointer; padding: 0; font-family: inherit; }
.ticket-edited { font-style: italic; }
.ticket-edit-field { width: 100%; resize: vertical; min-height: 80px; }

/* Снимок экрана в справке (services/helpDocs.js, блок img): по ширине колонки
   чтения, клик открывает в полном размере. */
.help-fig { margin: 10px 0 16px; }
.help-fig img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: 6px; }
.help-fig figcaption { font-size: 13px; color: var(--muted); margin-top: 4px; }

/* Полоска «Система обновлена» (common.js, showAppUpdatedBanner): липкая
   сверху, пока страницу не обновят. */
/* BACKLOG.md §30 п.3 (решение Игоря): снизу, не сверху - sticky top:0
   делила бы место с .acting-banner (тоже top:0), да ещё и под новой
   закреплённой шапкой верх страницы и так стал тесным. fixed, а не
   sticky: это не часть потока документа, а всплывающее уведомление. */
.app-updated-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center; gap: 8px 14px;
  padding: 8px 16px; background: #2a7f2e; color: #fff; font-size: 14px;
}
.app-updated-banner button { background: #fff; color: #2a7f2e; font-weight: 600; }

/* Сворачиваемая карточка «Настроек» («Резервные копии», «Журнал действий»,
   решение Игоря 2026-10-16): заголовок + итог одной строкой, остальное - по
   нажатию. */
.card-fold > summary {
  list-style: none; cursor: pointer; display: flex; align-items: baseline;
  flex-wrap: wrap; gap: 4px 10px;
}
.card-fold > summary::-webkit-details-marker { display: none; }
.card-fold > summary::before { content: "▸"; color: var(--muted); width: 1em; }
.card-fold[open] > summary::before { content: "▾"; }
.card-fold > summary h3 { margin: 0; }
.card-fold[open] > summary { margin-bottom: 10px; }
.card-fold-sum { font-size: 13px; }

/* «Обращения» (BACKLOG.md §20.8.1, tickets.html). */
.nav-count {
  display: inline-block; vertical-align: 2px; margin-left: 6px; min-width: 16px;
  background: #b3261e; color: #fff; border-radius: 9px; text-align: center;
  padding: 0 5px; font-size: 10px; font-weight: 700; line-height: 16px;
}
.tickets-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.tickets-head .chips { margin-bottom: 0; }
.ticket-form { display: grid; gap: 10px; margin-top: 14px; }
.ticket-form label, .ticket-reply label { display: grid; gap: 4px; font-size: 13px; }
.ticket-form input, .ticket-form textarea, .ticket-reply textarea { width: 100%; }
.ticket-form textarea, .ticket-reply textarea { resize: vertical; min-height: 120px; }
.ticket-types { display: flex; flex-wrap: wrap; gap: 6px; }
.ticket-types .chip[aria-checked="true"] { border-color: #2a7f2e; background: #2a7f2e14; font-weight: 600; }
.ticket-count.over { color: #b3261e; font-weight: 600; }
.ticket-hint { font-size: 13px; background: #fff8e1; border: 1px solid #f1d98a; border-radius: 6px; padding: 8px 10px; }
.ticket-hint p { margin: 0; }
.ticket-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.ticket-thumbs:empty { display: none; }
.ticket-thumb { position: relative; }
.ticket-thumb img { display: block; height: 64px; max-width: 120px; object-fit: contain; border: 1px solid var(--border); border-radius: 6px; background: #fff; cursor: pointer; }
.ticket-thumb button { position: absolute; top: -6px; right: -6px; padding: 0 6px; line-height: 18px; border-radius: 9px; font-size: 12px; }
#ticketsTable tbody tr[data-id] { cursor: pointer; }
#ticketsTable tbody tr[data-id]:hover { background: #2a7f2e0a; }
#ticketsTable tr.unread td { font-weight: 600; }
.ticket-unread { color: #b3261e; margin-right: 4px; }
.ticket-subject { overflow-wrap: anywhere; }
.ticket-status { display: inline-block; padding: 1px 8px; border-radius: 9px; background: #eee; font-size: 12px; white-space: nowrap; }
.ticket-status.st-new { background: #e3f0ff; }
.ticket-status.st-done { background: #2a7f2e1f; }
.ticket-status.st-rejected { background: #f3e1e0; }
.ticket-view-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 10px 0; }
.ticket-view-head h2 { margin: 0; font-size: 18px; overflow-wrap: anywhere; }
.ticket-feed { display: grid; gap: 10px; margin: 12px 0; }
.ticket-msg { border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; background: #fff; min-width: 0; }
.ticket-msg.mine { background: #2a7f2e08; }
.ticket-msg-head { font-size: 13px; margin-bottom: 4px; }
/* Переносы строк - как ввёл человек (диагностика расширения вставляется
   текстом); длинная строка без пробелов переносится, а не растягивает экран. */
.ticket-text { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; font-size: 14px; }
.ticket-msg.system { border: none; background: none; padding: 0 2px; font-size: 13px; color: var(--muted); }
.ticket-error { color: #b3261e; margin: 0; }
.ticket-reply { display: grid; gap: 8px; border-top: 1px solid var(--border); padding-top: 12px; }

/* ===== Бургер-меню шапки на телефоне (BACKLOG.md §30 п.3, common.js
   initBurgerMenu/updateBurgerDot) ================================
   Порог 700px - свой, не 720px карточных таблиц выше (два разных вопроса,
   см. комментарий в common.js у initBurgerMenu). #mainNav - id, поэтому
   .mainnav{flex-basis:100%;...} выше не нужно трогать: правило по id всегда
   сильнее правила по классу независимо от порядка файла (CLAUDE.md про
   специфичность), конфликта с десктопным .mainnav нет и не будет. */
.nav-burger {
  display: none;
  background: none; border: 1px solid var(--border); border-radius: 6px;
  width: 36px; height: 36px; font-size: 18px; line-height: 1; cursor: pointer;
  position: relative;
  /* Цвет явно: общее правило button {color:#fff} (фон зелёный) иначе делает
     значок белым на белом - найдено на приёмке 2026-10-17. */
  color: #1a1a1a;
}
/* Точка "есть что показать" (навёрстывает те же индикаторы, что спрятаны
   внутри свёрнутого #mainNav - updateBurgerDot в common.js). */
.nav-burger.has-dot::after {
  content: ""; position: absolute; top: 2px; right: 2px;
  width: 8px; height: 8px; border-radius: 50%; background: #b3261e;
  border: 1px solid #fff;
}
.nav-whoami-row { display: none; }

@media (max-width: 700px) {
  header .header-top { flex-wrap: nowrap; }
  header .header-top h1 {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
  }
  /* "кто я / выйти" десктопного вида уходит: тот же смысл показан внутри
     раскрытого меню строкой .nav-whoami-row ниже. */
  header .header-top > .row { display: none; }
  .nav-burger { display: inline-flex; align-items: center; justify-content: center; }

  /* header уже position:sticky (своё правило выше) - этого достаточно, чтобы
     служить containing block для раскрытого меню, отдельный
     position:relative здесь не нужен (та же причина, по которой его нет у
     самого header - см. комментарий там). */
  #mainNav.mainnav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; flex-wrap: nowrap; gap: 0; margin-top: 0;
    background: #fff; border-bottom: 1px solid var(--border);
    box-shadow: 0 4px 10px rgba(0, 0, 0, .12);
    padding: 4px 0; z-index: 99;
  }
  #mainNav.mainnav.open { display: flex; }
  #mainNav.mainnav .nav-item { border-radius: 0; padding: 10px 20px; }
  /* Точка «есть заявки» в списке - рядом со словом, а не у правого края
     строки во всю ширину (приёмка 2026-10-17). */
  #mainNav.mainnav .nav-dot { position: static; display: inline-block; margin-left: 6px; vertical-align: 6px; }
  .nav-whoami-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding: 10px 20px; border-top: 1px solid var(--border);
    margin-top: 4px;
  }
}

/* «Добавить товар вручную» (BACKLOG.md §30 п.4, решение Игоря 2026-10-17):
   вся полоса <summary> - кликабельна (это и так summary), кружок слева от
   текста меняется с «+» на «−» чисто по CSS (details[open]), без
   обработчика в JS - тот же приём, что у .card-fold > summary::before
   (▸/▾) выше, но с псевдоэлементом на вложенном span, не на самом summary
   (кружок - отдельная визуальная форма, а не маркер списка). */
.add-item-summary {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  cursor: pointer; font-weight: 600; list-style: none;
}
.add-item-summary::-webkit-details-marker { display: none; }
.add-item-plus {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  background: #2a7f2e; color: #fff; font-size: 20px; line-height: 1;
}
.add-item-plus::before { content: "+"; }
details[open] > .add-item-summary .add-item-plus::before { content: "\2212"; }
.add-item-summary:focus-visible { outline: 2px solid #2a7f2e; outline-offset: 2px; border-radius: 8px; }

/* Конструктор формулы «Оплач.» (BACKLOG.md §32, часть B; с B.2 - в
   «Настройках» → «Формулы «Оплач.»», settings.html). Описания переменных -
   видимым текстом под фишкой (title на телефоне не работает). Сетка фишек сама
   переходит в одну колонку на узком экране. Блок прячется атрибутом hidden -
   правило [hidden] выше перебивает display. */
.formula-builder {
  display: grid; gap: 8px; margin-top: 12px; padding: 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
.formula-label { font-weight: 600; }
.formula-input-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.formula-input {
  flex: 1 1 320px; min-width: 0; font: inherit; font-family: ui-monospace, Consolas, monospace;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
}
.formula-save-error:empty, .formula-check:empty { display: none; }
.formula-save-error, .formula-error, .formula-row-error { color: #b3261e; }
.formula-row-error { font-size: 11px; margin: 2px 0; }
/* Настройка «Оплач.» указывает на удалённую формулу (import.html, §32 B.2). */
.paid-mode-note { color: #b3261e; margin-top: 8px; }
.formula-ok { color: #1d5e21; }
.formula-pointer {
  font-family: ui-monospace, Consolas, monospace; font-size: 12px; white-space: pre-wrap;
  overflow-wrap: anywhere; margin-top: 2px;
}
.formula-pointer mark { background: #f6c3bf; color: inherit; border-radius: 2px; }
.formula-section-title { font-size: 12px; color: var(--muted); margin-top: 4px; }
.formula-chips-intro { font-size: 13px; line-height: 1.4; }
/* Группы фишек («За 1 шт», «Количество», «На всю позицию») - колонками, на узком
   экране (~400px) одна колонка без горизонтальной прокрутки страницы. */
.formula-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); gap: 14px; }
.formula-group { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.formula-group-title { font-size: 12px; font-weight: 600; color: var(--muted); }
.formula-chip-wrap { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.formula-chip-line { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.formula-chip { font-family: ui-monospace, Consolas, monospace; max-width: 100%; overflow-wrap: anywhere; }
.formula-chip-short { font-size: 12px; color: var(--muted); line-height: 1.35; }
/* Полное описание - .help-pop из common.js (position: fixed, не обрезается контейнером). */
.formula-chip-full { overflow-wrap: anywhere; }
.formula-ops { display: flex; gap: 6px; flex-wrap: wrap; }
.formula-op { min-width: 40px; font-size: 16px; }
.formula-examples { display: flex; gap: 8px; flex-wrap: wrap; }
.formula-example-btn { display: inline-flex; flex-direction: column; align-items: flex-start; text-align: left; }
.formula-example-text { font-size: 11px; color: var(--muted); font-family: ui-monospace, Consolas, monospace; }
.formula-editor-title { font-weight: 600; font-size: 15px; }
.formula-name-input {
  max-width: 420px; font: inherit; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
}
.formula-hint { font-size: 12px; }
.formula-text { font-family: ui-monospace, Consolas, monospace; font-size: 12px; overflow-wrap: anywhere; }

/* Импорт: посылки заказа и трек позиции (BACKLOG.md §32, C2). */
.track-unmatched { color: #b3261e; }
.order-packages .package-line { margin-top: 2px; }
.order-packages .package-item { padding-left: 10px; overflow-wrap: anywhere; }

/* ===== Резервные ссылки на товар (BACKLOG.md §20.9) =====
   Значок 🔗 с числом стоит ПОД стрелкой основной ссылки и колонку не
   расширяет (в ячейке 40px места на одну иконку). Окошко - тот же
   .img-modal-overlay / .img-modal, что у остальных окон. */
.link-stack { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; }
.backup-links-btn {
  display: inline-flex; align-items: center; gap: 1px;
  padding: 0 4px; height: 20px; border-radius: 10px; border: 1px solid var(--border);
  background: #fff; color: #2563eb; font-size: 11px; line-height: 1; cursor: pointer;
}
.backup-links-btn:hover { background: #eef4ff; }
.backup-links-count { font-weight: 600; }
@media (max-width: 720px) {
  /* В карточке места хватает: стрелка и значок - в одну строку. */
  .link-stack { flex-direction: row; gap: 8px; }
}
.backup-links-block { display: flex; flex-direction: column; gap: 6px; }
.backup-links-title { font-size: 12px; color: var(--muted); }
.backup-links-note { font-size: 12px; margin: 0; }
.backup-links-modal { max-width: min(640px, 94vw); overflow-y: auto; }
.backup-links-for { font-size: 13px; word-break: break-word; }
.backup-links-list { list-style: decimal inside; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.backup-links-list li { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; min-width: 0; }
.backup-links-list input { width: 100%; min-width: 0; box-sizing: border-box; font-size: 13px; }
.backup-links-actions { display: flex; flex-wrap: wrap; gap: 8px; }
a.backup-open {
  display: inline-flex; align-items: center; padding: 6px 12px; border-radius: 6px;
  background: #2563eb; color: #fff; text-decoration: none; font-size: 14px;
}

/* Корзина удалённых товаров (BACKLOG.md §5, «ТЗ 5»): шапка над таблицей
   (пояснение + «Восстановить выбранные»), причина «восстановить нельзя» под
   кнопками. Таблица - .mobile-simple-table (#trashTable, renderItemTrash). */
.trash-head { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; justify-content: space-between; margin: 0 0 8px; }
.trash-block { font-size: 12px; margin-top: 4px; max-width: 260px; }

/* BACKLOG.md §36: «Не разобрано: N коробок, X кг — доставка делится без них»
   в итогах посылки — жёлтым, пока такие коробки есть. */
.unknown-warning { background: #fff3c4; color: #5c3f00; padding: 1px 6px; border-radius: 4px; }

/* ===== Панель списка A + B (BACKLOG.md §34, «ТЗ 34», волна 1) =============
   Стандарт — docs/rules/ui-standard.md (решение Игоря 2026-10-29); код —
   common.js (initFilterSheet, mountSegmented, createSelectMode,
   initAddItemButton, initMoreText, initMenuPops). Одна копия на все страницы.
   - .list-panel — ЕДИНСТВЕННАЯ липкая панель списка: чипы + поиск +
     «Фильтры (N)» + плашки; режим выбора встаёт на её место (.list-bar
     прячется), а не второй панелью. У липких элементов внутри — никакого
     bottom: так .bulk-bar наезжала на фильтры (жалоба Игоря 2026-10-29),
     держит tests/listPanelGuard.test.js.
   - Шире 720px шторка — те же элементы строкой (display: contents), как было;
     уже — та же разметка выезжает снизу. */
.list-panel {
  position: sticky;
  top: var(--sticky-top, 0px);
  z-index: 30;
  background: #fff;
}
/* Пока открыта шторка, панель (а в ней шторка и затемнение) — выше шапки
   (100), но ниже плашки «вы работаете как» (200). */
.list-panel.filter-sheet-open { z-index: 150; }
.header-action { margin-left: auto; white-space: nowrap; }
.chips-row { margin-bottom: 10px; }
.list-search { margin-bottom: 10px; }
.list-search input[type="search"] { flex: 1 1 210px; max-width: 320px; }
.filters-btn { display: none; }
.filter-pills { display: none; }
.filter-sheet, .filter-sheet-body { display: contents; }
.filter-sheet-head, .filter-sheet-foot, .filter-sheet-backdrop { display: none; }
.filter-field { display: inline-flex; align-items: center; gap: 6px; }
.filter-label { display: none; color: var(--muted); font-size: 13px; }
.filter-label.always { display: inline; }
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.seg .seg-btn {
  background: #fff; color: #222; border-radius: 0; padding: 6px 12px;
  border-left: 1px solid var(--border);
}
.seg .seg-btn:first-child { border-left: none; }
.seg .seg-btn[aria-pressed="true"] { background: #2a7f2e14; color: #1d5e20; font-weight: 600; }
/* Режим выбора: рамка (✕ · «Выбрано: N» · «Все N») ставится JS, кнопки
   страницы идут следом в той же строке. */
.select-frame { display: contents; }
.select-all { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.select-summary {
  font-size: 13px; color: var(--muted); margin: -6px 0 10px; padding: 0 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Меню «⋯» на <details>: список абсолютный, панель не растёт. */
details.menu-pop { position: relative; }
details.menu-pop > summary {
  list-style: none; cursor: pointer; user-select: none;
  background: #eee; color: #222; border-radius: 6px; padding: 6px 12px;
}
details.menu-pop > summary::-webkit-details-marker { display: none; }
.menu-pop-list {
  position: absolute; z-index: 40; top: calc(100% + 4px); right: 0;
  min-width: 240px; display: flex; flex-direction: column; gap: 6px;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
}
.menu-pop-list button { text-align: left; }
.menu-pop-list .danger-text { background: #fff; color: #b3261e; border: 1px solid #f1c8c4; }
.menu-pop-note { font-size: 12px; color: var(--muted); margin: -2px 2px 2px; }
/* Ссылка, которая выглядит кнопкой и называет действие («Взвесить» на
   «Посылках», ТЗ 34): ведёт на другую страницу, поэтому <a>, а не <button>. */
a.button-link {
  display: inline-flex; align-items: center; gap: 6px; min-height: 34px;
  padding: 6px 14px; border-radius: 6px; text-decoration: none;
  background: #2a7f2e; color: #fff; white-space: nowrap;
}
a.button-link.secondary { background: #eee; color: #222; }
a.button-link:hover { filter: brightness(0.95); }
/* Пояснение длиннее строки: на компьютере — целиком, кнопки нет. */
button.more-text-btn { display: none; }

@media (max-width: 720px) {
  /* Телефон: панель липкая и здесь (стандарт §1 — не больше двух строк).
     Прежний сброс .table-sticky-panel { position: static } остаётся для
     «Импорта» (волна 2); у панели списка специфичность выше. */
  .table-sticky-panel.list-panel { position: sticky; padding-top: 6px; }
  .list-panel { margin: 0 -10px; padding: 6px 10px 0; }
  .chips-row { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin-bottom: 8px; }
  .chips-row::-webkit-scrollbar { display: none; }
  .chips-row .chip { flex: none; min-height: 44px; font-size: 14px; white-space: nowrap; }
  .chips-row button.chip.no-track-chip { margin-bottom: 0; }
  .list-search { flex-wrap: nowrap; gap: 8px; margin-bottom: 8px; }
  .list-search input[type="search"] { flex: 1 1 auto; max-width: none; min-width: 0; width: auto !important; }
  .list-panel input[type="search"], .list-panel select, .filter-sheet input, .filter-sheet select {
    font-size: 16px; min-height: 44px;
  }
  .list-search .search-info { display: none; }
  .filters-btn { display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; white-space: nowrap; }
  .filter-pills { display: flex; gap: 6px; flex-wrap: nowrap; overflow-x: auto; margin-bottom: 8px; scrollbar-width: none; }
  .filter-pills[hidden] { display: none !important; }
  .filter-pill {
    flex: none; min-height: 44px; padding: 0 12px; border-radius: 22px; white-space: nowrap;
    background: #2a7f2e14; color: #1d5e20; border: 1px solid #2a7f2e55;
  }
  /* Шторка снизу: та же разметка фильтров, что строкой на компьютере. */
  .filter-sheet { display: none; }
  .filter-sheet.open {
    display: flex; flex-direction: column;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 2;
    max-height: 85vh; background: #fff; border-radius: 14px 14px 0 0;
    box-shadow: 0 -6px 24px rgba(0, 0, 0, .18);
  }
  .filter-sheet.open .filter-sheet-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 16px; border-bottom: 1px solid var(--border); font-size: 16px;
  }
  .filter-sheet-close { min-width: 44px; min-height: 44px; }
  .filter-sheet.open .filter-sheet-body {
    display: flex; flex-direction: column; align-items: stretch; gap: 14px;
    overflow-y: auto; padding: 14px 16px; margin: 0;
  }
  .filter-sheet.open .filter-sheet-foot {
    display: flex; gap: 10px; padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border);
  }
  .filter-sheet-foot button { min-height: 44px; padding: 0 18px; }
  .filter-sheet-foot .filter-sheet-show { flex: 1; }
  .filter-sheet-backdrop { display: block; position: fixed; inset: 0; z-index: 1; background: rgba(0, 0, 0, .35); }
  .filter-sheet-backdrop[hidden] { display: none !important; }
  body.filter-sheet-lock { overflow: hidden; }
  .filter-field { display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
  .filter-label { display: block; }
  .filter-sheet-body select, .filter-sheet-body input[type="search"] { width: 100%; }
  .filter-sheet-body label.row { min-height: 44px; font-size: 15px; }
  .filter-sheet-body input[type="checkbox"] { width: 22px; height: 22px; }
  .seg { display: flex; }
  .seg .seg-btn { flex: 1; min-height: 44px; font-size: 15px; }
  .filter-sheet-body .col-menu > summary { min-height: 44px; display: flex; align-items: center; }
  .filter-sheet-body .col-menu-list { position: static; margin-top: 6px; box-shadow: none; max-height: none; }
  /* Режим выбора на телефоне — две строки: ✕ · «Выбрано: N» · «Все N»,
     под ней «Статус» · «В посылку» · «⋯». */
  .bulk-bar { gap: 8px; padding: 6px 8px; margin-bottom: 8px; }
  .bulk-bar .select-frame { display: flex; align-items: center; gap: 10px; flex-basis: 100%; }
  .bulk-bar .select-all { margin-left: auto; }
  .bulk-bar > select { flex: 1 1 0; min-width: 0; }
  .select-summary { margin: 0 0 8px; }
  /* Режим выбора на телефоне: пальцевые цели 44px. */
  .bulk-bar button, .bulk-bar select, details.menu-pop > summary {
    min-height: 44px; font-size: 15px;
  }
  .select-x { min-width: 44px; }
  .select-all { min-height: 44px; }
  .select-all input { width: 22px; height: 22px; }
  details.menu-pop > summary { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; }
  .menu-pop-list button { min-height: 44px; }
  .header-action { min-height: 40px; padding: 0 12px; flex: none; }
  a.button-link { min-height: 44px; }
  /* Пояснение — одна строка + «Подробнее» (стандарт §4). */
  .more-text { display: flex; align-items: baseline; gap: 6px; }
  .more-text:not(.expanded) .more-text-body { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  .more-text.expanded { flex-direction: column; }
  button.more-text-btn { display: inline; flex: none; padding: 10px 4px; }
}
