/* ============================================================
   base.css — общие переменные и примитивы обеих страниц прототипа.

   Вынесено из <style> страниц app/index.html и app/index-desktop.html:
   10 переменных :root (совпадали по значению) и 24 правила, дословно
   совпадающих в обеих страницах. Всё, что различается (отступы, радиусы,
   размеры, разрядность денег, каркас страницы), осталось в страницах.

   Подключается до <style> страницы:
       <link rel="stylesheet" href="css/base.css">
   ============================================================ */

:root{
  --bg:#0f1419; --panel:#1a2027; --panel2:#232b34; --line:#2e3a45;
  --txt:#e6edf3; --muted:#8b98a5; --accent:#4a9eff; --accent2:#3fb950;
  --warn:#d29922; --danger:#f85149;

  /* Цвета уровней заполнения: пара «верх → низ» линейного градиента.
     Объявлены переменными, чтобы светлая тема переопределяла их одним
     блоком [data-theme="light"], а не правкой каждого градиента. */
  --lvl-green-1:#4dd263;  --lvl-green-2:#2a9b3e;
  --lvl-yellow-1:#e6d93c; --lvl-yellow-2:#b3a41e;
  --lvl-orange-1:#f0a44a; --lvl-orange-2:#c67416;
  --lvl-red-1:#ef6359;    --lvl-red-2:#b3312a;
  --lvl-maroon-1:#a62548; --lvl-maroon-2:#5c1024;
  --lvl-empty-1:#2a333d;  --lvl-empty-2:#1e262e;
  /* Витки пустой ячейки чуть светлее — их видно, но они не спорят
     с заполненными. */
  --lvl-turn-1:#232b34;   --lvl-turn-2:#1a2027;
  /* Рамка блока объединённых ячеек: цвет уровня, но глуше градиента спирали —
     рамка обрамляет, а не спорит с содержимым. */
  --grp-green:#3fb950;  --grp-yellow:#d4c72a; --grp-orange:#e08e2a;
  --grp-red:#e5534b;    --grp-maroon:#7d1a2e; --grp-empty:#4d5560;
}

.b-blue{background:rgba(74,158,255,.15); color:var(--accent)}
.b-gray{background:rgba(139,152,165,.15); color:var(--muted)}
.b-green{background:rgba(63,185,80,.15); color:var(--accent2)}
.b-red{background:rgba(248,81,73,.15); color:var(--danger)}
.b-warn{background:rgba(210,153,34,.15); color:var(--warn)}
.config-actions .info b{color:var(--accent)}
.form-row .form-field{flex:1}
.pulse{animation:pulse 2s ease-out infinite}
.scan-result{margin-top:10px; padding:8px 10px; border-radius:8px; font-size:13px}
.scan-result.err{background:rgba(248,81,73,.12); border:1px solid rgba(248,81,73,.45)}
.scan-result.ok{background:rgba(63,185,80,.12); border:1px solid rgba(63,185,80,.45)}
.slot .qty .in-backup{color:var(--accent); font-weight:600}
.slot .qty .in-backup::after{content:')'}
.slot .qty .in-backup::before{content:'('}
.slot-group .group-info .qty .in-backup{color:var(--accent); font-weight:600}
.slot-group .group-info .qty .in-backup::after{content:')'}
.slot-group .group-info .qty .in-backup::before{content:'('}
.slot.empty{border-style:dashed}
.slot.empty .price{color:var(--muted); font-weight:400}
.slot.empty .product{color:var(--muted); font-style:italic}
.slot.low .qty{color:var(--warn)}
.slot.zero .qty{color:var(--danger)}
.slots.select-mode .slot{cursor:crosshair}
.stat .value.up{color:var(--accent2)}

/* ============================================================
   СПИРАЛЬ ЗАПОЛНЕНИЯ ЯЧЕЙКИ

   Спираль показывает, сколько витков занято товаром. Раньше заполнение
   читалось только по числу «8/10» — чтобы понять, полная ячейка или пустая,
   приходилось вчитываться в цифры. Спираль видна с одного взгляда.

   Живёт в общем файле: ячейки рисуют обе страницы, и разошедшиеся копии
   стилей дали бы разный вид одного и того же экрана.
   ============================================================ */

.spiral{
  position:absolute;
  left:6px;
  top:14px;
  bottom:14px;
  width:7px;
  display:flex;
  flex-direction:column-reverse;   /* заполненные витки — снизу вверх */
  gap:1px;
  pointer-events:none;             /* спираль декоративная: клик идёт ячейке */
  z-index:1;
}
.spiral .turn{
  flex:1 1 0;
  min-height:2px;                  /* при 10 витках виток всё ещё различим */
  border-radius:2px;
  background:linear-gradient(180deg, var(--lvl-empty-1), var(--lvl-empty-2));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.05),
    inset 0 -1px 1px rgba(0,0,0,.35);
  transition:background .25s, box-shadow .25s;
}
.spiral .turn.filled{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.3),
    inset 0 -1px 1px rgba(0,0,0,.3);
}

/* Уровни заполнения. Границы включающие: ровно 90% — уже зелёный. */
.spiral[data-level="green"]  .turn.filled{background:linear-gradient(180deg, var(--lvl-green-1), var(--lvl-green-2))}
.spiral[data-level="yellow"] .turn.filled{background:linear-gradient(180deg, var(--lvl-yellow-1), var(--lvl-yellow-2))}
.spiral[data-level="orange"] .turn.filled{background:linear-gradient(180deg, var(--lvl-orange-1), var(--lvl-orange-2))}
.spiral[data-level="red"]    .turn.filled{
  background:linear-gradient(180deg, var(--lvl-red-1), var(--lvl-red-2));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.3),
    inset 0 -1px 1px rgba(0,0,0,.4),
    0 0 3px rgba(229,83,75,.4);
}
.spiral[data-level="maroon"] .turn.filled{
  background:linear-gradient(180deg, var(--lvl-maroon-1), var(--lvl-maroon-2));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.25),
    inset 0 -1px 1px rgba(0,0,0,.45),
    0 0 3px rgba(166,37,72,.45);
}

/* Пустая ячейка: витки приглушены, но видны — видно, сколько их всего. */
.slot.empty .spiral .turn{
  background:linear-gradient(180deg, var(--lvl-turn-1), var(--lvl-turn-2));
  opacity:.75;
}

/* Процент заполнения: вертикально у нижнего края спирали, на свободном месте. */
.pct{
  position:absolute;
  left:4px;
  bottom:6px;
  font-size:7px;
  font-weight:700;
  color:rgba(255,255,255,.55);
  writing-mode:vertical-rl;
  text-orientation:mixed;
  letter-spacing:.3px;
  line-height:1;
  z-index:3;
  pointer-events:none;
}
.slot.empty .pct{display:none}

/* ============================================================
   СПИРАЛЬ БЛОКА ОБЪЕДИНЁННЫХ ЯЧЕЕК

   У блока своя спираль — по суммарному остатку: внутренние ячейки показывают
   только код, и без общей спирали заполнение блока было бы не видно.
   ============================================================ */

.slot-group .spiral{
  left:7px;
  top:14px;
  bottom:14px;
  width:7px;
}
/* Внутренние ячейки блока спираль не показывают — только код. */
.slot-group .slot .spiral{display:none}

.slot-group[data-level="green"]{
  border-color:var(--grp-green);
  background:linear-gradient(180deg, rgba(63,185,80,.10), rgba(63,185,80,.03));
  box-shadow:0 0 0 1px rgba(63,185,80,.25);
}
.slot-group[data-level="yellow"]{
  border-color:var(--grp-yellow);
  background:linear-gradient(180deg, rgba(212,199,42,.10), rgba(212,199,42,.03));
  box-shadow:0 0 0 1px rgba(212,199,42,.25);
}
.slot-group[data-level="orange"]{
  border-color:var(--grp-orange);
  background:linear-gradient(180deg, rgba(224,142,42,.10), rgba(224,142,42,.03));
  box-shadow:0 0 0 1px rgba(224,142,42,.3);
}
.slot-group[data-level="red"]{
  border-color:var(--grp-red);
  background:linear-gradient(180deg, rgba(229,83,75,.10), rgba(229,83,75,.03));
  box-shadow:0 0 0 1px rgba(229,83,75,.35);
}
.slot-group[data-level="maroon"]{
  border-color:var(--grp-maroon);
  background:linear-gradient(180deg, rgba(125,26,46,.12), rgba(125,26,46,.04));
  box-shadow:0 0 0 1px rgba(125,26,46,.4);
}
.slot-group[data-level="empty"]{
  border-color:var(--grp-empty);
  background:linear-gradient(180deg, rgba(139,152,165,.06), rgba(139,152,165,.02));
  box-shadow:none;
}

/* ============================================================
   ЛЕГЕНДА ЗАПОЛНЕНИЯ

   Показывается над списком полок и не сворачивается: цвета нужно сверять
   глазами, а спрятанная под спойлер легенда не помогает.
   ============================================================ */

.legend{
  display:flex; gap:14px; flex-wrap:wrap; align-items:center;
  padding:10px 14px;
  background:var(--panel2); border:1px solid var(--line);
  border-radius:8px; font-size:12px; margin-bottom:16px;
}
.legend .item{display:flex; align-items:center; gap:6px; color:var(--muted)}
.legend .swatch{
  width:10px; height:10px; border-radius:2px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.25), inset 0 -1px 1px rgba(0,0,0,.4);
}
.legend .swatch.green {background:linear-gradient(180deg, var(--lvl-green-1), var(--lvl-green-2))}
.legend .swatch.yellow{background:linear-gradient(180deg, var(--lvl-yellow-1), var(--lvl-yellow-2))}
.legend .swatch.orange{background:linear-gradient(180deg, var(--lvl-orange-1), var(--lvl-orange-2))}
.legend .swatch.red   {background:linear-gradient(180deg, var(--lvl-red-1), var(--lvl-red-2))}
.legend .swatch.maroon{background:linear-gradient(180deg, var(--lvl-maroon-1), var(--lvl-maroon-2))}
.legend .swatch.empty {background:linear-gradient(180deg, var(--lvl-empty-1), var(--lvl-empty-2))}

/* ============================================================
   СХЕМА АВТОМАТА: БУНКЕРЫ, ЁМКОСТИ, СТАКАНЫ

   Раздел «Бункеры и ёмкости» показывает автомат как он есть: ёмкости
   нарисованы сосудами, уровень — заливкой снизу. Так состояние читается
   мгновенно, без разбора столбца чисел.

   Цвет заливки — признак СОДЕРЖИМОГО (кофе, молоко, шоколад), а не уровня:
   по цвету видно, что где лежит. Уровень читается высотой и процентом.

   Стаканы стоят слева отдельной группой за пунктиром: это расходник,
   а не ингредиент, и форма у них своя — конус.
   ============================================================ */

/* Цвета ингредиентов: пара «верх → низ» линейного градиента */
:root{
  --ing-coffee:#b07a4a;  --ing-coffee-2:#8a5a32;
  --ing-milk:#d8d2c4;    --ing-milk-2:#b3ab99;
  --ing-choco:#7a4a2a;   --ing-choco-2:#5c3520;
  --ing-sugar:#c9d6dd;   --ing-sugar-2:#9aa9b3;
  --ing-vanilla:#d9c46a; --ing-vanilla-2:#b09a3e;
  --ing-water:#3f9ad6;   --ing-water-2:#2a6f9e;
  --ing-default:#8b98a5; --ing-default-2:#5c6874;
  --cup-1:#5aa9f0;       --cup-2:#2a6fb0;
}

/* --- Ряд ёмкостей: выравнивание по ВЕРХНЕМУ краю ---------------------
   Подписи под сосудами разной длины, и по нижнему краю они бы «плавали».
   Верх у всех на одной линии — ряд читается как панель автомата. */
.vessels{display:flex; gap:12px; align-items:flex-start; flex-wrap:wrap}

.vessel{width:92px; text-align:center}
.vessel .tube{position:relative; height:152px}
.vessel .body{
  position:absolute; inset:0; border:1px solid var(--line);
  background:#1e262e; overflow:hidden;
  box-shadow:inset 0 2px 8px rgba(0,0,0,.5);
}
.vessel .fill{
  position:absolute; left:0; right:0; bottom:0;
  box-shadow:inset 0 2px 0 rgba(255,255,255,.22), inset 0 -2px 4px rgba(0,0,0,.35);
}
.vessel .pct{
  position:absolute; top:5px; left:0; right:0; text-align:center; z-index:2;
  font-size:12px; font-weight:600; text-shadow:0 1px 3px rgba(0,0,0,.95);
}
/* Текущий объём — по центру сосуда: это главное число, его ищут глазами
   первым. Процент остаётся сверху как оценка уровня. */
.vessel .qty-center{
  position:absolute; top:50%; left:0; right:0; z-index:2;
  transform:translateY(-50%); text-align:center;
  font-size:15px; font-weight:700;
  text-shadow:0 1px 4px rgba(0,0,0,.95);
  pointer-events:none;
}
/* Подпись под сосудом: общая ёмкость и, если есть, остаток в отсеке */
.vessel .cap{
  font-size:11px; color:var(--muted); margin-top:5px; line-height:1.3;
}
.vessel .num{font-size:12px; font-weight:600; margin-top:7px}
.vessel .nm{
  font-size:11px; color:var(--muted); line-height:1.25;
  margin-top:2px; height:28px; overflow:hidden;
}

/* Цилиндр — силуэт бункера */
.f-cyl .body{border-radius:50% / 12px}

/* Конус-стакан: верх шире низа, с ободком */
.f-cup{width:104px}
.f-cup .tube{height:160px}
.f-cup .body{
  clip-path:polygon(0 0,100% 0,80% 100%,20% 100%);
  /* Корпус светлее фона сцены (#161c22): на прежнем #14202c пустая часть
     стакана сливалась с фоном, и силуэт читался только по ободку. */
  border:none; background:#243a52;
  box-shadow:inset 0 0 0 1px rgba(124,196,255,.80);
}
.f-cup .fill{clip-path:polygon(0 0,100% 0,80% 100%,20% 100%); left:-1px; right:-1px}
.f-cup .num, .f-cup .nm{color:var(--cup-1)}
.f-cup .rim{
  position:absolute; top:-4px; left:-3px; right:-3px; height:6px; z-index:3;
  border:1px solid rgba(124,196,255,.85); border-radius:50%;
  background:rgba(124,196,255,.18);
}

/* Группа стаканов: слева, за пунктиром */
.cups-strip{
  display:flex; gap:12px; align-items:flex-start;
  padding-right:12px; margin-right:4px;
  border-right:2px dashed rgba(90,169,240,.45);
}

/* Клик по ёмкости открывает пополнение с уже выбранной ёмкостью */
.vessel.clickable{cursor:pointer}
.vessel.clickable:hover .body{
  border-color:var(--accent);
  box-shadow:inset 0 0 0 1px var(--accent), 0 0 10px rgba(74,158,255,.35);
}
.vessel.clickable:hover .num{color:var(--accent)}

/* --- ВОДА: во всю ширину, под всеми ёмкостями -----------------------
   Вода хранится в отдельной ёмкости и крупнее прочих, поэтому идёт
   отдельной полосой. Название стоит НА полосе: подпись под ней
   отрывалась бы от уровня, который поясняет.

   Заливка отражена по вертикали: она растёт ВВЕРХ от нижней кромки,
   а её верхняя кромка выгнута кверху — как поверхность воды в сосуде.
   У остальных ёмкостей верх заливки ровный. */
.water-vessel{width:100%; margin-top:16px; text-align:left}
.water-vessel .tube{height:44px}
.water-vessel .body{border-radius:50% / 8px}
.water-vessel .fill{
  /* Отражение по вертикали: растём вверх, выпуклость смотрит кверху */
  transform:scaleY(-1);
  transform-origin:center;
  /* Зеркалим и градиент: иначе светлая часть ушла бы вниз */
  filter:none;
}
.water-vessel .label{
  position:absolute; inset:0; z-index:2;
  display:flex; align-items:center; gap:10px; padding:0 14px;
  font-size:12px; text-shadow:0 1px 3px rgba(0,0,0,.9);
}
.water-vessel .label .nm{color:var(--txt); font-weight:600; height:auto; margin:0}
.water-vessel .label .num{margin:0 0 0 auto; font-weight:600}

/* Легенда цветов под схемой */
.vessels-legend{
  display:flex; gap:14px; flex-wrap:wrap;
  font-size:12px; color:var(--muted); margin-top:14px;
}
.vessels-legend span{display:flex; align-items:center; gap:6px}
.vessels-legend i{width:10px; height:10px; border-radius:2px; display:inline-block}

/* ============================================================
   МОДАЛЬНЫЕ ОКНА: ЕДИНАЯ ПОДЛОЖКА

   Один набор классов на всё приложение. Раньше эти правила лежали прямо
   в разметке десктопной страницы, и мобильная страница оставалась без них:
   окна, которые рисуют общие модули (приёмка, шаблон модели, задания),
   на ней выводились обычными блоками в потоке — без затемнения, рамки
   и прокрутки. Теперь подложка общая, и вид окна не зависит от страницы.
   ============================================================ */
.modal-backdrop{
  position:fixed;inset:0;background:rgba(0,0,0,.6);
  display:flex;align-items:center;justify-content:center;
  z-index:1000;padding:20px;
}
.modal{
  background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);width:100%;max-width:900px;
  max-height:90vh;display:flex;flex-direction:column;
  box-shadow:0 20px 60px rgba(0,0,0,.5);
}
.modal.sm{max-width:520px}
.modal.xs{max-width:420px}
.modal-header{
  padding:14px 18px;border-bottom:1px solid var(--line);
  display:flex;justify-content:space-between;align-items:center;
}
.modal-header h2{font-size:15px;margin:0}
.modal-body{padding:18px;overflow:auto}
/* Кнопки окна отделены от содержимого: у длинных форм они не уезжают
   вместе с текстом, а остаются внизу. */
.modal-footer{
  display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;
  margin-top:16px;padding-top:14px;border-top:1px solid var(--line);
}
.modal-close{
  background:transparent;border:none;color:var(--muted);
  font-size:22px;cursor:pointer;line-height:1;padding:0 6px;
}
.modal-close:hover{color:var(--txt)}

/* ============================================================
   МЕСТО ПОД ИНДИКАТОР ЗАГРУЗКИ

   Сам индикатор — в css/ui-loader.css (VendingLoader: спираль автомата
   с опускающейся монетой). Здесь только контейнер: пока данных нет,
   показывать нечего, но место под содержимое занято — экран не «прыгает».
   ============================================================ */
.loader-panel{
  display:flex; align-items:center; justify-content:center;
  min-height:180px; padding:24px;
  background:var(--panel2); border:1px solid var(--line); border-radius:8px;
}

/* ============================================================
   ДЕЙСТВИЯ-ИКОНКИ В ТАБЛИЦАХ СОПОСТАВЛЕНИЯ
   ============================================================
   Четыре текстовые кнопки в строке перебивали данные. Значение иконки
   подписано в легенде над таблицей, а title/aria-label подсказывают его
   на месте. Размер — под строку таблицы, но не меньше удобной цели. */
.icon-btn{
  background:var(--panel2); border:1px solid var(--line); color:var(--txt);
  border-radius:6px; padding:3px 7px; font-size:14px; line-height:1;
  cursor:pointer; margin-left:4px; vertical-align:middle;
}
.icon-btn:hover:not(:disabled){border-color:var(--accent); color:var(--accent)}
.icon-btn:disabled{opacity:.45; cursor:default}
.icon-btn:focus-visible{outline:2px solid var(--accent); outline-offset:1px}

/* Список выбора остаётся текстовым: без него непонятно, с чем сопоставляют. */
.icon-select{min-width:160px; font-size:12px; padding:3px 6px;
  background:var(--panel2); border:1px solid var(--line); color:var(--txt);
  border-radius:6px; vertical-align:middle}
.icon-actions{white-space:nowrap; text-align:right}

/* Легенда значений: читается один раз, поэтому стоит над таблицей. */
.icon-legend{display:flex; flex-wrap:wrap; gap:12px; margin:10px 0 6px;
  font-size:12px; color:var(--muted)}
.icon-legend b{color:var(--txt); font-size:14px; margin-right:2px}

/* Отказ под строкой: название из реестра, подсказка и текст сервера. */
.row-error td{background:rgba(248,81,73,.08); border-left:3px solid var(--danger);
  padding:8px 10px; font-size:12px}
.row-error b{color:var(--danger)}
.row-error-action{color:var(--muted)}
.row-error-hint{color:var(--txt); margin-top:3px; max-width:760px}
.row-error-detail{color:var(--muted); margin-top:3px; font-family:ui-monospace,monospace;
  font-size:11px}

/* Сводка отказов в плашке: сколько записей ждёт разбора. */
.plate-errors{color:var(--danger); font-size:12px; margin-top:6px}
/* Значок отказа в самой строке: причина написана под строкой, но при
   прокрутке длинного списка нужно видеть, какие строки её имеют. */
.row-error-mark{color:var(--danger); font-size:13px; margin-left:4px; cursor:help}
/* ============================================================
   РАСХОЖДЕНИЕ ЦЕНЫ ПРОДАЖИ С НАСТРОЕННОЙ
   ============================================================
   Телеметрия сообщает фактическую цену. Продажа проводится по ней, но если
   настроенная цена другая, это видно на плашке напитка и на ячейке.
   Без пометки расхождение обнаруживается только сверкой выручки с чеком. */
.price-warn{border-color:var(--warn) !important; box-shadow:inset 0 0 0 1px rgba(210,153,34,.35)}
.warn-mark{color:var(--warn); font-size:11px; font-weight:600; white-space:nowrap}
.price-mismatch{color:var(--warn); font-size:11px; margin-top:2px; line-height:1.3}
.price-mismatch b{font-weight:600}
/* ============================================================
   ЧТО НЕ ПРОДАЁТСЯ / КАННИБАЛИЗАЦИЯ
   ============================================================
   Раскрытие мест внутри строки: <details> не даёт таблице разъехаться,
   а вложенная таблица показывает конкретные автоматы и ячейки. */
.an-places-row td{padding:0 !important; border-top:0 !important}
.an-places-row details{padding:6px 10px 10px}
.an-places-row summary{cursor:pointer; color:var(--muted); font-size:12px}
.an-places{margin-top:8px; width:100%; font-size:12px}
.an-places th{color:var(--muted); font-weight:500; text-align:left; padding:4px 8px}
.an-places td{padding:4px 8px; border-top:1px solid var(--border)}

/* ============================================================
   МАТРИЦЫ SKU
   ============================================================ */
.sku-svg{width:100%; height:auto; display:block}
.sku-plot{fill:var(--panel2); opacity:.35; rx:6}
.sku-axis{fill:var(--muted); font-size:11px}
.sku-axis-mid{stroke:var(--border); stroke-dasharray:4 4}
.sku-line{fill:none; stroke:var(--accent); stroke-width:2}
.sku-label{fill:var(--txt); font-size:10px}
.sku-chip{display:inline-block; padding:1px 7px; border-radius:9px; font-size:11px;
  color:#0d1117; font-weight:600}
.sku-grid td, .sku-grid th{text-align:left}
.sku-cell{border:1px solid var(--border); border-radius:8px; padding:8px 10px; min-width:120px}
.sku-cell.empty{opacity:.45}
.sku-cell-key{font-weight:600; font-size:13px}
.sku-cell-count{font-size:20px; font-weight:600; line-height:1.1}
.sku-cell-rev{color:var(--muted); font-size:11px}

/* ============================================================
   ПЕРИОД АНАЛИЗА (общий фрейм AppPeriod)
   ============================================================
   Кнопки вариантов — те же .an-tab, что были на каждом экране.
   Свой период раскрывается двумя полями дат под кнопками: держать их
   рядом с шестью кнопками тесно, а прятать в модалку — лишний шаг. */
.ap-custom{display:flex; align-items:center; gap:6px; margin-top:6px}
.ap-custom input{width:150px}

/* Полоса доли в разрезе по товарам: показывает, какую часть от лидера
   занимает товар. Без неё список из сотни строк не читается. */
.sku-bar{height:8px; background:var(--panel2); border-radius:4px; overflow:hidden}
.sku-bar i{display:block; height:100%; background:var(--accent); border-radius:4px}

/* ============================================================
   ФИЛЬТРЫ ПОРТФЕЛЯ SKU
   ============================================================
   Каждый столбец — группа кнопок-переключателей. Выбранное состояние
   видно нажатием, а не выпадающим списком: значений немного (4–9),
   и держать их на виду дешевле, чем открывать меню на каждый выбор. */
.sku-filters{display:flex; flex-wrap:wrap; gap:10px 16px; align-items:flex-end;
  margin:6px 0 4px}
.sku-filter{display:flex; flex-direction:column; gap:2px}
.sku-filter .an-tab{font-size:11px; padding:4px 9px}

/* Выбранная ячейка ABC/XYZ: подсветка рамкой, чтобы было видно,
   к чему относится список под матрицей. */
.sku-cell{cursor:pointer; transition:border-color .15s}
.sku-cell:hover{border-color:var(--accent)}
.sku-cell.selected{border-color:var(--accent); box-shadow:0 0 0 1px var(--accent) inset}
.sku-cell.empty{cursor:default}

/* Неактивная кнопка листания: видно, что дальше некуда. */
.an-tab[disabled]{opacity:.4; cursor:default}

/* ============================================================
   ЛОАДЕР ПЕРВОГО ЭКРАНА
   ============================================================
   Пока данные грузятся, виден признак работы. Без него пустой каркас
   страницы читается как «не открылось»: пять секунд молчания — это
   именно то, из-за чего страницу хочется перезагрузить.

   Оверлей на весь экран, поверх каркаса: частично отрисованный экран
   под ним не должен отвлекать. Скрывается из boot() в обоих исходах. */
.boot-loader{
  position:fixed; inset:0; z-index:9999;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; background:var(--bg, #0d1117);
}
.boot-caption{color:var(--muted, #8b949e); font-size:13px}

/* Сам индикатор — стандартный .v-loader из ui-loader.css: единый вид
   на всех экранах, и второй такой же рисовать незачем. Здесь остаётся
   только подложка и центрирование. */

/* ============================================================
   ПОДЛОЖКА «ПЕРИОД»
   ============================================================
   Группирует всё, что относится к выбору периода: название, кнопки
   вариантов и поля своего периода. Нужна потому, что рядом с периодом
   стоят другие переключатели (вид товара, метрика, разрез), и без
   видимой границы они читались как один общий набор.

   Фон — panel2, а не panel: карточка сама лежит на panel, и подложка
   того же цвета не была бы видна. */
.ap-frame{
  display:inline-flex; flex-direction:column; gap:6px;
  padding:8px 10px; border:1px solid var(--border); border-radius:10px;
  background:var(--panel2);
}
.ap-title{font-size:11px; color:var(--muted); letter-spacing:.02em}

/* Подложка фильтров таблицы SKU: тот же вид, что у «Периода» —
   один инструмент в одной рамке. */
.sku-filter-frame{
  display:flex; flex-direction:column; gap:8px;
  padding:10px 12px; border:1px solid var(--border); border-radius:10px;
  background:var(--panel2); margin:8px 0;
}
.sku-filter-title{font-size:11px; color:var(--muted); letter-spacing:.02em}

/* Плашка загрузки раздела: отступы и центрирование вокруг штатного
   индикатора. Сам индикатор — .v-loader из ui-loader.css. */
.loading-block{display:flex; align-items:center; justify-content:center;
  padding:24px 0; min-height:60px}

/* Подсказка на точках матриц: скрыта, показывается на наведении.
   Текст рисуем в SVG, а не во всплывающем div: так подсказка не уезжает
   при прокрутке и не требует пересчёта координат страницы. */
.sku-dot .sku-tip{display:none; fill:var(--txt); font-size:11px;
  paint-order:stroke; stroke:var(--bg); stroke-width:3px; stroke-linejoin:round}
.sku-dot:hover .sku-tip{display:block}
.sku-dot:hover circle{fill-opacity:1; stroke:var(--txt); stroke-width:1}

/* Версия приложения рядом с названием: тише основного текста, но читаемо. */
.app-version{
  font-size:11px; font-weight:500; letter-spacing:.02em;
  color:var(--muted); background:var(--panel2);
  border:1px solid var(--line); border-radius:10px;
  padding:1px 7px; margin-left:6px; vertical-align:middle;
  white-space:nowrap;
}
