/* myozon v2 — компактный full-width интерфейс
   Правила: никаких max-width контейнеров, маленькие отступы (4-8px),
   плотные таблицы, flex под всю ширину. */

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font: 13px/1.35 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  color: #1d2330;
  background: #f5f6f8;
}

/* ---------- topbar ---------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 6px 12px;
  background: #1d2330;
  color: #fff;
  position: sticky; top: 0; z-index: 100;
}
/* P368: название — настройка `service_name`, может быть длиннее «myozon»: в одну строку,
 * ужимается группа ссылок (.nav-links скроллится), а не шапка по высоте. */
.topbar .brand { font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; flex: 0 0 auto; }
.topbar .nav { display: flex; gap: 4px; flex: 1 1 auto; min-width: 0; }
/* P228: ссылки страниц вынесены в свою группу, которая ужимается и при
 * нехватке места скроллится ВНУТРИ себя. Без этого длинное меню (суперадмин
 * видит больше пунктов, чем тенант) растягивало страницу в горизонтальный
 * скролл и раздувало высоту шапки. Правый блок (кабинет / юзер / выход) —
 * сосед этой группы, поэтому остаётся на месте и всегда виден. */
.topbar .nav-links {
  display: flex; gap: 4px;
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; scrollbar-width: thin;
}
.topbar .nav a {
  color: #cfd5e0; text-decoration: none;
  padding: 4px 10px; border-radius: 4px;
  white-space: nowrap;
}
.topbar .nav a:hover { background: #2a3140; color: #fff; }
/* P343-18 (D27): бейдж у пункта «Поставки» — число планов кабинета,
   оформленных, но не утверждённых; пустой/0 → скрыт (атрибут hidden). */
.topbar .nav a .nav-badge {
  display: inline-block; margin-left: 5px; padding: 0 6px; min-width: 8px;
  border-radius: 9px; background: #d4a800; color: #1d2330;
  font-size: 11px; font-weight: 700; line-height: 16px; text-align: center; vertical-align: 1px;
}
.topbar .nav a .nav-badge[hidden] { display: none; }

/* ---------- main ---------- */
.main { padding: 8px 12px; }

/* ---------- bars (фильтры/действия) ---------- */
.bar {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 8px 0;
  flex-wrap: wrap;
}
/* Sticky filter bar — залипает под topbar при scroll'е страницы. Применяется
 * к form.bar который сразу под topbar (форма периода/группировки/обновить).
 * P236: точку прилипания считает JS (applyBarStickyOffset в v2_tabletools.js)
 * от ФАКТИЧЕСКОЙ высоты topbar'а. Константа 36px давала щель: реальная высота
 * дробная (37.55px при DPR 0.9375), и тулбар под баром липнул на 2.25px ниже
 * его настоящего низа. 36px остаётся лишь fallback'ом до первого запуска JS.
 * Фон/паддинг компенсируют что bar теряет inline flow при sticky. */
form.bar.bar-sticky {
  position: sticky;
  top: var(--bar-sticky-top, 36px);
  z-index: 99;
  background: #fff;
  padding: 6px 12px;
  margin: 0 -12px 8px;
  border-bottom: 1px solid #e3e7ec;
  box-shadow: 0 1px 0 rgba(0,0,0,0.03);
}
.bar label { display: inline-flex; align-items: center; gap: 4px; }
.bar input, .bar select, .bar button {
  font: inherit;
  padding: 3px 8px;
  border: 1px solid #c7ccd6;
  border-radius: 3px;
  background: #fff;
  color: inherit;
}
.bar input[type="search"], .bar input[type="text"] { min-width: 240px; }
.bar button {
  cursor: pointer;
  background: #2d6cdf;
  color: #fff;
  border-color: #2d6cdf;
  padding: 3px 12px;
}
.bar button:hover { background: #2257b8; }
.bar button.btn-2 { background: #fff; color: #2d6cdf; }
.bar button.btn-2:hover { background: #eef3fc; }
.bar .grow { flex: 1; }
.bar .status { color: #5b6275; font-size: 12px; }

/* Date presets — компактные кнопки быстрого выбора периода. Tooltip на
 * каждой; диапазоны включающие сегодня предупреждают про неполные фин.данные. */
.date-presets { display: inline-flex; gap: 2px; align-items: center; margin-left: 2px; }
.date-preset {
  font: inherit; font-size: 11px;
  padding: 2px 6px; height: 22px;
  border: 1px solid #c7ccd6; border-radius: 3px;
  background: #fff; color: #2d6cdf;
  cursor: pointer; white-space: nowrap;
  line-height: 1;
}
.date-preset:hover  { background: #eef3fc; border-color: #2d6cdf; }
.date-preset:active { background: #d9e6fa; }

/* ---------- tabs ---------- */
.tabs { display: flex; gap: 0; border-bottom: 1px solid #c7ccd6; margin: 0 0 8px 0; }
.tab {
  padding: 5px 14px; cursor: pointer;
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  color: #5b6275; font: inherit;
}
.tab.active { color: #1d2330; border-bottom-color: #2d6cdf; }
.tab-pane { display: none; }
.tab-pane.active { display: block; }

/* ---------- cards / sections ---------- */
.grid-2 {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  margin: 0 0 12px 0;
}
.card {
  display: block;
  padding: 10px 12px;
  background: #fff;
  border: 1px solid #d9dde6;
  border-radius: 4px;
  text-decoration: none; color: inherit;
}
.card:hover { border-color: #2d6cdf; box-shadow: 0 0 0 1px #2d6cdf inset; }
.card-h { font-weight: 600; margin-bottom: 4px; }
.card-b { color: #5b6275; font-size: 12px; }

.section { margin-top: 12px; }
.section-h { font-weight: 600; padding: 4px 0; }

/* P144: «table card» — обёртка вокруг toolbar (Колонки/Подсветка/Экспорт)
 * + table-scroll-wrap. Визуально группирует bar и таблицу одной карточкой,
 * чтобы юзер не воспринимал toolbar как «парящий вверху». NO sticky:
 * карточка scroll'ится обычным потоком, sticky thead зашит в table-scroll-wrap. */
.table-card {
  background: #fff;
  border: 1px solid #d9dde6;
  border-radius: 3px;
  /* P146: убираем overflow:hidden — sticky child cannot escape overflow:hidden parent. */
}
.table-card .table-card-toolbar {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
  padding: 6px 10px;
  background: #f7f9fc;
  border-bottom: 1px solid #e3e7ec;
  /* P146: toolbar sticky под bar-sticky (topbar+bar-sticky height = ~109px).
   * Точное значение per-page выставляется JS-ом через CSS var --toolbar-sticky-top.
   * Дефолт 109 покрывает РНП/Аналитика/Склады (с bar-sticky).
   * P341/P343-33: z-index ВЫШЕ ячеек шапки (sticky th до z:6 — углы
   * замороженных колонок Аналитики). Шапка таблицы в своей области прокрутки
   * встаёт под тулбаром с нахлёстом в пиксель (модель прокрутки — раздел
   * «Закреплённые слои и шапка таблицы» в v2_tabletools.js): нахлёст
   * обязан уйти ПОД тулбар. Ниже bar-sticky (z:99) и topbar (z:100). */
  position: sticky;
  top: var(--toolbar-sticky-top, 109px);
  z-index: 7;
}
/* P244: базовый горизонтальный скроллер для широких таблиц. Раньше он жил
 * только в <style> Аналитики, из-за чего Склады растягивали документ и вся
 * страница ездила вбок — закреплённые слои уезжали вместе с ней, открывая
 * полосу, сквозь которую строки видны НАД шапкой. `sticky` их спасти не мог:
 * смещаться за пределы своего контейнера он не умеет, а body шириной с экран.
 * Теперь вбок ездит только таблица.
 * overflow-y тоже становится auto (браузер не даёт держать одну ось visible
 * при не-visible второй) → таблица прокручивается в своей области по обеим осям.
 * P343-33 (D49): модель прокрутки такой области — в v2_tabletools.js, раздел
 * «Закреплённые слои и шапка таблицы»: высота — окно под закреплёнными слоями
 * (--tt-area-max-h пишет TableTools; calc — только до его запуска), шапка
 * липнет внутри области и стоит под слоями, когда страница прокручена дальше. */
.table-scroll-wrap {
  overflow-x: auto;
  overflow-y: auto;
  max-height: var(--tt-area-max-h, calc(100vh - 210px));
  min-height: 220px;
}
/* Колесо над областью с вертикальной прокруткой страницу не уводит. Признак
 * пишет TableTools (markAreaScrollY): Chrome держит колесо и над областью с
 * contain БЕЗ прокрутки — страница под короткой таблицей не прокручивалась бы. */
.table-scroll-wrap[data-tt-scroll-y] { overscroll-behavior: contain; }
.table-scroll-wrap::-webkit-scrollbar { height: 14px; width: 12px; background: #eef0f4; }
.table-scroll-wrap::-webkit-scrollbar-thumb { background: #a3acba; border-radius: 4px; border: 2px solid #eef0f4; }
.table-scroll-wrap::-webkit-scrollbar-thumb:hover { background: #2d6cdf; }

.table-card .table-scroll-wrap {
  /* override .table-scroll-wrap inside card — убираем top-border т.к.
   * toolbar уже дал bottom-border сверху. */
  border-top: 0;
}

/* ---------- таблицы (плотные) ---------- */
table.dense {
  width: 100%;
  border-collapse: collapse;
  background: #fff;
  border: 1px solid #d9dde6;
}
table.dense th, table.dense td {
  padding: 3px 6px;
  border-bottom: 1px solid #ecedf1;
  text-align: left;
  vertical-align: top;
}
table.dense th {
  background: #eef0f4;
  font-weight: 600;
  font-size: 12px;
  color: #3a4154;
  /* P131: sticky thead — шапка остаётся видимой при scroll'е. Offset берётся
   * из CSS-переменной --sticky-top у <thead> (TableTools.applyStickyOffset:
   * в потоке страницы — линия под закреплёнными слоями, в своей области
   * прокрутки — точка внутри области, P343-33). Дефолт 36px — только до
   * запуска TableTools. z-index 5: выше tbody, ниже тулбара карточки (z:7),
   * topbar (z:100) и bar-sticky (z:99). */
  position: sticky; top: var(--sticky-top, 36px); z-index: 5;
  /* P143: box-shadow inset снизу + сверху перекрывает 1-pixel sub-pixel
   * щели между sticky <th> при scroll'е (когда data row проходит под thead).
   * Раньше через эти щели просвечивал контент следующей/предыдущей строки.
   * box-shadow рендерится как часть th, не выходит за table bounds. */
  box-shadow: inset 0 1px 0 #eef0f4, inset 0 -1px 0 #c7ccd6;
}
/* P143: solid background на сам <thead> — заполняет border-spacing зазоры
 * между <th> ячейками (computed border-spacing ~1.45px даже при
 * border-collapse:collapse в некоторых браузерах). Без этого через зазоры
 * было видно scroll'ящийся data row под thead.
 *
 * P144: дополнительно — solid background на ВСЕ tr внутри thead. Sticky
 * элементы в Chrome рендерятся через отдельные layers, и при body scroll
 * sub-pixel rendering между sticky-th и тем что под thead может оставлять
 * щель в 1px на retina/HiDPI. Background на tr перекрывает её. */
table.dense thead,
table.dense thead tr {
  background: #eef0f4;
}

/* P236d: шов на КРОМКЕ скролл-контейнера.
 *
 * Там, где таблица скроллится внутри своего контейнера (Аналитика —
 * .table-scroll-wrap, пользовательские справочники — .ucrud-scroll / .tcrud-scroll),
 * закреплённая шапка липнет на top:0, то есть ровно на верхнюю кромку контейнера.
 * Нахлёста нет вовсе, и добавить его НЕКУДА: слоя выше шапки внутри контейнера
 * не существует, сдвигать её не от чего. А сама кромка почти всегда стоит на
 * дробной позиции устройственной сетки (замер на Аналитике при DPR 0.9375:
 * верх 489.344, лево 12.25). Клип уезжающего контента и заливка закреплённой
 * шапки округляются до целых устройственных пикселей независимо, расходятся —
 * и на кромке остаётся полоска в один пиксель, видимая как пунктир над шапкой.
 *
 * Поэтому здесь лечим не offset'ом, а ЗАЛИВКОЙ: полоска-псевдоэлемент выступает
 * на 2px за кромку ячейки и закрашивает шов. `background: inherit` берёт фон
 * САМОЙ ячейки — hover, выделение строки и подсветка heatmap совпадают
 * автоматически, без дублирования цветов по правилам. Наружу выступ не вылезает:
 * его обрезает тот же overflow контейнера. Когда страница прокручена дальше
 * верха области (P343-33), шапка стоит под тулбаром карточки с нахлёстом —
 * выступ лежит под тулбаром (z:7) и не виден. */
.table-scroll-wrap thead th::before,
.ucrud-scroll thead th::before,
.tcrud-scroll thead th::before {
  content: ''; position: absolute;
  left: 0; right: 0; top: -2px; height: 2px;
  background: inherit;
}
/* P343-33 (D49): шапка таблицы в своей области прокрутки сдвигается под
 * закреплённые слои страницы, когда страница прокручена дальше верха области.
 * Сдвиг — визуальный (переменную пишет TableTools.setHeaderShift у <thead>):
 * смена top у липких ячеек перекладывала всю таблицу на каждом кадре
 * прокрутки страницы. */
.table-scroll-wrap thead th { transform: translateY(var(--tt-header-shift, 0px)); }
table.dense tr:hover td { background: #f7f9fc; }
table.dense td.num, table.dense th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* P327: ячейка категориальной метрики (ABC) — одна буква. Выравнивать её
   по правому краю не за чем (равнять не с чем), поэтому по центру. Идёт
   ПОСЛЕ .num той же специфичности: класс на ячейке стоит парой `num cat`,
   чтобы столбец сохранил остальные свойства числовой колонки. */
table.dense td.cat { text-align: center; }
/* P267/P268: текстовый столбец (артикул, наименование) на всю свою ширину +
   кнопка копирования у правой границы. Сам td остаётся table-cell (flex на td
   ломает раскладку таблицы) — гибким делаем внутренний wrapper.
   Порядок: текст → многоточие → кнопка → граница столбца.

   max-width у наименования — исторический (до P268 он и так стоял); менять
   его не на что: браузер не обязан учитывать max-width на ячейке таблицы
   (замер: computed 360px при фактических 1279px), ширину столбца задаёт
   раскладка таблицы и ручной ресайз (tabletools пишет width/min/max в th).
   Текст сжимается внутри wrapper'а по фактической ширине, какой бы она ни
   получилась, — поэтому обрезка честная при любом столбце. */
table.dense td.col-articul { overflow: hidden; }
table.dense td.name        { max-width: 360px; overflow: hidden; }
.cellcopy-wrap { display: flex; align-items: center; gap: 4px; min-width: 0; }
.cellcopy-text {
  flex: 1 1 auto; min-width: 0;              /* min-width:0 — иначе flex не даст сжаться */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cellcopy-btn {
  flex: 0 0 auto;                            /* кнопка не сжимается и не уезжает */
  border: 1px solid #d1d5db; background: #fff; border-radius: 3px;
  color: #6b7280; cursor: pointer; line-height: 1;
  font-size: 11px; padding: 1px 4px;
  opacity: 0; transition: opacity .12s;      /* не шумит, пока строку не навели */
}
tr:hover .cellcopy-btn, .cellcopy-btn:focus-visible { opacity: 1; }
.cellcopy-btn:hover { background: #f3f4f6; color: #111827; }
.cellcopy-btn.copied { color: #16a34a; border-color: #16a34a; opacity: 1; }
table.dense td.err { color: #b54040; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- series-picker (чекбоксы серий графика) ---------- */
.series-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 2px;
  padding: 4px 0 6px 0;
  font-size: 11px;
}
.series-cb {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px 1px 4px;
  border: 1px solid #d9dde6;
  border-radius: 11px;
  cursor: pointer;
  user-select: none;
  background: #fff;
  line-height: 18px;
  white-space: nowrap;
}
.series-cb input { display: none; }
.series-cb .sw {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--c);
  opacity: 0.35;
}
.series-cb:has(input:checked) { background: #f3f7ff; border-color: var(--c); }
.series-cb:has(input:checked) .sw { opacity: 1; }
.series-cb:hover { border-color: #888; }

/* статусы строк (для sync_log) */
tr.row-ok td      { }
tr.row-fail td    { background: #fbebeb; }
tr.row-running td { background: #fff8e0; }
tr.row-partial td { background: #fff4d6; }
/* ABORTED — «прогон оборван, об источнике не сказано ничего»
   (migrations/023_sync_log_aborted_status.sql). Ни успех, ни авария
   источника, поэтому ни белый фон OK, ни красный FAIL: нейтральный серый —
   «прогон был, сказать о нём нечего». По той же причине текст в колонке
   ошибки не красный: там лежит пояснение reaper'а, а не ответ Ozon. */
tr.row-aborted td { background: #e6e9ee; }
table.dense tr.row-aborted td.err { color: #55606f; }

/* P306: подпись под заголовком сводки оборванных прогонов — говорит, что
   серый значит «об источнике не сказано ничего». Отбивка как у toolbar'а
   карточки, чтобы подпись читалась его продолжением. */
.aborted-note {
  color: #5b6275;
  font-size: 12px;
  padding: 6px 10px;
}

/* ---------- P138: settlement lag notice (Analytics + РНП bar-sticky) ---------- */
.settlement-notice {
  color: #ea580c;
  font-size: 11px;
  padding: 2px 8px;
  background: #fff7ed;
  border: 1px solid #fed7aa;
  border-radius: 4px;
  margin-left: 8px;
}

/* P173: settlement-предупреждение на инпуте «по» вместо текстового лейбла.
   ⚠-иконка (hover → hint) + подсветка date-инпута: красный=сегодня, оранжевый=вчера. */
.settlement-warn {
  color: #d32f2f;
  cursor: help;
  font-size: 14px;
  margin: 0 2px 0 4px;
  vertical-align: middle;
}
input[type="date"].dp-warn-today {
  background: #ffe2e2;
  border: 1px solid #d32f2f;
  border-radius: 4px;
}
input[type="date"].dp-warn-yesterday {
  background: #fff0d6;
  border: 1px solid #e08600;
  border-radius: 4px;
}

/* ---------- инпуты внутри таблицы ---------- */
table.dense select, table.dense input {
  font: inherit;
  padding: 1px 4px;
  border: 1px solid #c7ccd6;
  border-radius: 2px;
  background: #fff;
  width: 100%;
}
table.dense input.cost-inp { width: 90px; }
table.dense button.set-cost {
  margin-left: 4px;
  padding: 1px 8px;
  background: #2d6cdf; color: #fff; border: 1px solid #2d6cdf; border-radius: 2px;
  cursor: pointer; font: inherit;
}
table.dense button.del-type {
  width: 22px; height: 22px;
  padding: 0; line-height: 18px;
  background: #fff; color: #b54040;
  border: 1px solid #d9b5b5; border-radius: 50%;
  cursor: pointer; font: 14px/1 inherit; font-weight: 700;
}
table.dense button.del-type:hover { background: #b54040; color: #fff; border-color: #b54040; }
table.dense button.del-type:disabled { opacity: 0.5; cursor: wait; }

/* ---------- сортируемые заголовки + двух-уровневая шапка ---------- */
/* P141: position должен оставаться sticky (от table.dense th), НЕ relative.
 * Sticky тоже positioned элемент (CSS Position Spec) — создаёт containing
 * block для absolute children (.sort-arrow). Раньше position:relative
 * переопределял sticky по specificity (0,2,2 vs 0,1,1) → шапка НЕ
 * закреплялась при scroll на ВСЕХ sortable-таблицах. P131/P136 не
 * помогали — там фиксили --sticky-top который applied к УЖЕ сломанной
 * sticky. Корневой fix — удаление одной property. */
.sortable-table thead th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.sortable-table thead th.sortable:hover { background: #e5e9f0; }

/* P324: колонка, которой пользователь задал ширину вручную (класс вешает
 * TableTools вместе с самой шириной — см. setCellWidth в v2_tabletools.js).
 *
 * Ширина теперь стоит на ВСЕХ ячейках столбца, поэтому колонка сужается до
 * заданной — но содержимое от этого короче не становится и без обрезки
 * вылезает поверх соседних колонок (заголовок «доля рекл. выручки %» в 45px
 * ложится на соседний заголовок). Обрезаем — с многоточием; полный текст у
 * заголовков остаётся в `title` (его проставляет реестр метрик).
 *
 * `clip` + `overflow-clip-margin: 2px`, а НЕ `hidden`: закреплённые ячейки
 * подрисовывают шов на 2px за своей рамкой (P236d — `thead th::before` сверху,
 * `td.sel-cell::after` слева). `hidden` срезал бы их и вернул пунктирную щель
 * на кромке скролл-контейнера; 2px запаса оставляют шов на месте, а вылет
 * содержимого на десятки пикселей всё равно обрезают.
 *
 * nowrap на ячейках тела — чтобы узкая колонка обрезалась в одну строку, а не
 * растила высоту строки переносом (разряды числа разделены NBSP, но браузер
 * рвёт по нему как по обычному пробелу, когда места нет). */
table.dense th.tt-col-sized,
table.dense td.tt-col-sized {
  overflow: clip;
  overflow-clip-margin: 2px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* P324, второй ограничитель: `table.dense { width: 100% }`. При автоматической
 * раскладке остаток ширины контейнера раздаётся колонкам СВЕРХ заданной им
 * ширины, и заданная ширина проигрывает не «на пару пикселей», а полностью
 * (замер: при 5 видимых колонках перетаскивание не сдвигало границу вообще).
 * Поэтому таблица, в которой пользователь закрепил ширину хотя бы одной
 * колонки, больше не тянется под контейнер — класс вешает TableTools
 * (markSizedTable). До первого ресайза всё как было: таблица во всю ширину. */
table.dense.tt-has-sized-cols { width: auto; }

/* Sort-arrow универсальный — поддерживает 2 режима рендера:
 *   1) Legacy text-mode: arrow.textContent = ' ▲' | ' ▼' | '' (CatalogView.updateSortArrows).
 *      Активная стрелка — синий жирный 10px.
 *   2) Tri-state stacked-mode: arrow.innerHTML = '<span class="arr-up">▲</span><span class="arr-dn">▼</span>'
 *      + arrow.dataset.state = 'asc' | 'desc' | 'none'. Используется TableTools.renderArrows
 *      (общий для warehouses/catalog/analytics после перехода на tri-state UI). Когда
 *      не отсортировано — два блёклых ▲▼ стопкой; при активной сортировке — один жирный
 *      ▲ или ▼ (см. data-state селекторы ниже).
 * Оба режима совместимы: text-mode оставляет innerHTML пустым (только textContent),
 * stacked-mode игнорирует textContent (рендерит inner spans). */
.sortable-table thead th .sort-arrow {
  color: #2d6cdf; font-weight: 700; margin-left: 2px; font-size: 10px;
}
.sortable-table thead th .sort-arrow[data-state] {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  display: inline-flex; flex-direction: column; align-items: center;
  line-height: 0.7; font-size: 8px; color: #b0b5be; font-weight: 400;
  margin-left: 0;
  user-select: none; pointer-events: none;
}
.sortable-table thead th .sort-arrow[data-state] .arr-up,
.sortable-table thead th .sort-arrow[data-state] .arr-dn { opacity: 0.55; }
.sortable-table thead th .sort-arrow[data-state="asc"]  .arr-up,
.sortable-table thead th .sort-arrow[data-state="desc"] .arr-dn {
  opacity: 1; color: #000; font-size: 13px; font-weight: 900; line-height: 1;
}
.sortable-table thead th .sort-arrow[data-state="asc"]  .arr-dn,
.sortable-table thead th .sort-arrow[data-state="desc"] .arr-up { display: none; }
/* Padding на th под absolute-arrow (tri-state режим). Применяется всегда,
 * т.к. legacy text-mode (margin-left на простом ▲) не требует доп. отступа,
 * но и не мешает. Без :has() — поддержка во всех браузерах. */
.sortable-table thead tr th.sortable { padding-right: 18px; }
/* (?) индикатор для th с title — визуальный hint что есть tooltip с
   описанием метрики. Hover на th показывает native title-tooltip. */
.sortable-table thead tr.sort-row th[title]:not(.sel-col)::after {
  content: '(?)';
  font-size: 9px;
  color: #6b7280;
  font-weight: 400;
  margin-left: 3px;
  cursor: help;
  vertical-align: super;
}

/* Шапка строки-фильтров под заголовками — отдельный фон, без жирных линий.
 * P131: top = --sticky-filter-top у <thead> — под строками шапки над ней
 * (TableTools.applyStickyOffset, пересчёт по ResizeObserver строк шапки,
 * P343-33). Дефолт 56px (36px topbar + 20px sort-row) — до запуска JS. z-index 4: выше tbody, ниже sort-row (5)
 * чтобы при scroll'е sort-row остался видим сверху над filter-row. */
.sortable-table thead tr.filter-row th {
  background: #fafbfd; border-top: 0; padding: 2px 4px;
  position: sticky; top: var(--sticky-filter-top, 56px); z-index: 4;
}

/* Контролы внутри строки-фильтров: [оператор][значение][×].
 * P229: подняты из <style> Аналитики в общий v2.css — теперь их рисуют две
 * страницы (Аналитика своим populateFilterRow, Склады — через
 * TableTools.initColumnFilters), и держать константы размеров в шаблоне
 * одной из них значило бы разъезжающийся вид одинаковых контролов.
 * Специфичный для Аналитики chart-toggle (.cf-chart) остался у неё. */
.col-filter { display: flex; gap: 2px; align-items: center; justify-content: flex-start; }
/* P239: переключатель условия — кнопка (было <select> с двумя пунктами).
 * Клик меняет > на < и обратно; открывать список ради двух вариантов незачем. */
.col-filter button.cf-op {
  flex: 0 0 auto;
  width: 20px; height: 18px; padding: 0;
  font-size: 12px; line-height: 1; font-weight: 600;
  border: 1px solid #c7ccd6; background: #fff; color: #3a4154;
  cursor: pointer;
}
.col-filter button.cf-op:hover { background: #eef3fc; border-color: #2d6cdf; color: #2d6cdf; }
.col-filter button.cf-op:active { background: #d9e6fa; }
/* P239: поле значения тянется на всю свободную ширину ячейки, поэтому [×]
 * всегда прижат к правому краю — вплотную к кнопке графика, без просвета
 * между ними. Минимальная ширина задана явно: без неё flex-элемент не даёт
 * сжать себя ниже собственного контента и узкие колонки распирает. */
.col-filter input.cf-val {
  flex: 1 1 auto; min-width: 24px; width: auto;
  font-size: 11px; padding: 0 2px; height: 18px;
  border: 1px solid #c7ccd6; text-align: right;
}
.col-filter input.cf-val-text { text-align: left; }
/* has-val — жёлтая заливка: видно «на этой колонке висит фильтр» даже когда
 * значение не влезло в узкое поле. */
.col-filter input.cf-val.has-val { background: #fffae0; border-color: #d4a800; }
.col-filter button.cf-clear { flex: 0 0 auto; width: 14px; height: 18px; padding: 0; font-size: 12px; line-height: 1; border: 1px solid #c7ccd6; background: #f0f0f0; cursor: pointer; color: #666; }
.col-filter button.cf-clear:hover { background: #ffd6d6; color: #c00; }
/* P343-17: кнопки-действия caller'а у фильтра (TableTools initColumnFilters
 * `actions`; матрица плана — «+ план» / «− план», D23). Подпись короткая,
 * смысл и числа — в title; выключены при пустом поле фильтра. Контейнер
 * `.cf-actions` — в строку; `is-stack` (опция actionsLayout: 'stack', P343-26)
 * — ярусом, узкий столбец: ячейка фильтра не распирает колонку. */
.col-filter .cf-actions { flex: 0 0 auto; display: flex; gap: 2px; margin-left: 2px; }
.col-filter .cf-actions.is-stack { flex-direction: column; gap: 1px; }
.col-filter button.cf-action {
  flex: 0 0 auto; height: 18px; padding: 0 5px;
  font-size: 11px; line-height: 1; white-space: nowrap;
  border: 1px solid #c7ccd6; background: #fff; color: #3a4154; cursor: pointer;
}
.col-filter .cf-actions.is-stack button.cf-action { height: 15px; padding: 0 3px; font-size: 10px; }
.col-filter button.cf-action:hover:not(:disabled) { background: #eef3fc; border-color: #2d6cdf; color: #2d6cdf; }
.col-filter button.cf-action:disabled { opacity: .5; cursor: default; }

/* Компактный селект периода продаж прямо в th — нативная стрелка убрана,
   вместо неё ▼ через ::after на обёртке (так HTML5 не запрещает). */
.sold-period-wrap {
  position: relative; display: inline-block;
  margin-left: 2px; vertical-align: middle;
}
.sold-period-wrap::after {
  content: '▼';
  position: absolute;
  right: 2px; top: 50%;
  transform: translateY(-50%);
  font-size: 7px; color: #2d6cdf;
  pointer-events: none;
}
.sold-period {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  font: inherit; font-size: 11px;
  padding: 0 11px 0 3px;            /* справа место под ▼ */
  border: 1px solid #c7ccd6; border-radius: 2px;
  background: #fff;
  height: 18px; line-height: 16px;
  cursor: pointer;
  width: auto;                       /* по содержимому, не 100% */
}
.sold-period:focus { outline: 1px solid #2d6cdf; outline-offset: -1px; }
.sortable-table th .sold-head { font-weight: 600; margin-right: 1px; }

/* ---------- мульти-фильтр по тегам (типы товара в шапке столбца) ---------- */
.multi-tag-filter {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 2px; min-height: 22px;
  padding: 1px 2px;
  background: #fff;
  border: 1px solid #d9dde6; border-radius: 3px;
}
.multi-tag-filter:focus-within { border-color: #2d6cdf; }
.multi-tag-filter .tag-list { display: contents; }
.multi-tag-filter .tag {
  background: #2d6cdf; color: #fff;
  padding: 0 4px 0 6px;
  border-radius: 2px;
  font-size: 11px; line-height: 16px;
  cursor: pointer; user-select: none;
  font-weight: 500;
}
.multi-tag-filter .tag::after { content: " ×"; opacity: 0.85; }
.multi-tag-filter .tag:hover { background: #b54040; }
.multi-tag-filter .tag-input {
  flex: 1 1 80px; min-width: 60px;
  border: 0; outline: none;
  background: transparent;
  font: inherit; font-size: 11px;
  padding: 1px 2px; color: inherit;
}

/* ---------- бейджи (источник cost) ---------- */
.badge {
  display: inline-block;
  padding: 0 6px;
  border-radius: 2px;
  font-size: 11px;
  color: #fff;
  line-height: 16px;
}
.badge.bg-1 { background: #1d7c4d; }   /* manual — зелёный */
.badge.bg-2 { background: #2d6cdf; }   /* ozon — синий */
.badge.bg-3 { background: #b54040; }   /* type — красный */
.badge.bg-4 { background: #8a8f99; }   /* себестоимость не задана — серый */
/* P343-28 (D39): товар в архиве Ozon — вне плана поставки */
.badge.bg-archived {
  background: #fff3cd;
  color: #7a5b00;
  border: 1px solid #d4a72c;
  padding: 0 5px;
}

/* ---------- график ---------- */
.chart-wrap {
  height: 540px;
  background: #fff;
  border: 1px solid #d9dde6;
  border-radius: 4px;
  padding: 6px;
  margin: 0 0 8px 0;
  /* CSS Grid gotcha: grid-items по дефолту имеют min-width:auto = min-content,
   * из-за чего canvas «впивается» в свою intrinsic ширину и блокирует
   * сжатие parent-div'а. min-width:0 + overflow:hidden разрешают grid'у
   * применять fr-значения честно (для vertical resize между графиками). */
  min-width: 0;
  overflow: hidden;
  /* Flex column — section-h + legend + canvas стекаются. canvas с flex:1
   * берёт остаток высоты, легенда наполняется чипами и растёт по содержимому. */
  display: flex;
  flex-direction: column;
}
.chart-wrap > canvas {
  flex: 1 1 0;
  min-height: 0;  /* разрешить flex сжимать canvas ниже intrinsic-размера */
}

/* P169: шапка-переключатель типа верхнего виджета (‹ заголовок ›).
   Лежит первой строкой в .chart-wrap (flex-column) над canvas. */
.top-chart-head {
  display: flex;
  align-items: center;
  gap: 4px;
}
.top-chart-nav {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  line-height: 1;
  padding: 0;
  font-size: 16px;
  color: #5a6477;
  background: #fff;
  border: 1px solid #d9dde6;
  border-radius: 4px;
  cursor: pointer;
  user-select: none;
}
.top-chart-nav:hover {
  border-color: #2d6cdf;
  color: #2d6cdf;
  background: #f1f5ff;
}

/* Контрольная планка dynChart — окно + легенда в одном ряду внутри
 * dynChartWrap, между section-h и canvas. Высота автоматическая
 * (растёт когда легенда wrap'ится). */
.dyn-controls-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding: 2px 0 4px 0;
  font-size: 11px;
}
.dyn-controls-bar label { display: inline-flex; align-items: center; gap: 3px; }
.dyn-controls-bar select {
  font: inherit; font-size: 11px;
  padding: 1px 4px; height: 20px;
  border: 1px solid #c7ccd6; border-radius: 3px;
  background: #fff;
}

/* Чипы прокси-метрик для скрытых колонок которые включены на dynChart.
   Стилистически как .cf-chart.active — зелёный фон, иконка + текст. */
.hidden-charts-bar {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  align-items: center;
}
.hidden-chart-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font: inherit; font-size: 11px;
  padding: 2px 7px 2px 5px;
  background: #dcfce7;
  border: 1px solid #16a34a;
  color: #166534;
  border-radius: 11px;
  cursor: pointer;
  white-space: nowrap;
  line-height: 1;
}
.hidden-chart-chip:hover {
  background: #bbf7d0;
  box-shadow: 0 0 6px 1px rgba(34, 197, 94, .55);
}
.hidden-chart-chip svg { display: block; flex-shrink: 0; color: #16a34a; }
.hidden-chart-chip::after { content: ' ×'; opacity: 0.7; }

/* Кастомная HTML-легенда для dynChart (Chart.js built-in отключён).
 * Чипы кликабельные — toggle visibility датасета. Flex-wrap → высота
 * легенды растёт сама когда метрик много. */
.dyn-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 6px;
  padding: 2px 0 4px 0;
  font-size: 11px;
  align-items: center;
  line-height: 1;
  flex: 1 1 auto;
}
.dyn-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px 2px 4px;
  border: 1px solid #d9dde6;
  border-radius: 11px;
  background: #fff;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  color: #1d2330;
}
.dyn-legend-item .sw {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
}
.dyn-legend-item:hover { border-color: #2d6cdf; }
.dyn-legend-item.is-hidden { opacity: 0.45; }
.dyn-legend-item.is-hidden .sw { opacity: 0.35; }
/* is-static — чипы legend'а только для индикации, не кликабельны.
   Управление видимостью серий — через кнопку 📈 в filter-row таблицы. */
.dyn-legend-item.is-static { cursor: default; }
.dyn-legend-item.is-static:hover { border-color: #d9dde6; }
/* Info-чип про outlier'ы — не кликабельный, маркер в стиле dynChart outlier'а
   (ярко-красный с двойным контуром тёмно-красного цвета). */
.dyn-legend-item.is-outlier-info {
  cursor: help;
  color: #7f1d1d;
  border-color: #fca5a5;
  background: #fef2f2;
}
.dyn-legend-item.is-outlier-info:hover { border-color: #7f1d1d; }
.dyn-legend-item .sw.outlier-sw {
  background: #ef4444;
  border: 2px solid #7f1d1d;
  box-sizing: border-box;
  width: 12px; height: 12px;
}

/* ---------- resize-handles графиков (толстые 12px с hover feedback) ---------- */
/* Используются в Аналитике (горизонтальный split + общая высота) и в РНП
 * (только горизонтальная — высота графика над таблицей).
 * grid-column: 1/-1 на .chart-handle-h работает только если родитель grid,
 * вне grid (как в РНП) свойство игнорируется — handle ведёт себя как блок. */
.chart-handle {
  position: relative;
  user-select: none;
  z-index: 2;
  background: #e3e7ec;
  transition: background 120ms;
}
.chart-handle:hover  { background: #c7ccd6; }
.chart-handle:active { background: #2d6cdf; }
.chart-handle-v { cursor: ew-resize; }
.chart-handle-h { cursor: ns-resize; height: 12px; grid-column: 1/-1; }
.chart-handle-icon {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  font-size: 12px;
  color: #5a6477;
  background: #fff;
  padding: 0 3px;
  line-height: 1.2;
  pointer-events: none;
  border-radius: 2px;
  border: 1px solid #c7ccd6;
}

/* ---------- P137 / P364: пометка неполного покрытия продаж заказами ----------
 * Покрытие (`coverage_pct`) — доля выручки продаж периода, чей заказ есть в
 * базе; у остальных продаж нет товара и себестоимости, их деньги — в строке
 * «Продажи без заказа». Ниже порога ячейка значения, искажённого этим, получает
 * блёклый бордовый шрифт и ⚠ через ::after, а подсказка — в какую сторону
 * значение отклонилось. Помечаются строки товаров и ИТОГО (th в tfoot); сама
 * строка «Продажи без заказа» — нет. Ячейка остаётся читаемой (color +
 * opacity, не visibility:hidden). ИТОГО прилипает к низу таблицы и обязано
 * оставаться непрозрачным (opacity просвечивала бы строки под ним) — его цвет
 * пометки задан рядом с остальными стилями tfoot в шаблоне страницы.
 *
 * Какие колонки помечать, решает не CSS: порог и охват суммы строк —
 * v2_analytics/coverage_marks.js, направление метрики — `coverageBias` в
 * metrics_registry.js.
 */
table.dense td.low-coverage {
  color: #7c2d12;
  opacity: 0.65;
}
table.dense td.low-coverage::after,
table.dense th.low-coverage::after {
  content: ' ⚠';
  margin-left: 1px;
  opacity: 0.7;
  font-size: 0.85em;
}

/* ---------- P285: доля выше 100% — погрешность атрибуции ----------
 * Рекламные доли (конверсии воронки, доля рекл. заказов/выручки) иногда
 * переваливают 100%: Ozon относит заказ ко дню кампании, а добавление в
 * корзину — ко дню, когда его положили, поэтому в числитель попадают заказы,
 * чьи корзины остались за границей периода. Это НЕ ошибка расчёта, и клампить
 * такое значение нельзя — оно правдиво. Помечаем янтарным бэйджем с
 * подсказкой, чтобы вопрос «как доля бывает больше единицы» снимался на месте.
 *
 * Какие метрики так умеют — знает реестр (`attrCap` в metrics_registry.js),
 * не CSS и не рендерер. Цвет намеренно отличается от бордового
 * .low-coverage: там «данные неполные», здесь «данные полные, но окно чужое».
 *
 * Красим ТОЛЬКО шрифтом, без фона: заливку ячейки занимает heatmap, и он
 * пишет её inline-стилем — фон отсюда всё равно проиграл бы по специфичности.
 * Тот же приём у .low-coverage выше, ради того же соседства.
 */
table.dense td.attr-overflow,
table.dense th.attr-overflow {
  color: #92400e;
  font-weight: 600;
}
table.dense td.attr-overflow::after,
table.dense th.attr-overflow::after {
  content: '∗';
  margin-left: 2px;
  font-size: 0.9em;
  font-weight: 700;
  opacity: 0.85;
}

/* P137: dyn-legend swatch для low-coverage info-chip — тёмно-красный кружок,
 * визуально соответствует pointBackgroundColor в Chart.js. */
.dyn-legend-item.is-low-coverage-info .low-coverage-sw {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #dc2626;
  border: 1px solid #7f1d1d;
  margin-right: 4px;
  vertical-align: middle;
}

/* ===== P159-H: общая «оболочка» попапов (Колонки/Подсветка) =====
   Липкий header с ✕ сверху + скролл-тело + липкий footer (Применить/Отмена).
   Каркас (flex-колонка, скролл только у тела) — общий класс .tt-popup;
   позиционирование задаётся попап-специфичными классами ниже. */
.tt-popup {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid #c7ccd6;
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18);
  font-size: 12px;
  overflow: hidden;            /* скролл уходит во внутреннее тело, не в контейнер */
  padding: 0;                  /* паддинги — на зонах, иначе липкие полосы не прижимаются вплотную */
}

/* Колонки: якорится под кнопкой (left/top проставляет JS), по умолчанию скрыт. */
.tt-cols-popup.tt-popup {
  position: absolute;
  z-index: 1000;
  min-width: 200px;
  max-height: 400px;
  display: none;               /* CLOSED */
}
.tt-cols-popup.tt-popup.is-open {
  display: flex;               /* OPEN — переопределяет display:none, сохраняя flex-колонку */
}

/* Подсветка: фиксированная позиция справа-сверху (как было до P159-H). */
.hm-popup.tt-popup {
  position: fixed;
  top: 60px;
  right: 20px;
  z-index: 9999;
  min-width: 520px;
  max-height: 80vh;
}

/* Липкая шапка с заголовком и ✕ */
.tt-popup__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid #e3e7ee;
  background: #fff;
  font-weight: 600;
}

/* P325: полоса фильтра по названию — между липкой шапкой и скролл-телом,
   поэтому не уезжает вместе со списком (flex:0 0 auto, как шапка и футер). */
.tt-popup__filter {
  flex: 0 0 auto;
  padding: 6px 10px;
  border-bottom: 1px solid #e3e7ee;
  background: #fff;
}
.tt-popup__filter-input {
  width: 100%;
  box-sizing: border-box;      /* иначе padding+border вылезают за min-width попапа */
  padding: 4px 8px;
  border: 1px solid #c7ccd6;
  border-radius: 4px;
  font: inherit;
  font-size: 12px;
}
.tt-popup__filter-input:focus {
  outline: none;
  border-color: #2d6cdf;
}

/* P325: пустой результат фильтра. Без него «ничего не нашлось» неотличимо
   от пустого попапа. */
.tt-popup__empty {
  padding: 8px 4px;
  color: #888;
  font-style: italic;
}

/* P325: скрытие строки фильтром. `!important` — не украшение: строки попапа
   «Колонки» параллельно гасит `layout.js` инлайновым `style.display`
   (неприменимые для группы колонки). Условия независимы и складываются по И;
   без важности фильтр и группа затирали бы друг друга через одно и то же
   свойство. Так — каждый механизм умеет только СКРЫТЬ, показать вопреки
   другому не может ни один. */
.tt-filtered-out { display: none !important; }

/* P325: строка попапа «Колонки». `display:block` переехал сюда из инлайнового
   стиля: `layout.js` возвращает строку в видимые через `style.display = ''`,
   что стирает инлайновое объявление целиком — вместе с блочностью, и <label>
   схлопывался в inline. Из класса блочность переживает такой сброс. */
.tt-popup__row {
  display: block;
  padding: 3px 4px;
  cursor: pointer;
  user-select: none;
}

/* P325: строка попапа «Подсветка» — обёртка над шестью ячейками грида
   (#hmRows). `display:contents` оставляет ячейки прямыми детьми грида, так что
   колонки не разъезжаются, но даёт фильтру ОДИН элемент, чтобы скрыть строку
   целиком. */
.tt-popup__grid-row { display: contents; }

/* Скроллируемое тело */
.tt-popup__body {
  flex: 1 1 auto;
  min-height: 0;               /* критично: иначе flex-ребёнок не даёт overflow работать */
  overflow: auto;
  padding: 8px 10px;
}

/* Липкий футер с кнопками */
.tt-popup__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid #e3e7ee;
  background: #fff;
}

/* Кнопка-крестик */
.tt-popup__x {
  font-size: 18px;
  line-height: 1;
  border: none;
  background: none;
  cursor: pointer;
  color: #888;
  padding: 0 2px;
}
.tt-popup__x:hover { color: #333; }

/* Кнопки футера */
.tt-popup__btn {
  padding: 5px 12px;
  border: 1px solid #c7ccd6;
  border-radius: 4px;
  background: #f5f6f8;
  cursor: pointer;
  font-size: 12px;
}
.tt-popup__btn:hover { background: #eceef2; }
.tt-popup__btn--primary {
  background: #2d6cdf;
  border-color: #2d6cdf;
  color: #fff;
}
.tt-popup__btn--primary:hover { background: #245ac0; }

/* «Сбросить всё»/«Показать все» прижать влево, оставив Применить/Отмена справа */
.tt-popup__foot .tt-popup__btn--reset { margin-right: auto; }

/* ---------- P333: страница «Финансы» — знак статьи задаёт цвет ---------- */
/* Цвет — из `sign` реестра колонок (v2_finance/columns.js): plus — идёт
   продавцу в плюс, minus — в минус; у итогов P&L (sign value) класс выбирает
   view по знаку значения теми же тремя цветами. */
table.dense td.fin-plus  { color: #1a7f37; }
table.dense td.fin-minus { color: #b54040; }
table.dense td.fin-none  { color: #444; }
/* P335: нет отчёта о реализации — пусты только ЕГО колонки (по `source`
   реестра), статьи операций и баланса в той же строке живут своей жизнью. */
table.dense td.fin-absent { color: #9aa3b0; }
/* «Сверка» (kind delta): сходится до копейки — галка; иначе разница красным. */
table.dense td.fin-delta-ok  { color: #1a7f37; text-align: center; font-weight: 700; }
table.dense td.fin-delta-off { color: #b54040; font-weight: 700; }
table.dense th.fin-group { text-align: center; font-weight: 600; background: #e6e9ef; border-left: 1px solid #d9dde6; }
/* P336-2: ячейка «Месяц» в строке групп — пустая, без левой границы группы. */
table.dense th.fin-group-blank { border-left: 0; }
table.dense th.fin-col   { white-space: normal; min-width: 96px; vertical-align: bottom; }
/* P336-2: вторая строка шапки липнет ПОД строкой групп — её высоту view
   кладёт в --fin-groups-h после каждой пересборки (P236c: слой под слоем,
   не два слоя на одном top). */
#finTable thead tr.sort-row th { top: calc(var(--sticky-top, 36px) + var(--fin-groups-h, 0px)); }
/* P336-2: график по месяцам страницы под таблицей — ниже графика РНП: точек 12. */
.fin-chart-wrap.chart-wrap { height: 380px; }
table.dense td.fin-month { white-space: nowrap; font-weight: 600; }
/* Галка «вести учёт …» над колонкой налога (виды — FinanceColumns.TAX_KINDS). */
.fin-tax-toggle { display: block; font-weight: 400; font-size: 11px; color: #555; margin-top: 3px; white-space: nowrap; }
.fin-tax-toggle input { vertical-align: middle; margin: 0 3px 0 0; }
.fin-pager { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; font-size: 12px; color: #555; }
.fin-pager button[disabled] { opacity: .45; cursor: default; }
/* P333-3 / P336-2: налог → польз. расход. «+» — чёрный, активный; галка —
   блекло-зелёная, неактивная: расход уже есть, состояние выведено из данных.
   Символы рисует ::after — текст ячейки остаётся числом (экспорт, сортировка). */
.tax-add { margin-left: 6px; width: 18px; height: 18px; line-height: 15px; padding: 0; border: 1px solid #222;
  border-radius: 3px; background: #fff; color: #111; font-weight: 700; font-size: 14px; cursor: pointer; vertical-align: middle; }
.tax-add::after { content: '+'; }
.tax-add:hover { background: #f0f0f0; }
.tax-add[disabled] { opacity: .45; cursor: default; }
/* P367: архив документов Ozon за месяц — кнопка в ячейке месяца «Финансов» */
.fin-docs { margin-left: 6px; width: 18px; height: 18px; line-height: 15px; padding: 0; border: 1px solid #222;
            border-radius: 3px; background: #fff; cursor: pointer; font-size: 12px; vertical-align: middle; }
.fin-docs::after { content: '⇩'; }
.fin-docs:hover { background: #f0f0f0; }
.fin-docs[disabled] { opacity: .45; cursor: default; }
.tax-linked { margin-left: 6px; display: inline-block; width: 18px; height: 18px; line-height: 16px; text-align: center;
  border: 1px solid #c3d9c8; border-radius: 3px; color: #8fbf9a; background: #f4f9f5; font-weight: 700; font-size: 12px;
  cursor: default; vertical-align: middle; }
.tax-linked::after { content: '\2713'; }

/* ---------- P343-23, P343-49 (D35, D64): «Группировка по типу товара» (plan_groups_table.js) ----------
 * Компонент двух страниц — «Склады» (под матрицей плана) и шаг 6 мастера поставки (под ТЗ). */
table.pg-table th, table.pg-table td { border-right: 1px solid #e3e6ec; }
table.pg-table tr > *:last-child { border-right: none; }
/* Метка «без себестоимости: N арт., M шт» у суммы закупки (в выгрузку не идёт — data-tt-skip). */
table.pg-table td .pg-note { display: block; font-size: 10px; font-weight: 400; color: #946000; white-space: nowrap; cursor: help; }
table.pg-table td.pg-empty { text-align: center; padding: 12px; color: #888; }
table.pg-table tfoot td { font-weight: 600; background: #f7f9fc; border-top: 2px solid #d9dde6; }

/* ---------- P343-20 (D29): диалог «Импорт плана из файла» (plan_import_dialog.js) ---------- */
/* Оболочка — общий каркас попапов .tt-popup (шапка / скролл-тело / футер);
   здесь только слой поверх страницы, ширина и оформление отчёта. */
.pi-overlay {
  position: fixed; inset: 0; z-index: 10000;
  background: rgba(20, 28, 45, .35);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 60px 16px 16px;
}
.pi-overlay[hidden] { display: none; }
.pi-changes { margin-top: 4px; }
.pi-dialog.tt-popup { width: min(780px, 100%); max-height: calc(100vh - 80px); font-size: 12px; }
/* D44 (P343-28): «Применить» при ошибках в файле видна, но выключена — и выглядит выключенной */
.pi-dialog .tt-popup__btn:disabled,
.pi-dialog .tt-popup__btn:disabled:hover { background: #c9d0dc; border-color: #c9d0dc; color: #fff; cursor: not-allowed; }
.pi-file { color: #555; margin: 0 0 6px; word-break: break-all; }
/* Строки отчёта: уровень задаёт маркер и цвет (ok / info / warn / err). */
.pi-lines { list-style: none; margin: 0 0 8px; padding: 0; }
.pi-lines li { position: relative; padding: 2px 0 2px 18px; line-height: 1.4; }
.pi-lines li::before { content: '•'; position: absolute; left: 4px; color: #8a93a3; }
.pi-lines li.ok { color: #1d7c4d; }
.pi-lines li.ok::before { content: '✓'; color: #1d7c4d; }
.pi-lines li.warn { color: #946000; }
.pi-lines li.warn::before { content: '!'; font-weight: 700; color: #e6a23c; }
.pi-lines li.err { color: #b54040; }
.pi-lines li.err::before { content: '✕'; color: #b54040; }
/* Предпросмотр изменений: артикул × кластер, сейчас → из файла. */
.pi-preview { border-collapse: collapse; width: 100%; font-size: 12px; }
.pi-preview th, .pi-preview td { border-bottom: 1px solid #eee; padding: 2px 6px; text-align: left; }
.pi-preview th { background: #f7f9fc; font-weight: 600; }
.pi-preview th.num, .pi-preview td.num { text-align: right; white-space: nowrap; }
.pi-preview td.from { color: #8a93a3; }
.pi-more { color: #8a93a3; font-style: italic; padding: 4px 6px; }
/* Статус в футере — слева от кнопок. */
.pi-status { margin-right: auto; font-size: 12px; color: #555; }
