/* ── Golos Text: свой, не с чужого сервера ───────────────────────────────────
 *
 * ⚠️ ПОЧЕМУ НЕ GOOGLE FONTS. Раньше шрифт тянулся с fonts.googleapis.com — и
 * не приходил: в браузере не оказалось НИ ОДНОГО начертания, интерфейс молча
 * съезжал на системный. Заметить это можно только зная, как должно быть:
 * ошибки нет, текст на месте, просто выглядит чужим.
 *
 * Причин у такого отказа много и все вне нашей власти: у Google Fonts в
 * России бывает и блокировка, и многосекундные ответы, и обрыв на середине.
 * Продавец в этот момент видит не «шрифт не загрузился», а «сайт какой-то
 * не такой».
 *
 * Свой файл снимает вопрос целиком: 60 КБ рядом с остальной статикой, тот же
 * сервер, никакой сети наружу, никакого исключения в политике безопасности и
 * никакой утечки IP каждого посетителя третьей стороне.
 *
 * ⚠️ ПЕРЕМЕННЫЙ ШРИФТ, ОДИН ФАЙЛ НА ВСЕ ВЕСА. `font-weight: 400 900` — это
 * не диапазон «выбери ближайшее», а честные промежуточные начертания. Восемь
 * отдельных файлов весили бы вчетверо больше и грузились бы восемью
 * запросами.
 *
 * ⚠️ `font-display: swap` — текст рисуется системным шрифтом сразу и
 * подменяется, когда файл готов. Без этого первые миллисекунды страница
 * стоит пустой: браузер ждёт шрифт, чтобы не показать «неправильный» текст.
 * Мигание подменой неприятно; пустая страница хуже.
 */
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('fonts/golos-text-cyrillic-wght-normal.woff2') format('woff2');
  /* Диапазон кириллицы: браузер скачает этот файл только если на странице
     есть кириллица. У нас есть всегда, но правило честное. */
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('fonts/golos-text-latin-wght-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Reset & Base ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ═══ ДИЗАЙН-СИСТЕМА ══════════════════════════════════════════════════════════
   Токены. Правило: в разметке не должно быть «магических» цветов и размеров —
   всё берётся отсюда, иначе экраны разъезжаются.
   Контраст каждого текстового цвета проверен на всех четырёх фонах (норма 4.5:1).
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── СВЕТЛАЯ ТЕМА (основная) ──────────────────────────────────────────────
     Контраст каждого текстового цвета посчитан на трёх фонах (белый / основной /
     серый). На светлом фоне это строже, чем на тёмном: яркие цвета проваливаются.
     Отдельная находка: ЖЁЛТЫЙ как текст на белом не проходит ни в одном оттенке
     (2.0–4.4), поэтому предупреждения — тёмно-янтарным по бледной заливке. */
  /* ── Ни одна поверхность не белая ─────────────────────────────────────────
     Карточки были #ffffff на фоне #f5f7f9 — всего 3% разницы, зато полотно
     чистого белого во весь экран. От него и резало глаза.

     Чисто «сделать карточки на 10% темнее» не выходит: 90% белого — это
     #e6e6e6, темнее прежнего фона, и карточки провалились бы под страницу.
     Поэтому опущена вся лестница целиком, с сохранением порядка:

        surface (карточка)  светлее  surface2 (поля, кнопки)
        surface2            светлее  surface3 (чипы, шапки)
        surface3            светлее  bg (страница)

     Регулируется двумя числами: --surface задаёт «бумагу», --bg — стол под
     ней. Разрыв между ними и есть глубина карточки. */
  --bg:        #e4eaf1;
  --surface:   #f8fafc;
  --surface2:  #f3f6fa;
  --surface3:  #eaeff5;
  --surface-hover: #dfe6ef;

  /* Границы чуть плотнее: на белом тонкая светло-серая линия «пропадает»,
     и карточки сливаются с фоном — именно этого не хватало против тёмной темы. */
  --border:        #d5dde7;
  --border-strong: #bcc8d6;

  --text:      #0f172a;
  --text-2:    #5b6779;      /* 5.06–5.74:1 — проходит везде */
  /* ⚠️ Было #7d8b9c с пометкой «только крупный текст и иконки». На деле им
     набраны подсказки в полях, подписи под артикулом, «нет данных» и мелкие
     пометки — 55 мест, почти все 11–13 пикселей. Контраст был 2,87–3,32:1
     при норме 4,5 для текста до 17pt: читаемо на хорошем мониторе и
     нечитаемо на солнце или на дешёвой матрице.

     Наш тест контраста это пропускал: он сверяет правила, где цвет и фон
     заданы ВМЕСТЕ, а тут фон наследуется от карточки. Ниже в тест добавлена
     проверка «каждый цвет текста против каждой поверхности» — она ловит
     именно такие случаи. */
  --text-3:    #5c6471;      /* минимум 4,75:1 на всех наших поверхностях */
  --text-muted:#5b6779;

  /* Акцент зелёный, как на референсе. Два оттенка: насыщенный для ЗАЛИВОК
     (на нём белый текст) и тёмный для ТЕКСТА — яркий на белом даёт лишь 3.5:1. */
  /* Белый текст на #0f9d58 даёт лишь 3.51:1 — для кнопок берём тёмный оттенок
     (5.36:1). Яркий остаётся для точек, иконок и тонких акцентов. */
  --accent:      #0b7a52;
  --accent-bright: #0f9d58;
  /* Чуть глубже заливочного зелёного. Бледные плашки (--accent-weak и прочие
     10-процентные заливки) считались на белой подложке; когда поверхности
     перестали быть белыми, подложка потемнела и текст на ней просел до 4,46
     при норме 4,5. Тон для ЗАЛИВОК (--accent) при этом не трогаем — на нём
     белые надписи, и его затемнение сделало бы кнопки грязными. */
  --accent-text: #0a7450;
  --accent-weak: rgba(15,157,88,.10);
  --accent-rgb:  15,157,88;
  /* Заливка акцентом + цвет текста на ней. Разные по темам не для красоты:
     на одном и том же зелёном белый текст читается только в светлой. */
  --accent-fill-a: #0b7a52;
  --accent-fill-b: #095f40;
  --accent-ink:    #ffffff;
  --accent-deep: #095f40;
  --accent2:     #7c5cff;
  --accent3:     #0a7450;

  --green:  #0a7450;
  --yellow: #92400e;
  --red:    #bd2c29;
  --danger-ink: #ffffff;   /* текст на плотной красной кнопке */
  --green-weak:  rgba(15,157,88,.10);
  --yellow-weak: rgba(245,158,11,.14);
  /* Шкала FBS: заливка яркая (её видно как цвет), текст темнее (его читают).
     Один цвет на оба не годится: яркий янтарь на белом даёт 2:1 и не читается,
     а тёмный в заливке выглядит коричневым — что и заметил пользователь. */
  --fbs-warn:      #f59e0b;
  --fbs-warn-text: #a94e08;
  /* Жёлтая ступень 42–48 ч. Раньше её красили серым — «база, ничего не
     происходит», — и это была ошибка: как раз здесь заказ ещё бесплатен, но
     последние часы. Серый говорил «не смотри сюда» ровно там, где ещё можно
     успеть. Жёлтый в заливке яркий, в тексте тёмный: светлый янтарь на белом
     даёт 2:1 и не читается. */
  --fbs-watch:      #eab308;
  --fbs-watch-text: #854d0e;
  --red-weak:    rgba(200,50,47,.09);

  /* Подложки строк FBS: очень слабые, они окрашивают ряд, а не забивают
     текст. Заливка полосы и подложка строки — один цвет разной силы. */
  --fbs-bad-bg:   rgba(189,44,41,.07);
  --fbs-soon-bg:  rgba(245,158,11,.10);
  --fbs-watch-bg: rgba(234,179,8,.09);

  /* Тени на светлом — мягкие и холодные. Чёрные жёсткие тени выглядят грязно. */
  --shadow-sm: 0 1px 2px rgba(16,24,40,.06), 0 1px 3px rgba(16,24,40,.04);
  --shadow:    0 2px 4px rgba(16,24,40,.06), 0 10px 28px rgba(16,24,40,.10);
  --shadow-lg: 0 6px 16px rgba(16,24,40,.10), 0 28px 56px rgba(16,24,40,.16);
  --shadow-accent: 0 4px 14px rgba(15,157,88,.28);

  /* Стекло шапки шло от чистого белого и на прокрутке снова давало ту самую
     белую полосу поверх приглушённых карточек. Ведём от «бумаги». */
  --glass-bg:     rgba(248,250,252,.74);
  --glass-border: rgba(255,255,255,.7);
  --glass-hi:     rgba(250,252,254,.85);
  --aurora-opacity: 1;
  --aurora-1: rgba(15,157,88,.16);
  --aurora-2: rgba(56,189,248,.14);
  --aurora-3: rgba(168,85,247,.10);
  --aurora-4: rgba(251,146,60,.11);

  /* ── Типографика ─────────────────────────────────────────────────────────── */
  --fs-caption2: 11px;
  --fs-caption:  12px;
  --fs-footnote: 13px;
  --fs-body:     14px;
  --fs-callout:  15px;
  --fs-title3:   17px;
  --fs-title2:   20px;
  --fs-title1:   24px;
  --fs-display:  32px;

  /* Веса работают на смысл, а не на украшение. Читательский текст — 400,
     подписи — 500, всё «служебное и структурное» — 600, числа и заголовки — 700.
     Восемь правил в файле ссылались на --fw-semibold, которого не существовало:
     переменная не раскрывалась, и вес просто не применялся. Отсюда и ощущение,
     что меню с шапками таблиц «недостаточно жирные». */
  /* Шкала спокойная. Поднимать её целиком нельзя: тогда темнеет каждая
     подпись и каждая сноска, страница начинает рябить и глазу негде
     отдохнуть. Вес добавляется ТОЧЕЧНО — только там, где он несёт смысл
     (см. блок «Опоры» в конце файла). */
  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semi:     600;
  --fw-semibold: 600;   /* алиас: под этим именем на него уже ссылались */
  --fw-bold:     700;
  --fw-heavy:    800;   /* только крупные числа в сводках */

  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;

  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius:    14px;
  --radius-lg: 18px;
  --radius-full: 999px;

  --ease: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --t-fast: .12s;
  --t-base: .2s;
  --t-slow: .32s;

  /* Раскрытое меню стало шире: после переименования разделов подписи вроде
     «Мониторинг остатки | поставки» перестали помещаться и обрывались
     многоточием. Обрезанное название хуже длинного меню — по нему не понять,
     тот это раздел или нет. */
  --sidebar-w: 326px;
  /* Свёрнутая полоса: 14px поля x2 + 12px паддинг кнопки x2 + 20px значок */
  --sidebar-rail: 72px;

  --cat-ads:      #d9488f;
  --cat-supply:   #b45309;
  --cat-analytics:#0b7a52;
  --cat-content:  #0369a1;
  --cat-other:    #5b6779;
}

/* ── ТЁМНАЯ ТЕМА ────────────────────────────────────────────────────────────
   Переключается атрибутом на <html>, значения токенов те же самые — поэтому
   компоненты переписывать не нужно. */
[data-theme="dark"] {
  --bg:        #0d0f1a;
  --surface:   #151828;
  --surface2:  #1c2035;
  --surface3:  #232843;
  --surface-hover: #252a45;

  --border:        rgba(255,255,255,.08);
  --border-strong: rgba(255,255,255,.14);

  --text:      #e8eaf6;
  --text-2:    #8f97c9;
  /* Та же правка, что в светлой теме: было #6a719b с контрастом 2,97–4,04:1
     на наших тёмных поверхностях. Нашла матрица токенов, добавленная в тест, —
     разбор правил этого не видел, потому что фон здесь наследуется. */
  --text-3:    #8b95ad;      /* минимум 4,68:1 */
  --text-muted:#8f97c9;

  --accent:      #17b871;
  --accent-text: #4ade9b;
  --accent-weak: rgba(23,184,113,.16);
  --accent-rgb:  23,184,113;
  --accent-fill-a: #17b871;
  --accent-fill-b: #12a163;
  --accent-ink:    #04210f;
  --accent-deep: #0d8f57;
  --accent2:     #8b84ff;
  --accent3:     #4ade9b;

  --green:  #4ade9b;
  --yellow: #ffc54b;
  --red:    #ff8098;          /* было #ff6b85 — 4.32:1 на своей подложке */
  --danger-ink: #2b0710;      /* на ярком красном белый даёт 2,39:1 */
  --green-weak:  rgba(74,222,155,.14);
  --yellow-weak: rgba(255,197,75,.14);
  /* На тёмной теме читается и яркий — разводить два оттенка незачем */
  --fbs-warn:      #ffc54b;
  --fbs-warn-text: #ffc54b;
  /* Жёлтая ступень: на тёмной теме нужен оттенок, отличимый от янтарного
     #ffc54b соседа, иначе 42–48 и 48–54 сливаются в одну полосу. */
  --fbs-watch:      #ffe066;
  --fbs-watch-text: #ffe066;
  --red-weak:    rgba(255,107,133,.14);

  --fbs-bad-bg:   rgba(255,128,152,.10);
  --fbs-soon-bg:  rgba(255,197,75,.10);
  --fbs-watch-bg: rgba(255,224,102,.09);

  --shadow-sm: 0 1px 2px rgba(0,0,0,.24);
  --shadow:    0 2px 4px rgba(0,0,0,.20), 0 6px 16px rgba(0,0,0,.28);
  --shadow-lg: 0 4px 10px rgba(0,0,0,.24), 0 16px 40px rgba(0,0,0,.40);
  --shadow-accent: 0 4px 16px rgba(23,184,113,.30);

  --glass-bg:     rgba(21,24,40,.66);
  --glass-border: rgba(255,255,255,.09);
  --glass-hi:     rgba(255,255,255,.07);
  --aurora-opacity: 1;
  --aurora-1: rgba(23,184,113,.20);
  --aurora-2: rgba(108,99,255,.18);
  --aurora-3: rgba(192,99,255,.12);
  --aurora-4: rgba(255,101,132,.10);

  --cat-ads:      #ff6584;
  --cat-supply:   #ffc54b;
  --cat-analytics:#4ade9b;
  --cat-content:  #6cc7ff;
  --cat-other:    #8f97c9;
}

/* Уважаем системную настройку «уменьшить движение» (Apple HIG, WCAG) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ═══ АНИМАЦИИ ═══════════════════════════════════════════════════════════════
   Три правила, чтобы движение помогало, а не раздражало:
   1) анимируем только transform и opacity — их считает видеокарта;
   2) появление быстрое (.25–.4с), уход ещё быстрее — ожидание бесит сильнее;
   3) всё отключается системной настройкой «уменьшить движение» (в конце файла).
   ═══════════════════════════════════════════════════════════════════════════ */

/* Появление карточек «лесенкой»: взгляд ведут сверху вниз, а не вываливают
   всё разом. Задержка растёт по номеру, дальше 8-й не ждём — иначе последние
   карточки появлялись бы через секунду. */
@keyframes rise {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to   { opacity: 1; transform: none; }
}
.cabinets-grid > *, .card, .mon-tile {
  animation: rise .38s var(--ease-out) both;
}
.cabinets-grid > *:nth-child(1) { animation-delay: .02s }
.cabinets-grid > *:nth-child(2) { animation-delay: .06s }
.cabinets-grid > *:nth-child(3) { animation-delay: .10s }
.cabinets-grid > *:nth-child(4) { animation-delay: .14s }
.cabinets-grid > *:nth-child(5) { animation-delay: .18s }
.cabinets-grid > *:nth-child(n+6) { animation-delay: .22s }

/* Переключение вкладок: контент «въезжает», а не подменяется рывком */
.tab-content.active { animation: rise .3s var(--ease-out) both; }

/* Мягкая пульсация для живых индикаторов (лента, статус «онлайн») */
@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .55; transform: scale(.86); }
}
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent-bright, var(--accent));
  animation: pulse-dot 2s var(--ease) infinite;
}

/* Скелет загрузки вместо голого «Загрузка...»: блик пробегает по плашке и
   показывает, что приложение живо, а не зависло. */
@keyframes shimmer { from { background-position: -160% 0 } to { background-position: 260% 0 } }
.skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface3) 25%, var(--surface-hover) 45%, var(--surface3) 65%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
  color: transparent !important;
}

/* Цифры, которые изменились, коротко подсвечиваются — иначе обновление
   в реальном времени проходит незамеченным. */
@keyframes flash-value {
  0%   { color: var(--accent-bright, var(--accent)); transform: scale(1.04); }
  100% { color: inherit; transform: none; }
}
.value-changed { animation: flash-value .9s var(--ease-out); display: inline-block; }

/* Кнопки: лёгкий подъём вместо резкой смены цвета */
.btn-primary, .btn-ai { will-change: transform; }

/* Видимое кольцо фокуса — обязательное требование доступности.
   :focus-visible, а не :focus — мышью кольца не будет, с клавиатуры будет. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

body {
  font-family: 'Golos Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* Цифры одной ширины по всему интерфейсу: без этого колонки в таблицах
     дёргаются при каждом обновлении — «1 111» уже «1 985». */
  font-variant-numeric: tabular-nums;
  background: var(--bg);
  background-attachment: fixed;
  color: var(--text);
  /* Было 16px/600 — ВЕСЬ интерфейс полужирным. Именно это делало его «тяжёлым»:
     когда жирное всё, иерархии нет и выделить главное нечем. Текст 400,
     вес работает на смысл. */
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  /* Golos Text шире Inter, ему просторнее строка идёт на пользу */
  line-height: 1.55;
  letter-spacing: 0;
  min-height: 100vh;
  display: flex;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Цифры моноширинной ширины — в финансах критично: колонки сумм не «прыгают»
   при обновлении, 111 и 999 занимают одинаковое место. */
body, input, button, table, .kpi-value, .cab-name {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'cv05' 1;
}

/* Заголовки: чем крупнее кегль, тем плотнее трекинг — иначе на больших
   размерах буквы «разъезжаются» (правило оптической компенсации). */
h1, h2, h3, .section-title, .kpi-value, .cab-name {
  letter-spacing: -.02em;
  line-height: 1.25;
}

/* ═══ ФОН: медленно дрейфующие световые пятна ═════════════════════════════════
   Живой фон под контентом. Держится на transform/opacity — их считает видеокарта,
   перерисовки страницы не вызывают. Слой неинтерактивный и лежит ПОД всем.
   По правилам HIG это content layer, поэтому стекло сюда не идёт — только свет. */
body::before {
  content: '';
  position: fixed; inset: -20%;
  z-index: -2; pointer-events: none;
  /* Цвета пятен свои у каждой темы: фиолетовые из тёмной на белом фоне просто
     выцветают в серую муть. В светлой берём насыщенные, но с малой альфой. */
  background:
    radial-gradient(38% 42% at 18% 22%, var(--aurora-1), transparent 62%),
    radial-gradient(34% 38% at 82% 18%, var(--aurora-2), transparent 60%),
    radial-gradient(42% 46% at 70% 82%, var(--aurora-3), transparent 64%),
    radial-gradient(30% 34% at 25% 78%, var(--aurora-4), transparent 60%);
  filter: blur(28px);
  opacity: var(--aurora-opacity);
  animation: aurora 42s var(--ease) infinite alternate;
  will-change: transform;
}
/* Зернистость поверх градиентов — убирает полосатость (banding) на больших
   растяжках цвета и добавляет «плёночную» текстуру вместо пластиковой заливки. */
body::after {
  content: '';
  position: fixed; inset: 0;
  z-index: -1; pointer-events: none; opacity: .07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.45'/%3E%3C/svg%3E");
}
@keyframes aurora {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(2.5%, -2%, 0) scale(1.06); }
  100% { transform: translate3d(-2%, 2.5%, 0) scale(1.03); }
}
/* Системная настройка «уменьшить движение» — фон замирает, но остаётся */
@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
}

/* ═══ СТЕКЛО ══════════════════════════════════════════════════════════════════
   ТОЛЬКО для функционального слоя: меню, верхняя панель, модальные окна.
   Правило HIG: в слое контента стекло не используют — карточки на стекле
   ломают иерархию, потому что перестают читаться как отдельные объекты.
   `--glass-hi` — тонкий блик по верхней кромке: та самая «линза», ради которой
   поверхность выглядит стеклянной, а не просто полупрозрачной. */
.glass {
  background: var(--glass-bg);
  backdrop-filter: saturate(180%) blur(22px);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--glass-hi);
}
/* Запасной вариант для браузеров без backdrop-filter: просто плотный фон,
   иначе на них поверх контента оказался бы прозрачный прямоугольник. */
@supports not (backdrop-filter: blur(2px)) {
  .glass { background: var(--surface); }
}

/* ── Диалоги ───────────────────────────────────────────────────────────────
   Модалки НЕ стеклянные, и это осознанно. `.glass` пропускает 28% фона, а за
   ним ходит аврора — из-за неё панель окрашивалась в зелёный и цвет «плыл»
   вместе с анимацией. В диалоге читают и вводят данные, поэтому фон должен
   быть один и тот же в любую секунду. Размытие осталось на подложке позади
   окна — глубина есть, а цвет не гуляет.

   Рамка тоже своя: у `.glass` она белая (rgba(255,255,255,.85)) — на светлой
   теме такая рамка просто исчезает, и внешний край окна выглядел сдвинутым
   относительно внутренних рамок, нарисованных обычным --border. */
.modal-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 24px 70px rgba(15, 23, 42, .28);
}
[data-theme="dark"] .modal-panel,
:root:not([data-theme="light"]) .modal-panel {
  box-shadow: 0 24px 70px rgba(0, 0, 0, .55);
}

/* Таблица внутри рамки-контейнера: у последней строки убираем нижнюю границу,
   иначе она сдваивается с рамкой контейнера и даёт «жирную» полосу. */
.table-framed {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: auto;
}
.table-framed .stock-table tr:last-child td { border-bottom: none; }
/* На узком окне колонки сжимались до нечитаемого: номер артикула обрезался
   на середине, и понять, какой это товар, было нельзя. Лучше горизонтальная
   прокрутка (рамка её и так допускает), чем обрезанный идентификатор. */
.table-framed .stock-table { min-width: 460px; }
/* Шапка не уезжает при прокрутке длинного списка */
.table-framed .stock-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface3);
}

::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--surface3); border-radius: 99px; }

/* ── Sidebar ────────────────────────────────────────────────────────────────── */
/* --- Свёрнутое меню -------------------------------------------------------
   По умолчанию видны только значки, подписи появляются при наведении.
   Меню НАКРЫВАЕТ контент, а не раздвигает его: оно и так position:fixed, а
   отступ у .main остаётся по узкой полосе. Иначе каждое движение мыши мимо
   левого края перекладывало бы всю страницу — самое раздражающее, что может
   делать интерфейс.
   Раскрытие работает и по фокусу с клавиатуры (:focus-within), иначе меню
   было бы недоступно без мыши. */
.sidebar {
  width: var(--sidebar-rail);
  min-height: 100vh;
  overflow: hidden;
  /* Крупные поверхности делают плотнее — иначе текст меню теряется на пёстром
     фоне (в HIG: чем больше элемент, тем менее он прозрачен). */
  background: var(--glass-bg);
  backdrop-filter: saturate(180%) blur(24px);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: fixed;
  left: 0; top: 0;
  z-index: 100;
  padding: 24px 14px 20px;
  transition: width .28s cubic-bezier(.32, .72, 0, 1),
              box-shadow .28s var(--ease);
}

/* Раскрываем только там, где есть настоящий курсор. На тач-экране «наведения»
   не бывает: меню осталось бы свёрнутым навсегда, и по значкам пришлось бы
   гадать. */
@media (hover: hover) and (pointer: fine) {
  .sidebar:hover, .sidebar:focus-within {
    width: var(--sidebar-w);
    box-shadow: 0 8px 40px rgba(0, 0, 0, .13);
  }
}
/* Планшет и всё, где нет курсора, но экран широкий: меню открыто постоянно.
   Наведения там нет, а места хватает. */
@media not all and (hover: hover) {
  @media (min-width: 769px) {
    .sidebar { width: var(--sidebar-w); }
    .main { margin-left: var(--sidebar-w); }
  }
}

/* Подписи прячем прозрачностью и сдвигом, а не display:none — исчезающий
   элемент анимировать нечем. nowrap обязателен: пока полоса узкая, текст
   иначе переносится и дёргает высоту кнопок при каждом раскрытии. */
.nav-text {
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity .18s var(--ease), transform .22s cubic-bezier(.32, .72, 0, 1);
}
.sidebar:hover .nav-text,
.sidebar:focus-within .nav-text { opacity: 1; transform: none; }

/* Лёгкая лесенка: подписи проявляются одна за другой сверху вниз. Разница в
   десятки миллисекунд не читается как задержка, но раскрытие перестаёт быть
   резким скачком. При сворачивании задержек нет — уходить всё должно разом. */
@media (hover: hover) and (pointer: fine) {
  .sidebar:hover .nav-btn:nth-child(2)  .nav-text { transition-delay: .02s; }
  .sidebar:hover .nav-btn:nth-child(3)  .nav-text { transition-delay: .04s; }
  .sidebar:hover .nav-btn:nth-child(4)  .nav-text { transition-delay: .06s; }
  .sidebar:hover .nav-btn:nth-child(5)  .nav-text { transition-delay: .08s; }
  .sidebar:hover .nav-btn:nth-child(6)  .nav-text { transition-delay: .10s; }
  .sidebar:hover .nav-btn:nth-child(7)  .nav-text { transition-delay: .12s; }
  .sidebar:hover .nav-btn:nth-child(8)  .nav-text { transition-delay: .14s; }
  .sidebar:hover .nav-btn:nth-child(9)  .nav-text { transition-delay: .16s; }
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 5px;
  margin-bottom: 28px;
}

.logo-icon {
  width: 34px; height: 34px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; flex-shrink: 0;
}

/* Знак марки в плитке. Белый: плитка залита акцентом, и собственный зелёный
   логотипа на ней не виден. Внутренние отступы — чтобы знак не упирался в
   скруглённые углы. */
.logo-icon img { width: 20px; height: auto; display: block; filter: brightness(0) invert(1); }

/* Градиент шёл от #fff — на светлой теме логотип становился белым по белому.
   Берём цвет текста темы, поэтому работает в обеих. */
.logo-text {
  font-size: var(--fs-title3); font-weight: var(--fw-bold); letter-spacing: -.02em;
  background: linear-gradient(90deg, var(--text) 35%, var(--accent));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* Логотип вместо набранного названия. Ширину не фиксируем: в разметке стоят
   родные размеры файла, и без width:auto он растянется на всю панель.
   Файл красит себя зелёным #0b7a52 — это цвет светлой темы. На тёмной он
   тонет в фоне, поэтому там подсветляем фильтром до уровня тёмного акцента:
   перекрашивать картинку переменной нельзя, она подключена как <img>. */
.logo-img { height: 28px; width: auto; display: block; }
[data-theme="dark"] .logo-img { filter: brightness(1.55) saturate(1.05); }

.sidebar-label {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi); letter-spacing: 1.2px;
  color: var(--text-muted); text-transform: uppercase;
  padding: 0 6px; margin-bottom: 6px;
}

.sidebar nav { display: flex; flex-direction: column; gap: 3px; }

/* --- Разделы меню ---------------------------------------------------------
   «Сервисы» — то, чем пользуются каждый день. «Система» — настройки, куда
   заходят раз в месяц. Разделены линией, чтобы взгляд не искал настройки
   среди рабочих разделов. */
.nav-group + .nav-group {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--border);
}

.nav-group-label {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi); letter-spacing: 1.2px;
  color: var(--text-muted); text-transform: uppercase;
  padding: 0 8px; margin-bottom: 18px;
  height: 14px;
  overflow: hidden;
  /* Свёрнутое меню — это полоса значков, и подписям групп в ней делать нечего.
     Раньше место под них резервировалось, чтобы значки не прыгали. Но прыжок
     плох не сам по себе, а тем, что он мгновенный: если ту же перестановку
     проиграть за четверть секунды, она читается как раскрытие, а не как сбой.
     Поэтому высота и отступ анимируются вместе с шириной полосы. */
  transition: height .26s cubic-bezier(.32, .72, 0, 1),
              margin-bottom .26s cubic-bezier(.32, .72, 0, 1);
}
/* В свёрнутом виде подпись раздела не нужна — линия и так разделяет. Место
   под неё оставляем, чтобы значки не подскакивали при каждом раскрытии. */
.nav-group-label span {
  display: inline-block;
  opacity: 0; transform: translateX(-8px);
  transition: opacity .18s var(--ease), transform .22s cubic-bezier(.32, .72, 0, 1);
}
.sidebar:hover .nav-group-label span,
.sidebar:focus-within .nav-group-label span { opacity: 1; transform: none; }

.nav-btn {
  /* Иконка прижата левее, а зазор до подписи меньше: каждый пиксель отсюда
     достаётся тексту, ради которого пункт и находят. В свёрнутом виде это не
     мешает — там подпись спрятана, а иконка центруется своим правилом ниже. */
  display: flex; align-items: center; gap: 9px;
  padding: 9px 8px; border-radius: var(--radius-sm);
  border: none; background: none; color: var(--text-2);
  cursor: pointer; font-size: var(--fs-callout); font-weight: var(--fw-semi);
  letter-spacing: -.01em;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  text-align: left; width: 100%; position: relative;
}
/* Контурные иконки меню: цвет берут от текста кнопки, поэтому подсвечиваются
   вместе с активным пунктом и одинаково работают в обеих темах. */
.nav-btn .nav-icon {
  width: 20px; height: 20px; flex-shrink: 0;
  fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  /* Неактивные пункты чуть светлее подписи — иконка не должна перетягивать
     внимание с текста, по которому пункт и находят. */
  opacity: .78;
  transition: opacity var(--t-fast) var(--ease),
              transform .25s cubic-bezier(.34, 1.56, .64, 1);
}
/* Значок — единственное, что видно в свёрнутом меню, поэтому отклик на
   наведение нужен именно ему. Лёгкий подъём, без вращений и подпрыгиваний:
   меню открывают десятки раз в день, и цирк тут надоедает на второй. */
.nav-btn:hover .nav-icon, .nav-btn.active .nav-icon { opacity: 1; }
.nav-btn:hover .nav-icon { transform: translateY(-1px) scale(1.12); }
.nav-btn.active .nav-icon { transform: scale(1.06); }
.nav-btn:active .nav-icon { transform: scale(.96); transition-duration: .08s; }
/*
 * ⚠️ ПОДСВЕТКА ТА ЖЕ, ЧТО У УВЕДОМЛЕНИЙ, И ЭТО НЕ СОВПАДЕНИЕ.
 *
 * И там, и здесь наведение отвечает на один вопрос: «эта строка откликнется
 * на нажатие». Раньше в меню была серая подложка, а в панели уведомлений —
 * зелёная с полоской: два разных языка для одного смысла. Человек их не
 * формулирует, но чувствует как небрежность.
 *
 * Полоска слева — та же, что у активного пункта, только тоньше по смыслу:
 * у активного она сплошная и остаётся, у наведённого появляется и уходит.
 * Одна форма, два состояния — вместо двух разных форм.
 */
.nav-btn:hover { background: var(--accent-weak); color: var(--text); }

/* Полоска наведения. У активного пункта своя (см. .nav-btn.active::before),
   и перебивать её не нужно — поэтому только для неактивных. */
.nav-btn:not(.active)::before {
  content: ''; position: absolute; left: -14px; top: 50%;
  width: 3px; height: 18px; border-radius: 0 3px 3px 0;
  background: var(--accent);
  /* Растёт из середины: движение вдоль пункта, а не поперёк — поперечное
     дёргает взгляд сильнее, чем помогает. */
  transform: translateY(-50%) scaleY(0);
  opacity: 0;
  transition: transform .18s var(--ease), opacity .18s var(--ease);
}
.nav-btn:not(.active):hover::before,
.nav-btn:not(.active):focus-visible::before {
  transform: translateY(-50%) scaleY(1); opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  /* Движение убрано, различимость осталась: полоска просто появляется. */
  .nav-btn:not(.active)::before {
    transition: opacity .18s var(--ease);
    transform: translateY(-50%) scaleY(1);
  }
}
.nav-btn.active {
  background: var(--accent-weak);
  /* Активный пункт тяжелее остальных: это якорь, по нему понимают,
     в каком разделе находятся, не вчитываясь. */
  color: var(--text); font-weight: var(--fw-bold);
}
/* Индикатор активного пункта — полоска слева. Раньше была рамка в 1px, из-за
   неё пункт при активации смещался на пиксель. */
.nav-btn.active::before {
  content: ''; position: absolute; left: -14px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 18px; border-radius: 0 3px 3px 0; background: var(--accent);
}

.sidebar-footer {
  margin-top: auto; padding: 10px 6px;
  border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-muted); line-height: 1.5;
  white-space: nowrap; overflow: hidden;
}

/* ── Main ───────────────────────────────────────────────────────────────────── */
/* Отступ — по СВЁРНУТОЙ полосе: раскрытое меню накрывает контент сверху и не
   двигает его. Иначе страница перекладывалась бы на каждое движение мыши. */
/* min-width: 0 обязателен: у flex-элемента минимальная ширина по умолчанию
   равна содержимому, и одна широкая таблица внутри распирала всю страницу
   вместо того, чтобы прокручиваться у себя в контейнере. */
.main { margin-left: var(--sidebar-rail); flex: 1; min-width: 0; min-height: 100vh; display: flex; flex-direction: column; }

.topbar {
  height: 56px;
  /* Полупрозрачность + размытие: контент «просвечивает» при скролле, слой
     читается как плавающий, а не приклеенный (материалы в HIG). */
  background: var(--glass-bg);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; padding: 0 var(--sp-6); gap: var(--sp-3);
  position: sticky; top: 0; z-index: 50;
}
.topbar-title { font-size: var(--fs-title3); font-weight: var(--fw-semi); flex: 1; letter-spacing: -.02em; }
.topbar-subtitle { font-size: var(--fs-footnote); color: var(--text-2); }
.content { padding: var(--sp-6); }

/* ── Cards ──────────────────────────────────────────────────────────────────── */
.card {
  /* Полупрозрачность без размытия: фоновые пятна мягко просвечивают, но карточка
     остаётся плоским объектом слоя контента. Стекло здесь запрещено правилом. */
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-5);
  box-shadow: var(--shadow-sm);
}

/* ── Buttons ────────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 8px var(--sp-4); border-radius: var(--radius-sm);
  /*
   * ⚠️ ПРОЗРАЧНАЯ РАМКА, А НЕ `border: none`, И ЭТО НЕ ПРИДИРКА.
   *
   * У .btn-ghost, .btn-danger и .btn-success рамка есть, у .btn-primary и
   * .btn-ai её не было. При одинаковых отступах это давало разную высоту:
   * замерено — primary 36 пикселей, ghost 37. Пара «Сохранить / Отмена»
   * стояла на разном уровне, и это видно глазом как неаккуратность, причину
   * которой не найти: классы разные, а разницы в стилях будто нет.
   *
   * Прозрачная рамка выравнивает коробку у всех вариантов. Отступ снижен с
   * 9 до 8 пикселей, чтобы общая высота осталась прежней: 8 + 8 + 18 + 2 = 36.
   */
  border: 1px solid transparent; cursor: pointer;
  font-size: var(--fs-body); font-weight: var(--fw-medium);
  font-family: inherit; letter-spacing: -.01em;
  /* Переходим только по нужным свойствам: transition:all анимирует и размеры,
     из-за чего кнопка «плывёт» при смене текста. */
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-base) var(--ease),
              transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
  white-space: nowrap; min-height: 36px;
  /*
   * ⚠️ ВЫСОТА СТРОКИ ЗАДАНА ЯВНО, ИНАЧЕ ЕЁ ЗАДАЁТ СОДЕРЖИМОЕ.
   *
   * При line-height: normal высоту строки выбирает шрифт, которым нарисован
   * текст. Стрелка «→», знак валюты или эмодзи берутся из ЗАПАСНОГО шрифта —
   * у него свои метрики, и строка выходит выше. Замерено: кнопка со стрелкой
   * была 37 пикселей там, где соседняя с обычным словом — 36.
   *
   * Отладить такое почти невозможно: в стилях у кнопок нет ни одного
   * различия, они отличаются только буквами внутри.
   */
  line-height: 18px;
}
/* Нажатие должно быть заметным — иначе непонятно, сработало ли (press-feedback) */
.btn:active:not(:disabled) { transform: scale(.97); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary {
  background: linear-gradient(135deg, var(--accent-fill-a), var(--accent-fill-b));
  color: var(--accent-ink); box-shadow: var(--shadow-accent);
}
.btn-primary:hover:not(:disabled) { box-shadow: 0 6px 22px rgba(var(--accent-rgb), .42); transform: translateY(-1px); }
.btn-ghost { background: var(--surface2); color: var(--text); border: 1px solid var(--border); }
.btn-ghost:hover:not(:disabled) { background: var(--surface-hover); border-color: var(--border-strong); }
.btn-danger { background: var(--red-weak); color: var(--red); border: 1px solid rgba(255,107,133,.22); }
.btn-danger:hover:not(:disabled) { background: rgba(255,107,133,.20); }
.btn-success { background: var(--green-weak); color: var(--green); border: 1px solid rgba(67,233,123,.22); }
.btn-ai { background: linear-gradient(135deg, #6d4bd8, #5b3fbf); color: #fff; box-shadow: 0 4px 16px rgba(109,75,216,.30); }
.btn-ai:hover { box-shadow: 0 6px 22px rgba(109,75,216,.42); transform: translateY(-1px); }
/* Размер цели нажатия: минимум 32×32 (веб-минимум WCAG 24, берём с запасом) */
/*
 * ⚠️ КНОПКА В ОДНОМ РЯДУ С ПОЛЯМИ ФОРМЫ.
 *
 * Поля ввода и списки высотой 38 пикселей (см. базовый сброс форм), кнопки —
 * 36 и 32. Стоящая рядом кнопка «Сохранить» оказывалась ниже поля, и ряд
 * выглядел сбитым.
 *
 * Раньше это лечили на месте: `style="height:38px"` прямо в разметке. Такое
 * правило не видно в стилях и сильнее любого класса — одна кнопка тихо
 * выпадает из общего вида, и найти её можно только глазами. Здесь оно названо
 * и лежит там же, где остальные: сторож в tools/test-buttons.js ловит возврат
 * встроенных размеров.
 */
.btn-field { min-height: 38px; height: 38px; }

/* Та же причина, что у .btn выше: высоту строки задаём мы, а не глиф. */
.btn-sm { padding: 5px var(--sp-3); font-size: var(--fs-footnote); min-height: 32px; line-height: 16px; }
.btn-icon { padding: 6px; width: 34px; height: 34px; min-height: 34px; justify-content: center; }
/*
 * ⚠️ ЗНАЧКОВАЯ КНОПКА РЯДОМ С ОБЫЧНОЙ ДОЛЖНА БЫТЬ ТОЙ ЖЕ ВЫСОТЫ.
 *
 * .btn-icon задаёт жёсткие 34×34, а .btn-sm — 32. В панелях они стоят бок о
 * бок (обновить, карандаш, удалить рядом с «Прочитать всё», поиском и
 * списками), и значковые торчали на два пикселя выше соседей. Ряд читался
 * как чуть сбитый, хотя каждая кнопка по отдельности выглядит правильно.
 */
.btn-icon.btn-sm { width: 32px; height: 32px; min-height: 32px; }

/* ── Forms ──────────────────────────────────────────────────────────────────── */
.form-group { display: flex; flex-direction: column; gap: 5px; }
label { font-size: var(--fs-footnote); font-weight: var(--fw-medium); color: var(--text-2); }
/*
 * ⚠️ ФОРМЕННЫЕ ЭЛЕМЕНТЫ НЕ НАСЛЕДУЮТ ШРИФТ САМИ — И ЭТО НЕ ОЧЕВИДНО.
 *
 * `button`, `input`, `select`, `textarea` браузер рисует СВОИМ шрифтом
 * (Arial/системный), что бы ни стояло у `body`. Наследование, которое
 * работает для любого `div` и `span`, на них не распространяется — так
 * устроен HTML.
 *
 * Из-за этого на странице оказалось 36 кнопок и 16 полей в Arial посреди
 * Golos Text: меню, колокольчик, переключатели, кнопки в карточках. Отдельные
 * классы вроде `.btn` шрифт себе прописывали, а всё остальное — нет, и
 * выглядело это как «шрифт куда-то делся».
 *
 * Правило намеренно на голых тегах, без классов: это ПОЛ, а не потолок.
 * Любое правило с классом его перебьёт, если где-то понадобится другой шрифт.
 * Размеры и веса не трогаем — они заданы в своих местах, и шорткат `font:
 * inherit` снёс бы их заодно.
 */
button, input, select, textarea, optgroup {
  font-family: inherit;
}

input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text); font-size: var(--fs-callout);
  font-family: inherit; font-weight: var(--fw-regular);
  padding: 9px var(--sp-3); outline: none; width: 100%; min-height: 38px;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
input:not([type="checkbox"]):not([type="radio"]):focus, select:focus, textarea:focus {
  border-color: rgba(var(--accent-rgb), .55); box-shadow: 0 0 0 3px var(--accent-weak);
}
/* Плейсхолдер тусклее подписи — иначе пустое поле читается как заполненное */
input::placeholder, textarea::placeholder { color: var(--text-3); }
select option { background: var(--surface2); }

/* ── Badges ─────────────────────────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 3px var(--sp-2); border-radius: var(--radius-full);
  font-size: var(--fs-caption); font-weight: var(--fw-medium);
}
.badge::before { content:''; width:5px; height:5px; border-radius:50%; background:currentColor; }
.badge-pending { background: var(--yellow-weak); color: var(--yellow); }
.badge-running { background: var(--accent-weak); color: var(--accent-text); }
.badge-done    { background: var(--green-weak); color: var(--green); }
.badge-error   { background: var(--red-weak); color: var(--red); }
.badge-critical { background: var(--red-weak); color: var(--red); }
.badge-warning  { background: var(--yellow-weak); color: var(--yellow); }
.badge-info     { background: var(--accent-weak); color: var(--accent-text); }

/* Task category dots */
.cat-dot {
  width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0;
}
.cat-dot-ads      { background: var(--cat-ads); }
.cat-dot-supply   { background: var(--cat-supply); }
.cat-dot-analytics{ background: var(--cat-analytics); }
.cat-dot-content  { background: var(--cat-content); }
.cat-dot-other    { background: var(--cat-other); }

/* ── Tabs ───────────────────────────────────────────────────────────────────── */
.tab-content { display: none; }
.tab-content.active { display: block; }

/* ── Section header ─────────────────────────────────────────────────────────── */
.section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
.section-title { font-size: var(--fs-title3); font-weight: var(--fw-semi); letter-spacing: -.02em; }

/* ═══════════════════════════════════════════════════════════════════════════════
   КАБИНЕТЫ
═══════════════════════════════════════════════════════════════════════════════ */
.cabinets-grid {
  /* Карточки крупнее: в них живёт график заказов, на 320px он был тесным.
     min(420px, 100%), а не просто 420px: на экране уже 420 пикселей колонка
     всё равно оставалась 420-й, и вся страница — вместе с шапкой и меню —
     уезжала вправо на полсотни пикселей. На телефоне карточка занимает
     столько, сколько есть. */
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr));
}

/* Чипы-кнопки в карточке кабинета: задачи, триггеры, себестоимость.
   Раньше это были неинтерактивные бейджи — теперь ведут туда, где с этим работают. */
.chip-btn {
  font-size: var(--fs-caption); padding: 4px var(--sp-3);
  border-radius: var(--radius-full); cursor: pointer;
  background: var(--surface3); border: 1px solid var(--border);
  color: var(--text-2); font-family: inherit; font-weight: var(--fw-medium);
  min-height: 26px; letter-spacing: -.01em;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.chip-btn:active { transform: scale(.96); }
.chip-btn:hover { border-color: rgba(var(--accent-rgb), .45); color: var(--text); background: rgba(var(--accent-rgb), .10); }
.chip-btn.chip-done    { color: var(--green);  border-color: rgba(67,233,123,.25);  background: var(--green-weak); }
.chip-btn.chip-pending { color: var(--yellow); border-color: rgba(255,197,75,.25);  background: var(--yellow-weak); }
.chip-btn.chip-warn    { color: var(--red);    border-color: rgba(255,107,133,.25); background: var(--red-weak); }

/* Свой тултип для свечей: нативный <title> всплывает через секунду с лишним,
   а по графику водят курсором быстро — нужно мгновенно. */
.spark-tip {
  position: fixed; z-index: 900; pointer-events: none; display: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 9px; font-size: 11px; line-height: 1.45; white-space: nowrap;
  box-shadow: 0 6px 20px rgba(0,0,0,.45);
}

.cabinet-card {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--sp-5);
  display: flex; align-items: center; gap: var(--sp-4);
  /* Карточка кликабельна (открывает настройки) — курсор должен об этом говорить */
  cursor: pointer; min-width: 0;
  box-shadow: var(--shadow-sm);
  transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out),
              box-shadow var(--t-base) var(--ease), background var(--t-base) var(--ease);
}
.cabinet-card:hover {
  border-color: var(--border-strong); background: var(--surface3);
  transform: translateY(-2px); box-shadow: var(--shadow);
}
.cabinet-card:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
.cab-icon {
  width: 46px; height: 46px; border-radius: 12px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; flex-shrink: 0;
}
.cab-name {
  font-weight: var(--fw-semi); font-size: var(--fs-title3); margin-bottom: 3px;
  letter-spacing: -.02em;
  /*
   * ⚠️ overflow-wrap, А НЕ word-break: break-word.
   *
   * word-break разрешал рвать слово в любом месте, как только строка не
   * помещалась целиком, — и «Одежда» превращалось в «Одеж / да». Пока прибыль
   * стояла в этой же строке, места было полторы сотни пикселей, и рвалось
   * почти каждое название.
   *
   * overflow-wrap: break-word рвёт слово ТОЛЬКО если оно само по себе шире
   * строки: длинное имя без пробелов не вылезет за карточку, а обычное
   * перенесётся по пробелам, как читают люди.
   */
  overflow-wrap: break-word;
}
.cab-sub  { font-size: var(--fs-footnote); color: var(--text-2); }

/* Insights mini on cabinet card */
.cab-insights { display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap; }
.cab-insight-dot {
  width: 8px; height: 8px; border-radius: 50%;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ПЛАНЕР — ВИДЫ
═══════════════════════════════════════════════════════════════════════════════ */
.planner-toolbar {
  display: flex; align-items: center; gap: 10px; margin-bottom: 16px; flex-wrap: wrap;
}
.planner-toolbar .week-label, .planner-toolbar .month-label {
  font-size: 15px; font-weight: 600; flex: 1; text-align: center;
}
.view-toggle {
  display: flex; gap: 2px; background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 3px;
}
.view-toggle button {
  padding: 5px 12px; border-radius: 6px; border: none; background: none;
  color: var(--text-muted); cursor: pointer; font-size: 12px; font-weight: 500; transition: all .15s;
  /*
   * ⚠️ ВЫСОТА ЗАДАЁТСЯ ЗДЕСЬ, А НЕ СОДЕРЖИМЫМ КНОПКИ.
   *
   * Замерено: «Сегодня / Месяц» — 25 пикселей, а «По времени / По условию» в
   * напоминаниях — 40. Класс один и тот же, отступы одни и те же; разница
   * только в том, что во втором случае внутри кнопки есть значок, и он
   * растягивает строку. Один переключатель выглядел двумя разными, и понять
   * почему по стилям было невозможно.
   *
   * Та же причина, что у .btn: при line-height: normal высоту строки выбирает
   * содержимое — шрифт текста или размер картинки внутри.
   */
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  line-height: 16px; white-space: nowrap;
}
/* Значок внутри переключателя — по размеру строки, а не по своему исходному:
   иначе он и задаёт высоту кнопки. */
.view-toggle button .ic { width: 14px; height: 14px; flex: none; }
.view-toggle button.active { background: var(--accent-fill-a); color: var(--accent-ink); }

/* ── Неделя ─────────────────────────────────────────────────────────────────── */
/* Панель задач занимает правую колонку только когда открыта */
.planner-layout { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
.planner-layout.panel-open { grid-template-columns: 1fr 340px; }
@media (max-width: 1100px) { .planner-layout.panel-open { grid-template-columns: 1fr; } }

/* Доска недели: колонка = кабинет, внутри — задачи недели */
.cab-board { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); gap: 10px; align-items: start; }
.cab-board-col {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; transition: border-color .15s;
}
.cab-board-col.active-col { border-color: rgba(var(--accent-rgb), .5); }
.cab-board-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
  font-weight: 600; cursor: pointer; transition: background .13s;
}
.cab-board-head:hover { background: rgba(var(--accent-rgb), .10); }
.cab-board-tasks { padding: 8px; margin: 0; max-height: none; }
.board-empty { color: var(--text-muted); font-size: 12px; text-align: center; padding: 10px 0; list-style: none; }
.board-add { margin: 0 8px 10px; width: calc(100% - 16px); }

.day-col {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; min-height: 160px;
  transition: border-color .15s;
}
.day-col.today { border-color: rgba(var(--accent-rgb), .5); }
.day-header { padding: 9px 10px; border-bottom: 1px solid var(--border); text-align: center; }
.day-name { font-size: 12px; font-weight: 700; letter-spacing: .8px; color: var(--text-muted); text-transform: uppercase; }
.day-date { font-size: 19px; font-weight: 700; line-height: 1.2; margin-top: 1px; }
.day-col.today .day-date { color: var(--accent); }
.day-body { padding: 6px; display: flex; flex-direction: column; gap: 4px; }

.cab-chip {
  padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border);
  cursor: pointer; transition: all .13s;
  display: flex; align-items: center; justify-content: space-between; gap: 4px;
}
.cab-chip:hover { background: rgba(var(--accent-rgb), .12); border-color: rgba(var(--accent-rgb), .35); }
.cab-chip.active-chip { background: rgba(var(--accent-rgb), .18); border-color: rgba(var(--accent-rgb), .5); }
.cab-chip-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
.cab-chip-count { background: var(--accent-fill-a); color: var(--accent-ink); border-radius: 99px; font-size: 11px; font-weight: 700; padding: 2px 6px; flex-shrink: 0; }
.cab-chip-count.all-done { background: var(--accent3); }

/* Cat dots row */
.chip-cats { display: flex; gap: 2px; }

/* ── Месяц ──────────────────────────────────────────────────────────────────── */
.month-grid {
  display: grid; grid-template-columns: repeat(7,1fr); gap: 6px;
}
.month-day-names {
  display: grid; grid-template-columns: repeat(7,1fr); gap: 6px;
  margin-bottom: 6px;
}
.month-day-name {
  text-align: center; font-size: 13px; font-weight: 700;
  color: var(--text-muted); padding: 4px 0; text-transform: uppercase; letter-spacing: .6px;
}

.month-cell {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 10px; min-height: 100px; padding: 8px;
  cursor: pointer; transition: border-color .13s, background .13s;
  position: relative; overflow: hidden;
}
.month-cell:hover { border-color: rgba(var(--accent-rgb), .35); background: var(--surface3); }
.month-cell.today { border-color: rgba(var(--accent-rgb), .55); }
.month-cell.today .month-day-num { color: var(--accent); }
.month-cell.other-month { opacity: .35; }
.month-cell.active-month-cell { border-color: rgba(var(--accent-rgb), .6); background: rgba(var(--accent-rgb), .07); }

.month-day-num {
  font-size: 14px; font-weight: 700; margin-bottom: 5px; line-height: 1;
}

.month-task-item {
  display: flex; align-items: center; gap: 4px;
  font-size: 12px; padding: 2px 5px; border-radius: 4px;
  background: var(--surface3); margin-bottom: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
.month-task-item.done-task { opacity: .5; text-decoration: line-through; }
.month-reminder-dot { font-size: 12px; }
.month-more { font-size: 12px; color: var(--text-muted); margin-top: 2px; }

/* ── Панель задач (общая для неделя/месяц) ──────────────────────────────────── */
.tasks-panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; position: sticky; top: 72px;
}
.tasks-panel-header {
  padding: 14px 16px; background: var(--surface2); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
}
.tasks-panel-title { font-size: 16px; font-weight: 700; }
.tasks-panel-sub   { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
.tasks-panel-body  { padding: 12px; }

.task-list { list-style: none; display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; max-height: 380px; overflow-y: auto; }

.task-item {
  display: flex; align-items: center; gap: 8px; padding: 9px 10px;
  border-radius: var(--radius-sm); background: var(--surface2); border: 1px solid var(--border);
  transition: all .12s;
}
.task-item.done { opacity: .5; background: rgba(67,233,123,.04); }
.task-item.done .task-title { text-decoration: line-through; color: var(--text-muted); }
.task-item:hover .task-delete { opacity: 1; }
.task-item.priority-urgent { border-left: 3px solid var(--red); }
.task-item.priority-high   { border-left: 3px solid var(--yellow); }

/* Тег в селекторе обязателен: общее правило `input[type="checkbox"]` весит
   столько же и стоит НИЖЕ по файлу, поэтому выигрывало — галочка задачи
   рисовалась 22-й вместо 20-й, а строка списка подстраивалась под неё. */
input[type="checkbox"].task-checkbox {
  width: 20px; height: 20px; min-width: 20px; min-height: 20px; flex-shrink: 0;
}
.task-title { flex: 1; font-size: 14px; }
.task-delete {
  opacity: 0; background: none; border: none; color: var(--red);
  cursor: pointer; font-size: 14px; padding: 1px 3px; border-radius: 4px; transition: opacity .12s; line-height: 1;
}

.add-task-row { display: flex; gap: 6px; }
.add-task-row input { flex: 1; }

.add-task-cats { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }
.cat-btn {
  padding: 4px 11px; border-radius: 99px; border: 1px solid; cursor: pointer;
  font-size: 13px; font-weight: 600; background: none; transition: all .12s;
}
.cat-btn.active-cat { color: #fff !important; }
.cat-btn-ads      { border-color: var(--cat-ads);      color: var(--cat-ads); }
.cat-btn-supply   { border-color: var(--cat-supply);   color: var(--cat-supply); }
.cat-btn-analytics{ border-color: var(--cat-analytics); color: var(--cat-analytics); }
.cat-btn-content  { border-color: var(--cat-content);  color: var(--cat-content); }
.cat-btn-other    { border-color: var(--cat-other);    color: var(--cat-other); }
.cat-btn-ads.active-cat      { background: var(--cat-ads); }
.cat-btn-supply.active-cat   { background: var(--cat-supply); }
.cat-btn-analytics.active-cat{ background: var(--cat-analytics); }
.cat-btn-content.active-cat  { background: var(--cat-content); }
.cat-btn-other.active-cat    { background: var(--cat-other); }

.tasks-empty { text-align: center; padding: 20px 0; color: var(--text-muted); font-size: 15px; }
.tasks-empty-icon { font-size: 28px; margin-bottom: 6px; }

.template-picker { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }
.template-btn {
  padding: 9px 12px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--surface2); color: var(--text); cursor: pointer;
  font-size: 14px; text-align: left; transition: border-color .12s;
}
.template-btn:hover { border-color: rgba(var(--accent-rgb), .4); }

/* ═══════════════════════════════════════════════════════════════════════════════
   ОТЧЁТЫ
═══════════════════════════════════════════════════════════════════════════════ */
.reports-layout { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
@media (max-width: 960px) { .reports-layout { grid-template-columns: 1fr; } }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.form-grid .full { grid-column: 1 / -1; }

.radio-group { display: flex; gap: 6px; margin-top: 4px; }
.radio-opt {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface2); cursor: pointer; transition: all .13s;
  font-size: 15px; font-weight: 600;
}
.radio-opt input { display: none; }
.radio-opt.selected { border-color: rgba(var(--accent-rgb), .5); background: rgba(var(--accent-rgb), .1); color: var(--accent-text); }

.period-presets { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 4px; }
.period-btn {
  padding: 4px 10px; border-radius: 99px; border: 1px solid var(--border);
  background: var(--surface2); color: var(--text-muted); cursor: pointer;
  font-size: 13px; transition: all .12s;
}
.period-btn:hover { border-color: rgba(var(--accent-rgb), .4); color: var(--text); }

.report-list { display: flex; flex-direction: column; gap: 8px; }
.report-card {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px;
  display: flex; flex-direction: column; gap: 7px;
  transition: border-color .13s;
}
.report-card:hover { border-color: rgba(var(--accent-rgb), .25); }
.report-card-header { display: flex; align-items: center; gap: 10px; }
.report-type-icon {
  width: 34px; height: 34px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0;
}
.type-drr  { background: rgba(var(--accent-rgb), .18); }
.type-unit { background: var(--green-weak); }
.report-card-title { font-weight: 700; font-size: 15px; }
.report-card-meta  { font-size: 13px; color: var(--text-muted); }
.report-card-error { font-size: 13px; color: var(--red); padding: 5px 9px; background: rgba(255,77,109,.07); border-radius: 6px; }
.report-card-footer { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ── AI Analysis Panel ───────────────────────────────────────────────────────── */
.analysis-panel {
  background: var(--surface2); border: 1px solid rgba(var(--accent-rgb), .2);
  border-radius: var(--radius); padding: 16px; margin-top: 12px;
}
.analysis-panel h4 { font-size: 15px; font-weight: 700; color: var(--accent-text); margin-bottom: 10px; }
.analysis-text { font-size: 14px; line-height: 1.7; color: var(--text); white-space: pre-wrap; }

/* ═══════════════════════════════════════════════════════════════════════════════
   НАПОМИНАНИЯ
═══════════════════════════════════════════════════════════════════════════════ */
.reminders-layout { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
@media (max-width: 900px) { .reminders-layout { grid-template-columns: 1fr; } }

.reminder-list { display: flex; flex-direction: column; gap: 7px; }
.reminder-card {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 14px;
  display: flex; align-items: flex-start; gap: 10px; transition: border-color .13s;
}
.reminder-card:hover { border-color: rgba(255,197,75,.28); }
.reminder-card.sent { opacity: .45; }
.reminder-bell { font-size: 18px; flex-shrink: 0; margin-top: 1px; }
.reminder-info { flex: 1; min-width: 0; }
.reminder-text { font-size: 15px; font-weight: 600; }
.reminder-time { font-size: 13px; color: var(--text-muted); margin-top: 3px; }
.reminder-badges { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 5px; }

/* iOS picker */
.ios-picker-wrap { margin-top: 12px; }
.ios-picker-label { font-size: 14px; font-weight: 600; color: var(--text-muted); margin-bottom: 7px; }
.ios-picker-row { display: flex; gap: 8px; }
.ios-drum {
  flex: 1; position: relative; height: 160px; overflow: hidden;
  border-radius: 10px; background: var(--surface2); border: 1px solid var(--border);
}
.ios-drum-scroll {
  height: 100%; overflow-y: scroll; scroll-snap-type: y mandatory;
  scrollbar-width: none; padding: 58px 0;
}
.ios-drum-scroll::-webkit-scrollbar { display: none; }
.ios-drum-item {
  height: 44px; scroll-snap-align: center;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 500; color: var(--text-muted);
  cursor: pointer; user-select: none;
}
.ios-drum::before, .ios-drum::after {
  content: ''; position: absolute; left: 0; right: 0; height: calc(50% - 22px); z-index: 2; pointer-events: none;
}
.ios-drum::before { top: 0; background: linear-gradient(to bottom, var(--surface2) 0%, transparent 100%); }
.ios-drum::after  { bottom: 0; background: linear-gradient(to top, var(--surface2) 0%, transparent 100%); }
.ios-drum-highlight {
  position: absolute; left: 8px; right: 8px; top: 50%; transform: translateY(-50%);
  height: 44px; border-radius: 8px; background: rgba(var(--accent-rgb), .16);
  border: 1px solid rgba(var(--accent-rgb), .3); z-index: 1; pointer-events: none;
}
.ios-picker-result {
  margin-top: 8px; padding: 9px 12px; background: rgba(var(--accent-rgb), .09);
  border: 1px solid rgba(var(--accent-rgb), .22); border-radius: 8px;
  font-size: 15px; color: var(--accent-text); font-weight: 600; text-align: center;
}

/* Toggle switch */
.report-link-toggle {
  display: flex; align-items: center; gap: 10px; padding: 11px 13px;
  border-radius: 10px; background: var(--surface2); border: 1px solid var(--border);
  cursor: pointer; transition: border-color .13s; margin-top: 12px;
}
.report-link-toggle:hover { border-color: rgba(var(--accent-rgb), .35); }
.toggle-switch {
  width: 38px; height: 21px; background: var(--surface3); border-radius: 99px;
  position: relative; transition: background .2s; flex-shrink: 0;
}
.toggle-switch::after {
  content: ''; position: absolute; width: 15px; height: 15px; background: #fff;
  border-radius: 50%; top: 3px; left: 3px; transition: transform .2s;
}
.toggle-switch.on { background: var(--accent); }
.toggle-switch.on::after { transform: translateX(17px); }
.toggle-label { font-size: 15px; font-weight: 600; }
.toggle-sub   { font-size: 13px; color: var(--text-muted); }

.report-link-body {
  margin-top: 8px; padding: 13px; background: var(--surface2);
  border: 1px solid rgba(var(--accent-rgb), .18); border-radius: 10px;
  display: none; flex-direction: column; gap: 9px;
}
.report-link-body.visible { display: flex; }

/* ═══════════════════════════════════════════════════════════════════════════════
   AI АССИСТЕНТ
═══════════════════════════════════════════════════════════════════════════════ */
.ai-layout {
  display: grid; grid-template-columns: 1fr 340px; gap: 16px; align-items: start;
  height: calc(100vh - 104px);
}
@media (max-width: 1100px) { .ai-layout { grid-template-columns: 1fr; height: auto; } }

/* Чат */
.ai-chat-wrap {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  height: calc(100vh - 104px);
}
.ai-chat-header {
  padding: 14px 16px; background: var(--surface2); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 10px;
}
.ai-chat-header select { width: auto; flex: 1; }
.ai-chat-msgs {
  flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px;
}

.msg {
  max-width: 80%; display: flex; flex-direction: column; gap: 4px;
}
.msg.user   { align-self: flex-end; align-items: flex-end; }
.msg.assistant { align-self: flex-start; }

.msg-bubble {
  padding: 11px 14px; border-radius: 12px; font-size: 15px; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
}
.msg.user .msg-bubble {
  background: linear-gradient(135deg, var(--accent-fill-a), var(--accent-fill-b)); color: var(--accent-ink); border-radius: 12px 12px 3px 12px;
}
.msg.assistant .msg-bubble {
  background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: 12px 12px 12px 3px;
}
.msg-time { font-size: 12px; color: var(--text-muted); }

.ai-thinking {
  display: flex; align-items: center; gap: 6px; padding: 10px 13px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 12px 12px 12px 3px; width: fit-content;
}
.ai-thinking-dots { display: flex; gap: 3px; }
.ai-thinking-dots span {
  width: 6px; height: 6px; background: var(--accent); border-radius: 50%;
  animation: bounce .8s infinite;
}
.ai-thinking-dots span:nth-child(2) { animation-delay: .15s; }
.ai-thinking-dots span:nth-child(3) { animation-delay: .3s; }
@keyframes bounce { 0%,80%,100% { transform: scale(.6); } 40% { transform: scale(1); } }

.ai-chat-input {
  padding: 12px 14px; background: var(--surface2); border-top: 1px solid var(--border);
  display: flex; gap: 8px; align-items: flex-end;
}
.ai-chat-input textarea {
  flex: 1; resize: none; min-height: 40px; max-height: 120px;
  padding: 9px 12px; font-size: 15px;
}
.ai-input-hint { font-size: 13px; color: var(--text-muted); margin-top: 4px; }

/* Правая панель AI */
.ai-side { display: flex; flex-direction: column; gap: 12px; }

.insight-card {
  background: var(--surface2); border-radius: var(--radius);
  padding: 13px; border-left: 3px solid;
  display: flex; flex-direction: column; gap: 6px;
  transition: opacity .2s;
}
.insight-card.critical { border-color: var(--red); background: rgba(255,77,109,.05); }
.insight-card.warning  { border-color: var(--yellow); background: rgba(255,197,75,.04); }
.insight-card.info     { border-color: var(--accent); background: rgba(var(--accent-rgb), .04); }
.insight-title { font-size: 15px; font-weight: 700; }
.insight-desc  { font-size: 14px; color: var(--text-muted); line-height: 1.5; }
.insight-action{ font-size: 14px; color: var(--accent3); }
.insight-footer { display: flex; align-items: center; justify-content: space-between; }

.digest-card {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px;
}
.digest-card h4 { font-size: 15px; font-weight: 700; margin-bottom: 8px; color: var(--accent-text); }
.digest-text {
  font-size: 14px; line-height: 1.7; color: var(--text);
  white-space: pre-wrap; max-height: 300px; overflow-y: auto;
}

.quick-prompts { display: flex; flex-direction: column; gap: 5px; }
.quick-prompt-btn {
  padding: 8px 11px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--surface2); color: var(--text-muted); cursor: pointer;
  font-size: 14px; text-align: left; transition: all .12s;
}
.quick-prompt-btn:hover { border-color: rgba(var(--accent-rgb), .4); color: var(--text); background: rgba(var(--accent-rgb), .08); }

/* ═══════════════════════════════════════════════════════════════════════════════
   АНАЛИТИКА
═══════════════════════════════════════════════════════════════════════════════ */
.analytics-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: 12px; margin-bottom: 20px; }

.kpi-card {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px;
}
.kpi-label { font-size: 13px; color: var(--text-muted); font-weight: 600; margin-bottom: 6px; }
.kpi-value { font-size: 26px; font-weight: 700; line-height: 1; }
.kpi-trend { font-size: 13px; margin-top: 4px; }
.kpi-trend.up   { color: var(--accent3); }
.kpi-trend.down { color: var(--red); }

.analytics-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 900px) { .analytics-row { grid-template-columns: 1fr; } }

/* Широкие таблицы прокручиваются внутри своей карточки, а не обрезаются
   и не растягивают страницу вбок (правило адаптивности). */
.card table, .stock-table { max-width: 100%; }
.card { overflow-x: auto; }

.stock-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.stock-table th {
  color: var(--text-2); font-weight: var(--fw-semi);
  padding: 7px 9px; text-align: left; border-bottom: 1px solid var(--border);
  /* Заголовки таблиц — мелкие капсы с разрядкой: так они читаются как служебные
     подписи и не спорят с данными за внимание. */
  font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: .04em;
}
.stock-table td { padding: 7px 8px; border-bottom: 1px solid var(--border); }
/* Остатки склада: пять коротких колонок. Во всю ширину экрана между названием
   товара и его остатком получалось полтора метра пустоты, и строка читалась
   только по линейке. Ширина по содержимому, но не уже читаемого минимума. */
/* Таблица остатков склада: половина ширины страницы, по центру карточки.
   min-content ужимал её до 460 пикселей, и на широком экране она превращалась
   в узкую полоску, прижатую к левому краю, — при том что справа пустовало
   больше метра. Половина ширины — компромисс: строка читается целиком, а
   пустота поделена поровну и выглядит как поле, а не как обрыв. */
/* `table.` в селекторе не для красоты: выше стоит `.card table {max-width:100%}`,
   и оно весит больше одного класса. Без имени тега правило проигрывало, и
   таблица снова расползалась во всю карточку — при том что в стилях чёрным по
   белому был записан потолок. Третий раз за день та же ловушка: класс сам по
   себе слабее, чем «класс + тег». */
table.wh-stock-table {
  width: 100%; max-width: 720px; min-width: 460px;
  margin-left: auto; margin-right: auto;
}
.stock-table.table-framed td,
.stock-table.table-framed th { white-space: nowrap; }
.stock-table.table-framed .stock-qty { text-align: right; padding-left: var(--sp-5); }
.stock-table tr:hover td { background: var(--surface3); }
.days-cell { font-weight: 600; }
.days-ok      { color: var(--accent3); }
.days-warning { color: var(--yellow); }
.days-critical{ color: var(--red); }

/* ABC mini-chart */
/* ── Токен на запись ───────────────────────────────────────────────────────
   Под раскрытием намеренно: он нужен меньшинству, а на виду читался бы как
   обязательный шаг настройки. Состояние вынесено в саму строку — иначе, чтобы
   узнать «задан или нет», пришлось бы разворачивать. */
.wtok {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface2);
}
.wtok > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; cursor: pointer; list-style: none;
  font-size: var(--fs-footnote); font-weight: var(--fw-medium); color: var(--text);
}
.wtok > summary::-webkit-details-marker { display: none; }
.wtok > summary .ic { color: var(--text-3); }
.wtok[open] > summary { border-bottom: 1px solid var(--border); }
.wtok-state {
  margin-left: auto; padding: 2px 9px; border-radius: 99px;
  background: var(--surface3); color: var(--text-2);
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
}
.wtok-state.is-on { background: var(--accent-weak); color: var(--accent-text); }
.wtok-body { padding: 12px; }
.wtok-hint {
  font-size: var(--fs-caption); color: var(--text-2);
  line-height: 1.5; margin-bottom: 10px;
}
.wtok-msg {
  font-size: var(--fs-caption); color: var(--text-2);
  min-height: 16px; margin-bottom: 8px;
}
.wtok-msg.is-ok  { color: var(--accent-text); }
.wtok-msg.is-bad { color: var(--red); }

/* ── ABC кольцом ───────────────────────────────────────────────────────────
   Столбики заменены кольцом: ABC — это доли одного целого, и три отдельных
   столбика заставляли складывать проценты в уме. Цвета оставлены прежние,
   чтобы группа узнавалась и здесь, и в значках рядом с артикулами. */
.abc-donut-wrap {
  display: flex; align-items: center; gap: var(--sp-4);
  margin: var(--sp-4) 0 var(--sp-2); flex-wrap: wrap;
}
.abc-donut { width: 200px; height: 200px; flex: none; overflow: visible; }

/* Сектор поднимается по своей биссектрисе: сдвиг считает JS (у каждого он
   свой), здесь только движение. Оно же — единственная подсказка, что по
   сектору можно нажать, поэтому transition обязателен: мгновенный скачок
   читается как дефект отрисовки, а не как отклик. */
.abc-slice {
  cursor: pointer;
  transform: translate(0, 0);
  transition: transform .18s var(--ease-out), filter .18s var(--ease);
}
.abc-slice:hover, .abc-slice:focus-visible {
  transform: translate(var(--lift-x), var(--lift-y));
  filter: brightness(1.06);
  outline: none;
}
.abc-slice-a { fill: var(--accent-bright); }
.abc-slice-b { fill: #e8a01c; }
.abc-slice-c { fill: #7b82a8; }

.abc-center-num {
  font-size: 30px; font-weight: var(--fw-heavy); fill: var(--text);
  letter-spacing: -.02em;
}
.abc-center-lbl { font-size: 12px; fill: var(--text-2); }

.abc-legend { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.abc-leg {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: var(--radius-sm);
  border: 1px solid transparent; background: none; cursor: pointer;
  font: inherit; font-size: var(--fs-footnote); color: var(--text);
  text-align: left; width: 100%;
}
.abc-leg:hover:not(:disabled) { background: var(--surface3); border-color: var(--border); }
.abc-leg:disabled { opacity: .45; cursor: default; }
.abc-leg b { font-weight: var(--fw-heavy); width: 12px; }
.abc-leg span { color: var(--text-2); font-size: var(--fs-caption); }
.abc-dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.abc-dot-a { background: var(--accent-bright); }
.abc-dot-b { background: #e8a01c; }
.abc-dot-c { background: #7b82a8; }

/* Название товара под артикулом в окне со списком */
.abc-row-sub {
  display: block; font-weight: 400;
  font-size: var(--fs-caption); color: var(--text-2);
}

/* ── Misc ───────────────────────────────────────────────────────────────────── */
.mt-8  { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-20 { margin-top: 20px; }
.flex  { display: flex; align-items: center; gap: 8px; }
.text-muted { color: var(--text-muted); font-size: 14px; }
.spinner {
  display: inline-block; width: 13px; height: 13px;
  border: 2px solid rgba(var(--accent-rgb), .25); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.toast {
  position: fixed; bottom: 20px; right: 20px;
  background: var(--surface2); border: 1px solid var(--border);
  color: var(--text); padding: 12px 18px; border-radius: var(--radius-sm);
  font-size: 15px; z-index: 999; box-shadow: 0 6px 28px rgba(0,0,0,.4);
  animation: toastIn .2s ease; max-width: 300px;
}
@keyframes toastIn { from { opacity:0; transform:translateY(8px); } }
.divider { height: 1px; background: var(--border); margin: 14px 0; }

/* ── Settings ──────────────────────────────────────────────────────────────── */
.settings-perm-row {
  padding: 7px 12px;
  border-radius: 7px;
  font-size: 14px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.settings-perm-row.required { border-color: rgba(99,102,241,.3); background: rgba(99,102,241,.06); }
.settings-perm-row.optional { color: var(--text-muted); }

.settings-reason-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 14px;
  color: var(--text-muted);
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
  line-height: 1.5;
}
.settings-reason-row:last-child { border-bottom: none; }
.settings-reason-tag {
  flex-shrink: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 3px 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  min-width: 80px;
  text-align: center;
}


/* ═══ ДАШБОРД: KPI-карточки и плитки ══════════════════════════════════════════
   Схема с референса: иконка в мягкой цветной плашке, подпись, крупное число,
   мелкая сноска. Работает потому, что взгляд цепляется за иконку и сразу
   находит цифру — читать подпись не обязательно. */
.kpi-row {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  margin-bottom: var(--sp-4);
}
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--sp-4) var(--sp-5);
  box-shadow: var(--shadow-sm);
  display: flex; gap: var(--sp-3); align-items: flex-start;
  transition: box-shadow var(--t-base) var(--ease), transform var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease);
}
.kpi:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: var(--border-strong); }
.kpi-ico {
  width: 38px; height: 38px; flex-shrink: 0; border-radius: var(--radius-sm);
  display: grid; place-items: center; font-size: var(--fs-title3);
  background: var(--accent-weak);
}
.kpi-ico.warn { background: var(--yellow-weak); }
.kpi-ico.bad  { background: var(--red-weak); }
.kpi-ico.ok   { background: var(--green-weak); }
.kpi-body { min-width: 0; flex: 1; }
.kpi-label { font-size: var(--fs-footnote); color: var(--text-2); font-weight: var(--fw-medium); margin-bottom: 3px; }
.kpi-num   { font-size: var(--fs-title1); font-weight: var(--fw-heavy); letter-spacing: -.025em; line-height: 1.1; }
.kpi-num span { font-size: var(--fs-footnote); font-weight: var(--fw-medium); color: var(--text-2); }
.kpi-foot  { font-size: var(--fs-footnote); color: var(--text-2); margin-top: 6px; line-height: 1.45; }

/* Плитки кабинетов — они же фильтр ленты */
.mon-tiles { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); }
.mon-tile {
  text-align: left; cursor: pointer; font-family: inherit;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-base) var(--ease);
}
.mon-tile:hover { transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--border-strong); }
/* Выбранная плитка обведена акцентом И подписана — цвет не единственный признак */
.mon-tile.active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak); }
.mon-tile-name { font-size: var(--fs-caption); color: var(--text-2); margin-bottom: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mon-tile-value { font-size: var(--fs-title2); font-weight: var(--fw-bold); letter-spacing: -.02em; }
.mon-tile-value span { font-size: var(--fs-footnote); font-weight: var(--fw-medium); color: var(--text-2); }
.mon-tile-sub { font-size: var(--fs-caption); color: var(--text-2); }
.mon-tile-foot { display: flex; gap: 5px; align-items: center; margin-top: var(--sp-2); }
.mon-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* Лента заказов таблицей: плотнее и ровнее, чем строки-флексы */
.mon-feed { width: 100%; border-collapse: collapse; font-size: var(--fs-footnote); }
.mon-feed th {
  text-align: left; padding: 0 var(--sp-2) var(--sp-2);
  font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  text-transform: uppercase; letter-spacing: .05em; color: var(--text-3);
  border-bottom: 1px solid var(--border);
}
.mon-feed td { padding: 9px var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: middle; }
.mon-feed tr:last-child td { border-bottom: none; }
.mon-feed tbody tr { transition: background var(--t-fast) var(--ease); }
.mon-feed tbody tr:hover { background: var(--surface3); }
.mon-time b { display: block; }
.mon-time span, .mon-art span { display: block; font-size: var(--fs-caption2); color: var(--text-3); }
.mon-art b { font-weight: var(--fw-semi); }
.mon-cab { color: var(--text-2); }
.mon-wh  { color: var(--text-2); max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mon-stock { font-weight: var(--fw-semi); text-align: center; }
.mon-sum { text-align: right; font-weight: var(--fw-semi); white-space: nowrap; }

/* Шапка раздела: заголовок с описанием — как на референсе */
.page-head { margin-bottom: var(--sp-5); }
.page-head h2 { font-size: var(--fs-title1); font-weight: var(--fw-bold); letter-spacing: -.03em; }
.page-head p  { font-size: var(--fs-footnote); color: var(--text-2); margin-top: 3px; }

/* Список выбора кабинетов для наблюдения */
.mon-pick {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface2);
  cursor: pointer; font-size: var(--fs-footnote);
}
.mon-pick:hover { background: var(--surface-hover); }
/* Недоступный кабинет гасим, но причину оставляем читаемой */
.mon-pick-off { opacity: .6; cursor: default; }
.mon-pick-off:hover { background: var(--surface2); }

/* ═══ Компоненты: выбор кабинета, календарь, круглые галочки ═════════════════
   Родные <select> и <input type="date"> остаются в разметке — прячем их так,
   чтобы значение по-прежнему читалось кодом, а с экрана контрол исчез.
   display:none подошёл бы, но у скрытых полей ломается фокус и валидация,
   поэтому убираем размерами. */
/* Родной select/input прячем, оставляя доступным для клавиатуры и экранных
   читалок. `!important` здесь по делу, а не от лени: правило для input весит
   больше одного класса и до сих пор возвращало спрятанному полю ширину 100%
   и отступы. Спасал только clip — то есть поле было невидимым по случайности,
   а не потому что мы его спрятали. Утилита скрытия обязана выигрывать у любого
   оформления, иначе она не утилита. */
.ui-native-hidden {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  padding: 0 !important; margin: -1px !important; border: 0 !important;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* Кнопка-триггер: то, что пользователь видит вместо системного контрола */
.ui-trigger {
  display: inline-flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface2);
  color: var(--text); font: inherit; font-size: var(--fs-footnote);
  cursor: pointer; text-align: left; transition: border-color .14s, background .14s;
}
.ui-trigger:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.ui-trigger[aria-expanded="true"] {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak);
}
.ui-trigger-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-dim { color: var(--text-3); }

/* Шеврон рисуем сами: системная стрелка select'а в каждом браузере своя */
.ui-chev {
  width: 9px; height: 9px; flex: none; margin-left: 2px;
  border-right: 2px solid var(--text-3); border-bottom: 2px solid var(--text-3);
  border-radius: 1px; transform: translateY(-2px) rotate(45deg);
  transition: transform .18s;
}
.ui-trigger[aria-expanded="true"] .ui-chev { transform: translateY(1px) rotate(-135deg); }

.ui-cal-ico {
  width: 15px; height: 15px; flex: none; border-radius: 3px;
  border: 1.6px solid var(--text-3);
  box-shadow: inset 0 3px 0 -1.4px var(--text-3);
}

/* Кружок с инициалами — кабинет узнаётся до чтения подписи */
.ui-ava {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  display: grid; place-content: center;
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
}
.ui-ava-empty { background: var(--surface3); color: var(--text-2); }

/* ── Всплывающая панель ──────────────────────────────────────────────────── */
.ui-pop {
  position: fixed; z-index: 900;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; box-shadow: 0 18px 48px rgba(15,23,42,.20);
  padding: 6px; overflow: hidden;
  animation: ui-pop-in .16s cubic-bezier(.2,.9,.3,1);
}
:root:not([data-theme="light"]) .ui-pop,
[data-theme="dark"] .ui-pop { box-shadow: 0 18px 48px rgba(0,0,0,.55); }
.ui-pop-down { transform-origin: top center; }
.ui-pop-up   { transform-origin: bottom center; }
@keyframes ui-pop-in { from { opacity: 0; transform: scale(.96) translateY(-4px); } }

.ui-pop-head {
  padding: 8px 10px 6px; font-size: var(--fs-caption);
  color: var(--text-2); font-weight: var(--fw-medium);
}
.ui-pop-foot {
  padding: 8px 10px 6px; margin-top: 4px; border-top: 1px solid var(--border);
  font-size: var(--fs-caption2); color: var(--text-3);
}
.ui-pop-search { padding: 4px 4px 6px; }
.ui-pop-search input {
  width: 100%; padding: 7px 10px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font: inherit; font-size: var(--fs-footnote);
}
.ui-pop-search input:focus { outline: none; border-color: var(--accent); }
/* Список прокручивается: десять кабинетов не должны растягивать окно */
.ui-pop-list { max-height: 264px; overflow-y: auto; }
.ui-pop-empty { padding: 14px 10px; text-align: center; color: var(--text-3); font-size: var(--fs-footnote); }

.ui-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 9px; border: none; border-radius: 10px;
  background: transparent; color: var(--text); font: inherit;
  font-size: var(--fs-footnote); cursor: pointer; text-align: left;
}
.ui-row:hover { background: var(--surface3); }
.ui-row-on { background: var(--accent-weak); }
.ui-row-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-row-note { display: block; font-size: var(--fs-caption2); color: var(--text-3); }
.ui-row-off { opacity: .55; cursor: default; }
.ui-row-off:hover { background: transparent; }

/* Галочка выбранного пункта — тонкая, как в системных меню */
.ui-tick { width: 16px; height: 16px; flex: none; position: relative; }
.ui-row-on .ui-tick::after {
  content: ''; position: absolute; left: 4px; top: 1px;
  width: 6px; height: 11px; border: solid var(--accent-text);
  border-width: 0 2px 2px 0; transform: rotate(43deg);
}

/* ── Круглые галочки вместо квадратных ───────────────────────────────────── */
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; min-width: 22px; min-height: 22px;
  flex: none; margin: 0; padding: 0; box-sizing: border-box;
  border: 2px solid var(--border-strong); border-radius: 50%;
  background: var(--surface2); cursor: pointer; position: relative;
  transition: background .15s, border-color .15s, transform .12s;
}
input[type="checkbox"]:hover:not(:disabled) { border-color: var(--accent); }
input[type="checkbox"]:active:not(:disabled) { transform: scale(.92); }
input[type="checkbox"]:checked { background: var(--accent-fill-a); border-color: var(--accent-fill-a); }
input[type="checkbox"]::after {
  content: ''; position: absolute; left: 6px; top: 2.5px;
  width: 5px; height: 10px; border: solid var(--accent-ink); border-width: 0 2px 2px 0;
  transform: rotate(43deg) scale(0); transition: transform .16s cubic-bezier(.2,1.3,.4,1);
}
input[type="checkbox"]:checked::after { transform: rotate(43deg) scale(1); }
input[type="checkbox"]:disabled { opacity: .45; cursor: default; }

/* ── Календарь ───────────────────────────────────────────────────────────── */
.ui-cal { padding: 10px; }
.ui-cal-head { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.ui-cal-title {
  flex: 1; text-align: center; font-size: var(--fs-footnote);
  font-weight: var(--fw-semibold); color: var(--text);
}
.ui-cal-nav {
  width: 28px; height: 28px; flex: none; border-radius: 8px;
  border: none; background: transparent; cursor: pointer; position: relative;
}
.ui-cal-nav:hover { background: var(--surface3); }
.ui-cal-nav::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 8px; height: 8px; border: solid var(--text-2);
  border-width: 2px 0 0 2px; transform: translateX(2px) rotate(-45deg);
}
.ui-cal-next::after { transform: translateX(-2px) rotate(135deg); }

.ui-cal-week, .ui-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.ui-cal-week { margin-bottom: 4px; }
.ui-cal-week span {
  text-align: center; font-size: var(--fs-caption2);
  color: var(--text-3); padding: 4px 0;
}
.ui-cal-grid { gap: 2px; }
.ui-cal-day {
  aspect-ratio: 1; border: none; background: transparent; cursor: pointer;
  border-radius: 50%; color: var(--text); font: inherit;
  font-size: var(--fs-footnote); font-variant-numeric: tabular-nums;
  display: grid; place-content: center; transition: background .12s;
}
.ui-cal-day:hover { background: var(--surface3); }
.ui-cal-out { color: var(--text-3); opacity: .6; }
/* Сегодня — только обводка: заливкой помечен ВЫБОР, и путать их нельзя */
.ui-cal-today { box-shadow: inset 0 0 0 1.5px var(--border-strong); font-weight: 600; }
.ui-cal-sel { background: var(--accent-fill-a); color: var(--accent-ink); font-weight: 600; box-shadow: none; }
.ui-cal-sel:hover { background: var(--accent-fill-a); }

.ui-cal-foot {
  display: flex; gap: 6px; margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--border);
}
.ui-cal-link {
  flex: 1; padding: 6px; border: none; background: transparent;
  color: var(--accent-text); font: inherit; font-size: var(--fs-caption);
  font-weight: var(--fw-medium); border-radius: 8px; cursor: pointer;
}
.ui-cal-link:hover { background: var(--accent-weak); }

/* Шапка мониторинга: статус слева, управление справа */
/* Выбор кабинета стоит СЛЕВА, а не справа. Это первое действие в разделе —
   пока кабинет не выбран, остальное не имеет смысла, — а взгляд начинает
   строку слева. Отметка времени уехала вправо: она справочная. */
.mon-bar {
  display: flex; align-items: center; justify-content: flex-start;
  gap: 10px; margin-bottom: var(--sp-4);
}
.mon-bar #mon-status,
.mon-bar #fbs-live-status { margin-left: auto; }
/* Триггер по ширине содержимого — в панели он не должен растягиваться */
.ui-trigger-auto { width: auto; }
/* То же для кнопок, которые ui.js делает из select: в строке раздела выбор
   кабинета не должен занимать её целиком. Нижняя граница — чтобы длинное имя
   магазина не заставляло кнопку прыгать в ширину при каждой смене кабинета. */
.sec-bar .ui-trigger { width: auto; min-width: 190px; }
.sec-bar .ui-native-hidden { display: none; }

/* Строка выбора времени в календаре datetime-local */
.ui-cal-time {
  display: flex; align-items: center; gap: 6px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
}
.ui-cal-time-label {
  flex: 1; font-size: var(--fs-caption); color: var(--text-2);
}
.ui-cal-time select {
  width: auto; min-height: 0; padding: 5px 8px;
  font-size: var(--fs-footnote); font-variant-numeric: tabular-nums;
  border-radius: 8px;
}
.ui-cal-colon { color: var(--text-3); }

/* ── Иконки в интерфейсе ────────────────────────────────────────────────────
   Те же контуры, что в меню, но встраиваются в строку текста. Размер в em,
   поэтому иконка масштабируется вместе с подписью: в мелкой сноске она
   мелкая, в заголовке — крупная, и специальные размеры не нужны. */
.ic {
  width: 1.15em; height: 1.15em;
  flex-shrink: 0; vertical-align: -.2em;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
/* Крупные иконки в шапках блоков настроек — там, где раньше стояли эмодзи
   в цветном квадрате. */
.ic-lg { width: 20px; height: 20px; vertical-align: middle; }
.ic-xl { width: 26px; height: 26px; stroke-width: 1.5; vertical-align: middle; }

/* Точка серьёзности вместо цветных кружков-эмодзи: 🔴 и 🟡 в разных системах
   рисуются разным размером и цветом, а это статус — он должен быть одинаковым
   везде и совпадать с палитрой темы. */
.sev-dot {
  display: inline-block; width: .58em; height: .58em; border-radius: 50%;
  vertical-align: .04em; margin-right: .1em; background: currentColor;
}
.sev-red    { background: var(--red); }
.sev-yellow { background: var(--yellow); }
.sev-green  { background: var(--green); }

/* ── Карточка кабинета ──────────────────────────────────────────────────────
   Срок жизни токена — свойство самого кабинета, поэтому стоит НАД названием,
   отдельной строкой. В общем ряду он занимал место, и «Себестоимость»
   переносилась на вторую строку, разрывая ряд виджетов надвое. */
.cab-token-line { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
/* Кнопки стоят В ОДНОЙ СТРОКЕ с названием, а не в абсолютном углу: так они
   сами встают на его уровень и не требуют подгонки отступами, которая
   разъезжается при любом изменении шрифта. */
.cab-card-actions { display: flex; gap: 5px; flex-shrink: 0; }

/* flex-start, а не center: под названием идёт подпись клиента, и по центру
   ДВУХ строк кнопки провисали бы ниже имени. */
.cab-head-row { display: flex; align-items: flex-start; gap: var(--sp-3); }

/*
 * ⚠️ ПРИБЫЛЬ ПОД КНОПКАМИ, А НЕ В СТРОКУ С НАЗВАНИЕМ.
 *
 * Раньше она стояла в одной строке с названием — по замыслу «сколько
 * заработал ЭТОТ кабинет»: на одном уровне с именем читается как его
 * свойство. Замысел верный, места не было. Название, прибыль и три кнопки не
 * помещались в одну строку: имени оставалось полторы сотни пикселей, и оно
 * ломалось по одному слогу — «ДЕМО Одеж / да / „Лили / я“».
 *
 * Теперь справа одна колонка: кнопки сверху, прибыль под ними. Прибыль
 * по-прежнему в правом верхнем углу карточки и по-прежнему читается как
 * свойство кабинета, но название получает всю строку целиком.
 *
 * Подпись «~ЧП сегодня» обязательна: без неё сумма выглядит как оборот.
 */
/* Правая колонка шапки: кнопки сверху, прибыль под ними. */
.cab-head-right {
  margin-left: auto; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.cab-profit {
  display: flex; align-items: baseline; gap: 8px;
  cursor: help; font-variant-numeric: tabular-nums;
  /* Не переносится под кнопки по слогам: строка короткая и должна остаться
     одной, иначе правая колонка расползётся и снова съест название. */
  white-space: nowrap;
}
/* Подпись — тем же шрифтом и весом, что название кабинета: это равноправные
   заголовки строки, и разнобой в них читается как случайность вёрстки. */
.cab-profit-lbl {
  font-size: var(--fs-callout); font-weight: var(--fw-semi);
  letter-spacing: -.02em; color: var(--text-2); white-space: nowrap;
}
/* Значение — овальной кнопкой: сумма и так главная цифра карточки, а рамка
   отделяет её от кнопок справа, с которыми она раньше сливалась. */
.cab-profit-pill {
  display: inline-block; padding: 3px 12px; border-radius: 999px;
  font-size: var(--fs-callout); font-weight: 800; letter-spacing: -.02em;
  background: var(--surface3); white-space: nowrap;
}
.cab-profit-pos .cab-profit-pill { background: var(--accent-weak); }
.cab-profit-neg .cab-profit-pill { background: var(--red-weak); }
.cab-profit-pos { color: var(--accent-text); }
.cab-profit-neg { color: var(--red); }
.cab-profit-none { color: var(--text-3); font-weight: 400; }
.cab-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* ── Подсказка со списком ───────────────────────────────────────────────────
   Тот же элемент, что и у гистограммы заказов, но с разметкой внутри.
   pointer-events: none — иначе подсказка перехватывает курсор и мигает. */
.spark-tip.tip-rich {
  max-width: 320px; padding: 9px 11px; text-align: left;
  white-space: normal; line-height: 1.45; pointer-events: none;
}
.tip-head {
  font-size: var(--fs-caption2); color: var(--text-3);
  text-transform: uppercase; letter-spacing: .04em;
  margin-bottom: 6px;
}
.tip-row {
  display: flex; align-items: flex-start; gap: 7px;
  padding: 2px 0; font-size: var(--fs-footnote);
}
.tip-row .ic { margin-top: .15em; color: var(--text-3); }
/* Выполненное гасим, но не зачёркиваем: список читают глазами по диагонали,
   и зачёркнутый текст в нём разбирать труднее, чем просто тусклый. */
.tip-done { color: var(--text-3); }
.tip-done .ic { color: var(--accent-text); }
.tip-row i { display: block; font-style: normal; font-size: var(--fs-caption2); color: var(--text-3); }
.tip-empty { font-size: var(--fs-footnote); color: var(--text-3); }
.tip-more { margin-top: 4px; font-size: var(--fs-caption2); color: var(--text-3); }

/* Разбивка остатка по схемам в таблице мониторинга */
.mon-split { white-space: nowrap; font-variant-numeric: tabular-nums; }
.mon-slash { color: var(--text-3); margin: 0 3px; }
/* Свой склад выделяем: именно он пополняется быстро, и решение о поставке
   зависит от того, сколько лежит на WB, а сколько под рукой. */
.mon-fbs { color: var(--accent-text); font-weight: var(--fw-semibold); }

/* Выбор кабинета в панели задач месяца */
.month-task-cab { margin-bottom: 8px; }
.month-task-cab label {
  display: block; margin-bottom: 4px;
  font-size: var(--fs-caption); color: var(--text-2);
}

/* Шкала часов под графиком заказов. Подписи позиционируются в процентах —
   так они совпадают со своими столбцами при любой ширине карточки. */
.spark-axis { position: relative; height: 14px; margin-top: 3px; }
.spark-axis span {
  position: absolute; top: 0; white-space: nowrap;
  font-size: var(--fs-caption2); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* Разбивка склада в KPI. Сетка из двух колонок, а не строка с переносом:
   так числа стоят строго друг под другом одной вертикалью независимо от их
   длины и от ширины карточки. Инлайновый вариант выравнивал их только пока
   подписи совпадали по ширине, а при переносе строки разъезжался.

   Селектор с родителем нужен из-за соседнего `.kpi-num span` (мелкий суффикс
   «шт») — он перебивал стиль по специфичности, и числа выходили 13 px. */
.kpi-num .kpi-split {
  display: inline-grid; grid-template-columns: auto auto;
  align-items: baseline; gap: 4px 10px;
  /* Размер задаём явно: соседнее правило `.kpi-num span` (мелкий суффикс «шт»)
     ловит и этот span, из-за чего числа сжимались до 13 px. */
  font-size: 1em;
}
.kpi-num .kpi-split b {
  font-size: 1em; font-weight: var(--fw-bold); color: var(--text);
  letter-spacing: -.03em; justify-self: start;
}
/* Подпись схемы мелкая и тусклая: читается число, а не буквы рядом с ним */
.kpi-num .kpi-split i {
  font-style: normal; font-size: .55em; font-weight: var(--fw-semibold);
  color: var(--text-2); letter-spacing: .02em; justify-self: end;
}


/* Размерная линейка в подсказке: столбики длиной по остатку */
.size-row {
  display: grid; grid-template-columns: 34px 1fr 34px;
  align-items: center; gap: 8px; padding: 2px 0;
  font-size: var(--fs-footnote); font-variant-numeric: tabular-nums;
}
.size-name { color: var(--text-2); }
.size-qty  { text-align: right; color: var(--text); }
.size-bar {
  height: 6px; border-radius: 3px; background: var(--surface3); overflow: hidden;
}
.size-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
/* Закончившийся размер — красным: это дыра в линейке, товар формально есть,
   а купить нужный рост нельзя. */
.size-out .size-name, .size-out .size-qty { color: var(--red); }
.size-out .size-bar { background: var(--red-weak); }

/* ── Выбор кабинета в мониторинге ───────────────────────────────────────────
   Одна широкая полоса с текущим кабинетом; по клику раскрывается список всех
   со статистикой. Сетка плиток при десяти кабинетах занимала пол-экрана
   раньше самих остатков, а сегменты в строку — сжимали названия до «ДЕМО
   Одежда «Лили…». Здесь имя всегда целиком, а сравнить склады можно в списке. */
.mon-bar-pick {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 16px; margin-bottom: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text); font: inherit;
  cursor: pointer; text-align: left;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.mon-bar-pick:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.mon-bar-pick[aria-expanded="true"] {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak);
}
.mon-bar-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.mon-bar-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mon-bar-name {
  font-size: var(--fs-callout); font-weight: var(--fw-semibold);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mon-bar-sub { font-size: var(--fs-caption); color: var(--text-2); }
.mon-bar-low {
  flex: none; padding: 3px 9px; border-radius: 99px;
  background: var(--red-weak); color: var(--red);
  font-size: var(--fs-caption); font-weight: var(--fw-semibold);
}

/* Строка кабинета в раскрытом списке */
.mon-row { align-items: center; }
.mon-row-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.mon-row-sub {
  display: block; font-size: var(--fs-caption2);
  color: var(--text-3); font-weight: var(--fw-regular);
}
.mon-row-low {
  flex: none; min-width: 22px; padding: 1px 7px; border-radius: 99px;
  background: var(--red-weak); color: var(--red);
  font-size: var(--fs-caption2); font-weight: var(--fw-bold); text-align: center;
}

/* ── Окно подтверждения ─────────────────────────────────────────────────────
   Своё вместо системного confirm(): в системном нельзя назвать то, что
   удаляют, а именно это и защищает от мисклика. */
.ui-confirm-back {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(15, 23, 42, .5); backdrop-filter: blur(4px);
  animation: ui-fade-in .14s ease;
}
@keyframes ui-fade-in { from { opacity: 0; } }
.ui-confirm {
  width: 400px; max-width: calc(100vw - 32px);
  padding: 22px; border-radius: var(--radius-lg);
  animation: ui-pop-in .16s cubic-bezier(.2,.9,.3,1);
}
.ui-confirm-title {
  font-size: var(--fs-callout); font-weight: var(--fw-bold); margin-bottom: 6px;
}
.ui-confirm-text {
  font-size: var(--fs-footnote); color: var(--text-2); line-height: 1.5;
}
.ui-confirm-text b { color: var(--text); }
.ui-confirm-actions {
  display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px;
}
/* Плотная красная кнопка — только для необратимого действия. Обычная
   .btn-danger бледная, и на ней подтверждение выглядит как обычный пункт. */
.btn-danger-solid {
  background: var(--red); color: var(--danger-ink); border: 1px solid var(--red);
}
.btn-danger-solid:hover:not(:disabled) { filter: brightness(.92); }

/* ── Что допоставить ────────────────────────────────────────────────────────
   Два разных действия и два разных цвета: «отгрузить» — своё, делается за
   день; «докупить» — деньги и недели. Смешивать их в одну цифру нельзя. */
.res-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3);
}
.res-sum {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
}
.res-sum-item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--surface3);
  font-size: var(--fs-caption); color: var(--text-2);
}
.res-sum-num {
  font-size: var(--fs-title2); font-weight: var(--fw-bold);
  letter-spacing: -.02em; color: var(--text);
}
.res-ship { color: var(--accent-text); }
.res-buy  { color: var(--red); }
/* Горящая позиция: остаток кончится раньше, чем приедет поставка */
.res-urgent td { background: var(--red-weak); }
.res-hint {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: var(--sp-3); padding: 10px 12px;
  border-radius: var(--radius-sm); background: var(--accent-weak);
  font-size: var(--fs-caption); color: var(--text-2); line-height: 1.5;
}
.res-hint .ic { color: var(--accent-text); margin-top: .1em; }

/* ── Строка раздела: переключатель, кабинет и всё прочее в один ряд ─────────
   Раньше каждый раздел собирал свою шапку сам: где-то переключатель стоял над
   панелью с кабинетом, где-то рядом с кнопкой, и от меню до первой кнопки в
   каждом разделе было своё расстояние. Глазу это читается как съехавшая
   вёрстка, хотя каждый блок по отдельности выглядит правильно.

   Теперь шапка одна на все разделы: слева переключатель вида, следом кнопки
   самого раздела и выбор кабинета — всё в одной плоскости. Правый край
   оставлен пустым намеренно: туда пойдут пояснения к разделам. */
.sec-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
/* Вложенные блоки свой нижний отступ отдают строке — иначе он удваивается */
.sec-bar > .an-views,
.sec-bar > .mon-bar { margin-bottom: 0; }
.sec-bar > .mon-bar { gap: var(--sp-2); }
/* Место под пояснение раздела: прижато вправо, занимает остаток строки */
.sec-bar-note {
  margin-left: auto; font-size: var(--fs-caption); color: var(--text-3);
  text-align: right; max-width: 46ch;
}
/* Статус внутри строки уже не должен сам себя отжимать вправо: этим занята
   строка целиком, и двойной margin-left:auto рвал её на две части. */
.sec-bar .mon-bar #mon-status,
.sec-bar .mon-bar #fbs-live-status { margin-left: 0; }

/* Переключатель видов Аналитики: экономика и реклама смотрят на один и тот же
   кабинет за один и тот же период, поэтому это два вида, а не две вкладки. */
.an-views {
  display: inline-flex; gap: 2px; margin-bottom: var(--sp-4);
  padding: 4px; border-radius: var(--radius-md);
  background: var(--surface3); border: 1px solid var(--border);
}
.an-view {
  padding: 7px 18px; border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-2); font: inherit;
  font-size: var(--fs-footnote); font-weight: var(--fw-medium); cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  /*
   * ⚠️ ПОДПИСЬ НЕ ПЕРЕНОСИТСЯ, КАК И У .btn.
   *
   * Без этого в узком контейнере длинное слово уезжало на вторую строку, и
   * переключатель раздувался вдвое: замерено — «Напоминание / Авто-отчёт» в
   * шапке формы были 54 пикселя против 34 у точно таких же переключателей в
   * «Экономике» и «Остатках». Один и тот же элемент выглядел двумя разными.
   *
   * Высота строки задана явно по той же причине, что у .btn: при `normal` её
   * выбирает шрифт, и подпись со стрелкой или знаком валюты оказывается выше
   * соседней.
   */
  white-space: nowrap; line-height: 20px;
}
.an-view:hover { color: var(--text); }
.an-view.active {
  background: var(--surface); color: var(--text);
  font-weight: var(--fw-semibold); box-shadow: var(--shadow-sm);
}

/* ── РНП: метрики в строках, периоды в колонках ─────────────────────────────
   Разворот таблицы не косметика: показатели сравнивают по горизонтали —
   «CTR вчера и позавчера». Когда днями идут строки, взгляд прыгает по
   вертикали и провал в метрике не виден. */
/* ── РНП: шапка с переключателем взгляда ──────────────────────────────────
 *
 * ⚠️ ВЫБОР АРТИКУЛОВ И ПОИСК ЗАНИМАЮТ ОДНО МЕСТО. Они взаимоисключающие: во
 * «всём кабинете» выбирают разрезы вручную, «по артикулам» показаны все и
 * нужен поиск. Стоя рядом, они бы намекали, что работают вместе.
 */
.rnp-head {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  margin-bottom: 12px;
}
.rnp-head .ui-trigger { width: 220px; flex: none; }
/* ⚠️ Три класса, а не два: базовый сброс форм (0,2,1) бьёт пару классов и
   растягивает поле на всю шапку. Тот же капкан, что в «Рекламе» и А/Б. */
.rnp-head .rnp-art-search.ads-select { width: 240px; max-width: 100%; flex: none; }

/* ── РНП: строка артикула ─────────────────────────────────────────────────
 *
 * ⚠️ СНАЧАЛА СТРОКА, ТАБЛИЦА — ПО НАЖАТИЮ. Тридцать полных матриц подряд это
 * стена: в каждой пятнадцать показателей на семь колонок. Вопрос в этом
 * режиме один — «какой товар портит картину», и отвечает на него список.
 */
/*
 * ⚠️ СПИСОК ЛИСТАЕТСЯ ВНУТРИ СЕБЯ — так же, как лента уведомлений.
 *
 * Потолок в сто товаров убран (он прикрывал плохой запрос, а не защищал от
 * объёма), и теперь в списке бывает и двести пятьдесят строк. По 64 пикселя
 * это шестнадцать экранов: чтобы вернуться к поиску и переключателю периода,
 * пришлось бы прокрутить их все обратно.
 *
 * Высота в vh, а не в пикселях: «сколько влезает» на ноутбуке и на большом
 * мониторе — разные числа, а смысл один.
 */
.rnp-art-list {
  max-height: 68vh; overflow-y: auto;
  /* Прокрутка не перекидывается на страницу, когда список докручен до конца:
     иначе колесо «проваливается» и уезжает весь раздел — ровно то, от чего
     уходили. */
  overscroll-behavior: contain;
  /* Место под полосу прокрутки, чтобы строки не дёргались, когда она
     появляется и исчезает. */
  scrollbar-gutter: stable;
  /* Небольшое поле справа: иначе полоса прокрутки ложится на тень карточек. */
  padding-right: 4px;
}

.rnp-art {
  margin-bottom: var(--sp-3); padding: 0; overflow: hidden;
  /*
   * ⚠️ БРАУЗЕР НЕ СЧИТАЕТ ТО, ЧЕГО НЕ ВИДНО.
   *
   * Потолок на число товаров убран, и в списке бывает и две тысячи строк.
   * Замерено: 2000 строк это 58 000 узлов, и прокрутка стоила 38 миллисекунд
   * на кадр вместо шестнадцати — заметная дёрганность, хотя данные пришли за
   * доли секунды. Узкое место оказалось не в базе и не в сети, а в разметке.
   *
   * content-visibility: auto разрешает браузеру пропускать раскладку и
   * отрисовку строк за пределами экрана. Это одна строка стилей вместо
   * «виртуального списка» на сотни строк кода — а виртуальный список ещё и
   * ломает поиск по странице и доступность.
   *
   * ⚠️ contain-intrinsic-size ОБЯЗАТЕЛЕН. Без него у невидимой строки высота
   * ноль, полоса прокрутки считает список коротким и прыгает при каждом
   * появлении строки на экране. 64px — высота свёрнутой строки; точное
   * совпадение не требуется, важен порядок величины.
   *
   * Браузер без поддержки просто игнорирует обе строки и работает как раньше.
   */
  content-visibility: auto;
  contain-intrinsic-size: auto 64px;
}
/*
 * ⚠️ ШАПКА СТРОКИ ПРИЛИПАЕТ ПРИ ПРОКРУТКЕ РАСКРЫТОГО ТОВАРА. Полная матрица
 * выше экрана, и без этого, дочитав её до середины, уже не видно, о каком
 * товаре речь — а на экране рядом ещё двести сорок девять других.
 */
.rnp-art.is-open .rnp-art-row {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface2);
}
.rnp-art-row {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; padding: 10px var(--sp-4);
  background: none; border: none; cursor: pointer; text-align: left;
  color: inherit; font: inherit;
  transition: background var(--t-fast) var(--ease);
}
.rnp-art-row:hover { background: var(--surface2); }

/* Фото фиксированного размера: разнокалиберные карточки WB иначе развалили бы
   ряд, и метрики в соседних строках перестали бы стоять друг под другом. */
.rnp-art-photo {
  width: 44px; height: 44px; flex: none;
  border-radius: 8px; object-fit: cover;
  background: var(--surface3); border: 1px solid var(--border);
}
/* Место под фото держим всегда: без него строка без картинки съезжает влево
   относительно соседних, и список выглядит сломанным. */
.rnp-art-photo-none { display: block; }

.rnp-art-name { min-width: 160px; flex: 1 1 160px; overflow: hidden; }
.rnp-art-title {
  display: block; font-weight: var(--fw-semi); font-size: var(--fs-footnote);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rnp-art-sub {
  display: block; font-size: var(--fs-caption); color: var(--text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.rnp-art-metrics {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  margin-left: auto;
}
.rnp-art-m { display: flex; flex-direction: column; align-items: flex-end; min-width: 64px; }
.rnp-art-m-l {
  font-size: var(--fs-caption2); color: var(--text-3);
  text-transform: uppercase; letter-spacing: .04em;
}
.rnp-art-m-v { font-size: var(--fs-footnote); font-weight: var(--fw-semi); font-variant-numeric: tabular-nums; }
/*
 * ⚠️ ЦВЕТ ТОЛЬКО У ДРР. Он должен что-то значить: ДРР выше 30% — это разговор
 * про деньги, а «много показов» само по себе ни хорошо, ни плохо. Раскрасив
 * все числа, мы не выделим ни одного.
 */
/* ⚠️ Токены темы, а не свои коды цветов: --green/--yellow/--red подобраны под
   светлую и тёмную тему по контрасту (см. tools/test-contrast.js). Вписанный
   сюда #d98324 читался бы на тёмном фоне как грязь. */
.rnp-art-m.is-ok .rnp-art-m-v { color: var(--green); }
.rnp-art-m.is-warn .rnp-art-m-v { color: var(--yellow); }
.rnp-art-m.is-bad .rnp-art-m-v { color: var(--red); }

.rnp-art-chev {
  flex: none; width: 8px; height: 8px; margin-left: var(--sp-2);
  border-right: 2px solid var(--text-3); border-bottom: 2px solid var(--text-3);
  transform: rotate(45deg); transform-origin: center;
  transition: transform var(--t-fast) var(--ease);
}
.rnp-art.is-open .rnp-art-chev { transform: rotate(-135deg); }
.rnp-art .rnp-scroll { border-top: 1px solid var(--border); }

/* На узком экране метрики уезжают под название, а не режутся по краю. */
@media (max-width: 860px) {
  .rnp-art-row { flex-wrap: wrap; }
  .rnp-art-metrics { margin-left: 0; width: 100%; gap: var(--sp-3); }
  .rnp-art-m { min-width: 56px; align-items: flex-start; }
}

.rnp-block { margin-bottom: var(--sp-4); }
.rnp-block-head { margin-bottom: var(--sp-3); }
.rnp-block-title { font-size: var(--fs-callout); font-weight: var(--fw-bold); }
.rnp-block-sub { font-size: var(--fs-caption); color: var(--text-3); margin-top: 2px; }

/* Таблица широкая по замыслу: колонок столько, сколько периодов. Прокрутка
   горизонтальная, а первые две колонки закреплены — иначе, уехав вправо,
   перестаёшь понимать, какая это метрика. */
.rnp-scroll { overflow-x: auto; }
.rnp-table {
  width: 100%; border-collapse: separate; border-spacing: 0;
  font-size: var(--fs-footnote); font-variant-numeric: tabular-nums;
}
.rnp-table th, .rnp-table td {
  padding: 7px 12px; text-align: right; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
.rnp-table thead th {
  position: sticky; top: 0; z-index: 2;
  /* ⚠️ Шапка тёмная, а не серая. Серая читалась как выключенная: названия
     колонок — это навигация по таблице, к ним возвращаются глазами всё
     время, пока её изучают. */
  background: var(--surface3); color: var(--text);
  font-size: var(--fs-caption); font-weight: var(--fw-semi);
  text-transform: uppercase; letter-spacing: .04em;
}
.rnp-table tbody tr:last-child th, .rnp-table tbody tr:last-child td { border-bottom: none; }
.rnp-table tbody tr:hover td, .rnp-table tbody tr:hover .rnp-metric { background: var(--surface3); }

.rnp-table .rnp-metric {
  position: sticky; left: 0; z-index: 1;
  background: var(--surface); text-align: left;
  font-weight: var(--fw-medium); color: var(--text-2);
  min-width: 150px;
}
.rnp-table thead .rnp-metric { z-index: 3; background: var(--surface3); }
.rnp-table .rnp-total {
  position: sticky; left: 150px; z-index: 1;
  background: var(--surface); font-weight: var(--fw-semibold);
  border-right: 1px solid var(--border);
  min-width: 96px;
}
.rnp-table thead .rnp-total { z-index: 3; background: var(--surface3); }

.rnp-v { color: var(--text); }
/* Изменение к предыдущему периоду — мелко и рядом с числом, как приписка.
   Цвет по СМЫСЛУ: рост расхода и ДРР плохой, рост показов хороший. */
.rnp-d {
  font-style: normal; font-size: var(--fs-caption2);
  font-weight: var(--fw-semibold); margin-left: 5px;
}
.rnp-d.up   { color: var(--accent-text); }
.rnp-d.down { color: var(--red); }


/* Числа: тяжёлые и моноширинные. Golos Text даёт широкие цифры — в таблице
   они читаются с расстояния, ради этого шрифт и выбран. */
.mon-sum, .kpi-num, .rnp-card-num, .res-sum-num,
.mon-seg-num, .mon-bar-low, .size-qty, .rnp-total, .cab-chip-count {
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/*
 * ⚠️ ЗНАЧЕНИЯ В ЯЧЕЙКАХ РНП — ОБЫЧНЫМ ВЕСОМ, А НЕ ЖИРНЫМ.
 *
 * Они стояли в общем списке «тяжёлых чисел» и рисовались весом 800. В карточке
 * с двумя-тремя числами это верно: жирное число там одно и оно главное. В
 * таблице на пятнадцать строк и семь колонок жирные ВСЕ сто числа — и не
 * выделяется ни одно. Ударение, поставленное на каждое слово, перестаёт быть
 * ударением.
 *
 * Жирным остаётся колонка «За период»: она одна на строку, и она — итог.
 */
.rnp-v {
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Заголовки разделов — плотнее по трекингу, иначе широкий Golos «расползается» */
.section-title, .page-head h2, .rnp-block-title,
.tasks-panel-title, .cab-name {
  font-weight: var(--fw-bold);
  letter-spacing: -.02em;
}

/* Подписи и сноски — средний вес: они служебные, но должны читаться */
.kpi-label, .mon-seg-name, .form-group label, .tip-head {
  font-weight: var(--fw-semi);
}

/* Длинные пояснения — обычный вес и просторный интерлиньяж */
.page-head p, .res-hint, .tip-row, .kpi-foot, .mon-bar-sub {
  font-weight: var(--fw-regular);
  line-height: 1.5;
}

/* ── Опоры: три места, которые держат взгляд ────────────────────────────────
   Всё остальное сознательно оставлено лёгким. Утяжелять фон бессмысленно:
   выделено то, что тяжелее СОСЕДЕЙ, а не то, что тяжёлое само по себе. */

/* 1. Меню — по нему перемещаются по разделам */
.nav-btn { font-weight: 600; letter-spacing: -.01em; }
.nav-btn.active { font-weight: 800; }

/*
 * 2. Шапки колонок — по ним ищут нужный столбец в таблице.
 *
 * ⚠️ ЦВЕТ ТЁМНЫЙ, А НЕ СЕРЫЙ. Серые названия читались как выключенные, хотя
 * это навигация по таблице: к ним возвращаются глазами всё время, пока её
 * изучают. Правка в правиле выше по файлу не срабатывала — здесь стоял дубль
 * того же селектора, и он перебивал её «через файл».
 */
.stock-table th,
.rnp-table thead th,
.mon-feed th {
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--text);
}
/* Первая колонка шапки — по тому же правилу: у неё свой класс, который иначе
   остаётся серым и выбивается из ряда. */
.rnp-table thead .rnp-metric,
.rnp-table thead .rnp-total { color: var(--text); }

/* 3. Цифры — ради них сюда и заходят. Моноширинные, иначе колонка дёргается
      при каждом обновлении и глаз заново ищет строку.

   ⚠️ .rnp-v ЗДЕСЬ БОЛЬШЕ НЕТ. Это правило — вторая копия такого же списка
   выше по файлу, и оно перебивало тамошнюю правку: значения в таблице РНП
   продолжали рисоваться весом 800, хотя вес был снижен. Дубли селекторов
   именно так и ломают правки — молча, «через файл». */
.mon-sum, .kpi-num, .rnp-card-num, .res-sum-num,
.mon-seg-num, .mon-bar-low, .size-qty, .rnp-total, .cab-chip-count {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Заголовки разделов: широкий Golos без уплотнения «расползается» */
.section-title, .page-head h2, .rnp-block-title,
.tasks-panel-title, .cab-name { letter-spacing: -.02em; }

/* Пояснения и сноски — обычным весом. Это то, что читают, а не ищут. */
.page-head p, .res-hint, .tip-row, .kpi-foot, .mon-bar-sub, .mon-seg-sub {
  font-weight: 400;
  line-height: 1.5;
}


/* ── Гистограмма заказов и цифры по часам рядом ─────────────────────────────
   График показывает, КОГДА приходят заказы; колонка справа — насколько это
   много. Линиями поверх свечей это не рисуем: они перечёркивают данные, а на
   38 px высоты три линии и столбики сливаются в кашу. */
.pulse-row { display: flex; align-items: center; gap: var(--sp-3); }
.pulse-chart { flex: 1; min-width: 0; }

.pulse-hours {
  flex-shrink: 0; display: flex; flex-direction: column; gap: 1px;
  font-size: var(--fs-caption2); font-variant-numeric: tabular-nums;
}
.pulse-hours .ph-row { display: flex; gap: var(--sp-2); justify-content: space-between; }
.pulse-hours span { color: var(--text-3); }
.pulse-hours b { font-weight: 700; color: var(--text-2); }
/* Средний час — опора, с которой сравнивают остальные два */
.pulse-hours .ph-avg b { font-weight: 800; color: var(--text-1); }

/* Кому анимация мешает — меню меняет ширину без переходов и без лесенки */
@media (prefers-reduced-motion: reduce) {
  .sidebar, .nav-text, .nav-group-label span, .nav-btn .nav-icon { transition: none; }
  .sidebar:hover .nav-btn .nav-text { transition-delay: 0s; }
  .nav-btn:hover .nav-icon, .nav-btn.active .nav-icon { transform: none; }
}

/* --- Закрепление меню -----------------------------------------------------
   Меню, которое раскрывается по наведению, удобно ровно до того момента, пока
   не понадобилось часто прыгать между разделами: тогда оно захлопывается на
   каждом движении мыши. Кружок «статичная» это выключает.

   Закреплённое меню ведёт себя как обычное фиксированное: оно РАЗДВИГАЕТ
   контент, а не накрывает его. Накрывать имеет смысл только пока меню
   временное — постоянно закрытый кусок страницы это уже потеря места. */
.nav-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; height: 16px;
  /* Тот же отступ, что у подписей групп ниже: заголовки меню должны стоять от
     своих пунктов на одном расстоянии, иначе взгляд читает разный отступ как
     разную степень родства. */
  margin-bottom: 18px;
}

/* Главная страница — отдельным блоком над «Сервисами», без подписи: она тут
   одна, и называть группу из одного пункта нечем и незачем. */
.nav-group-home { margin-bottom: 2px; }

.nav-pin {
  display: inline-flex; align-items: center; gap: 6px;
  border: none; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-caption2); letter-spacing: .4px;
  color: var(--text-muted); text-transform: none;
  flex-shrink: 0;
}
.nav-pin-dot {
  width: 11px; height: 11px; border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  background: transparent;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease);
  flex-shrink: 0;
}
.nav-pin:hover .nav-pin-dot { border-color: var(--accent); }
.nav-pin:hover { color: var(--text-2); }

/* Включено: кружок залит акцентом и подсвечен кольцом — состояние должно
   читаться с одного взгляда, без вчитывания в подпись. */
.sidebar.pinned .nav-pin-dot {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
.sidebar.pinned .nav-pin { color: var(--accent-text); font-weight: var(--fw-semi); }

/* Закреплённое меню открыто всегда и держит контент справа от себя */
.sidebar.pinned { width: var(--sidebar-w); box-shadow: none; }
.sidebar.pinned .nav-text,
.sidebar.pinned .nav-group-label span { opacity: 1; transform: none; }
body.nav-pinned .main { margin-left: var(--sidebar-w); }

/* В свёрнутом виде заголовок и кружок не помещаются в 72px и скрыты вместе с
   остальными подписями. Это нормально: чтобы нажать кружок, к меню всё равно
   надо подвести курсор — а от этого оно и раскрывается. */

/* --- Меню на телефоне -----------------------------------------------------
   Постоянная полоса тут не годится ни в каком виде: развёрнутая съедает две
   трети экрана (232 из 375), свёрнутая — пятую часть, и всё ради значков,
   по которым на ходу не угадаешь раздел. Приложение и так плотное: таблицы,
   графики, суммы. Ширина здесь дороже быстрого доступа к меню.

   Поэтому шторка: контент занимает весь экран, меню выезжает по кнопке и
   накрывает его вместе с затемнением. Закрывается по фону, по выбору пункта
   и по Escape — на телефоне промахнуться по маленькому крестику слишком легко.

   Кнопка-бургер живёт в шапке и на больших экранах скрыта. */
.nav-burger { display: none; }

@media (max-width: 768px) {
  .nav-burger {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 40px; height: 40px; padding: 0 9px; margin-left: -8px;
    border: none; background: none; cursor: pointer; border-radius: var(--radius-sm);
    flex-shrink: 0;
  }
  .nav-burger span {
    display: block; height: 2px; border-radius: 2px;
    background: var(--text-2);
    transition: transform .22s var(--ease), opacity .18s var(--ease);
  }
  .nav-burger:hover span { background: var(--text); }
  /* Открыто — три полоски складываются в крестик: одна кнопка на оба действия,
     и по её виду сразу ясно, что она сделает. */
  body.nav-open .nav-burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  body.nav-open .nav-burger span:nth-child(2) { opacity: 0; }
  body.nav-open .nav-burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  .sidebar {
    width: var(--sidebar-w);
    transform: translateX(-100%);
    transition: transform .26s cubic-bezier(.32, .72, 0, 1);
    box-shadow: none;
    z-index: 200;
  }
  body.nav-open .sidebar {
    transform: none;
    box-shadow: 0 8px 48px rgba(0, 0, 0, .28);
  }
  /* Подписи в шторке видны всегда: она и открывается только целиком */
  .sidebar .nav-text, .sidebar .nav-group-label span { opacity: 1; transform: none; }

  /* Контент во всю ширину — ради этого всё и затевалось */
  .main, body.nav-pinned .main { margin-left: 0; }

  /* Закрепление — про мышь. На телефоне шторка и так закрывается по выбору
     пункта, а «закрепить» означало бы вечно перекрытый экран. */
  .nav-pin { display: none; }

  .nav-scrim {
    position: fixed; inset: 0; z-index: 150;
    background: rgba(0, 0, 0, .38);
    opacity: 0; transition: opacity .26s var(--ease);
  }
  body.nav-open .nav-scrim { opacity: 1; }

  .topbar { padding: 0 var(--sp-4); }
  .content { padding: var(--sp-4); }

  /* Карточка кабинета: прибыль уходит под кнопки ────────────────────────────
     На широком экране «~ЧП сегодня» стоит в одной строке с названием магазина,
     и это правильно — сумма читается как свойство кабинета. На телефоне в ту
     же строку не помещаются ни название, ни сумма, ни три кнопки: название
     ломается посреди слова («MasterSho / p»), а сумма сжимается в столбик.

     Поэтому на узком экране строка переносится, и прибыль встаёт ПОД
     карандашом и крестиком, по правому краю — под теми кнопками, рядом с
     которыми она стояла раньше. */
  .cab-head-row { flex-wrap: wrap; }
  .cab-head-row > div:first-child { flex: 1 1 auto; }
  .cab-card-actions { order: 1; }
  .cab-profit {
    order: 2; width: 100%; margin-left: 0;
    justify-content: flex-end; align-items: center;
  }
  /* Зазор от карандаша был нужен, когда сумма стояла слева от кнопок.
     Под ними он сдвигает её от правого края и ломает выравнивание. */
  .cab-profit-pill { margin-right: 0; }

  /* Строка раздела на телефоне переносится, а не сжимает переключатель */
  .sec-bar { gap: var(--sp-2); }
  .sec-bar-note { margin-left: 0; text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar, .nav-scrim, .nav-burger span { transition: none; }
}

/* --- Панель «Новое напоминание» -------------------------------------------
   Заголовок и переключатель разделов в одной строке. Когда панель узкая,
   переключатель уходит под заголовок, а не сжимает его до многоточия. */
.rem-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap; margin-bottom: 14px;
}
.rem-views { margin-bottom: 0; }

/* Точка на вкладке: авто-отчёт включён. Иначе, чтобы это узнать, надо
   переключиться на вкладку — состояние прячется от того, кто его задал. */
.rem-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-left: 6px; vertical-align: middle;
}
/* ⚠️ Обязательно: display в правиле выше перебивает атрибут hidden, и точка
   горела бы всегда — сообщая о включённом отчёте, которого нет. */
.rem-dot[hidden] { display: none; }

/* Переключатель режима — по центру и по ширине содержимого. Растянутый на всю
   панель, он читался как две половины формы, а не как выбор одного из двух. */
/* По центру и по ширине содержимого. auto-поля работают только у блочного
   бокса, поэтому flex + fit-content, а не inline-flex. */
.view-toggle.rem-mode {
  display: flex; width: fit-content;
  margin-left: auto; margin-right: auto;
}

/* Плитки «тип отчёта»: значок над подписью, одинаковая высота.
   Раньше значок и текст шли в строку, «PDF клиенту» переносился на вторую
   строку — и плитка становилась выше соседних. Ряд выглядел сломанным. */
.radio-group.radio-tiles { align-items: stretch; }
.radio-tiles .radio-opt {
  flex-direction: column; justify-content: center; gap: 5px;
  padding: 10px 6px; min-height: 62px;
  font-size: var(--fs-footnote); line-height: 1.2; text-align: center;
}
.radio-tiles .radio-opt .ic { width: 18px; height: 18px; }
.radio-tiles .radio-opt span { display: block; }

/* Задача, поставленная на неделю, а не на конкретный день. Показываем её и
   сегодня — иначе в любой день кроме понедельника доска пустовала бы при
   полном списке дел, — но помечаем, чтобы «сегодня» не смешивалось с
   «когда-нибудь на этой неделе». */
.task-weekly {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 99px; background: var(--surface3); color: var(--text-muted);
  font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  vertical-align: middle; white-space: nowrap;
}

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

   Теперь подложка одна на весь переключатель и ЕЗДИТ. Это не украшение: взгляд
   следует за движущимся объектом и сам приходит туда, где теперь активный
   пункт — не нужно перечитывать оба слова, чтобы понять, что выбрано.

   Подложка позиционируется из JS (initSegmented): ширина пунктов разная и
   заранее неизвестна — «Экономика» и «РНП» отличаются втрое. */
.seg-track { position: relative; }
.seg-thumb {
  position: absolute; top: 0; left: 0; height: 100%;
  border-radius: 999px;
  background: var(--surface); box-shadow: var(--shadow-sm);
  transform: translateX(var(--seg-x, 0)); width: var(--seg-w, 0);
  transition: transform .32s cubic-bezier(.32, .72, 0, 1),
              width .32s cubic-bezier(.32, .72, 0, 1);
  pointer-events: none; z-index: 0;
}
/* Первая отрисовка не должна «прилетать» из левого угла */
.seg-thumb.seg-init { transition: none; }

.seg-track > button { position: relative; z-index: 1; }

/* Собственный фон активного пункта убираем — его роль теперь у подложки */
.seg-track > button.active,
.seg-track > .an-view.active { background: transparent; box-shadow: none; }

/* Скругление: у «Экономика / РНП» углы были почти прямыми и спорили с
   остальным интерфейсом, где всё скруглено сильнее. */
.an-views { border-radius: 999px; }
.an-view { border-radius: 999px; }
.view-toggle { border-radius: 999px; }
.view-toggle button { border-radius: 999px; }
.rnp-scale, .rnp-scale button { border-radius: 999px; }

/* Подложка у переключателя режима напоминания — на акценте, как было */
.view-toggle .seg-thumb { background: var(--accent-fill-a); box-shadow: none; }
.view-toggle > button.active { color: var(--accent-ink); }

@media (prefers-reduced-motion: reduce) {
  .seg-thumb { transition: none; }
}

/* --- Фото товара в списке выбора ------------------------------------------
   Артикул HOME-DSK-02 ничего не говорит о том, что это за вещь. Картинка
   опознаётся мгновенно, код приходится вспоминать. */
.ui-row-photo {
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: 8px; object-fit: cover;
  background: var(--surface3); border: 1px solid var(--border);
}
/* Фото нет — буквы артикула. Пустой квадрат ломал бы ряд сильнее, чем
   осмысленная заглушка. */
.ui-row-photo-none {
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-caption2); font-weight: var(--fw-bold);
  color: var(--text-3); letter-spacing: .02em;
}

/* Колесо мыши не должно проваливаться на страницу под открытым меню: список
   докручивается до конца и останавливается, а не утаскивает за собой всю
   страницу (и не закрывает попап обработчиком прокрутки). */
.ui-pop, .ui-pop-list { overscroll-behavior: contain; }

/* --- Дата, время и срочность задачи ---------------------------------------
   Свёрнуто по умолчанию: у большинства задач дата сегодняшняя, и подтверждать
   её каждый раз — мешать быстрому вводу. Заголовок при этом всегда показывает
   выбранное, чтобы не пришлось разворачивать ради проверки. */
.task-when { margin-top: 8px; }
.task-when > summary {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 10px; border-radius: var(--radius-sm);
  background: var(--surface2); border: 1px solid var(--border);
  cursor: pointer; list-style: none;
  font-size: var(--fs-footnote); color: var(--text-2);
}
.task-when > summary::-webkit-details-marker { display: none; }
.task-when > summary:hover { border-color: rgba(var(--accent-rgb), .35); color: var(--text); }
.task-when > summary .ic { width: 15px; height: 15px; flex-shrink: 0; opacity: .8; }
.task-when[open] > summary { border-color: rgba(var(--accent-rgb), .35); color: var(--text); }

.task-when-body {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px; margin-top: 6px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.task-when-row { display: flex; align-items: center; gap: 8px; }
.task-when-row > label {
  width: 74px; flex-shrink: 0;
  font-size: var(--fs-caption); color: var(--text-2);
}
.task-when-row input[type=date], .task-when-row input[type=time] { flex: 1; min-width: 0; }
.task-when-check {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--fs-caption); color: var(--text-2); cursor: pointer;
  padding-left: 2px;
}
.task-prio { flex: 1; }
.task-prio button { flex: 1; }

/* Время и срок рядом с названием: задача, растянутая на неделю, иначе выглядит
   в списке дня так же, как сегодняшняя, и непонятно, почему она не уходит. */
.task-when-badge {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 99px; background: var(--accent-weak); color: var(--accent-text);
  font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  vertical-align: middle; white-space: nowrap;
}

/* Заголовок включённого фильтра. Невидимый фильтр — худший вид фильтра:
   человек решит, что данные пропали. */
.rem-filter {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3);
  padding: 7px 12px; border-radius: var(--radius-sm);
  background: var(--accent-weak); color: var(--text-2);
  font-size: var(--fs-footnote);
}
.rem-filter b { color: var(--text); font-weight: var(--fw-semi); }

/* Пока открыт выпадающий список, боковая навигация не выезжает по наведению.
   Путь курсора к списку идёт вдоль левого края экрана, и панель разворачивалась
   прямо под руку, накрывая то, ради чего список и открывали. Закреплённое меню
   (.pinned) это не касается — оно и так открыто. */
body.pop-open .sidebar:not(.pinned):hover,
body.pop-open .sidebar:not(.pinned):focus-within {
  width: var(--sidebar-rail);
  box-shadow: none;
}
body.pop-open .sidebar:not(.pinned):hover .nav-text,
body.pop-open .sidebar:not(.pinned):hover .nav-group-label span {
  opacity: 0; transform: translateX(-8px);
}

/* Увеличение фото по наведению — как в личном кабинете WB. В списке значок
   34x34, по нему товар узнаётся не всегда; крупный снимок решает это, не
   занимая места в самом списке. */
.photo-zoom {
  position: fixed; z-index: 1000;
  /* Никакой рамки и подложки: показываем сам снимок. Оправа вокруг картинки —
     это второй прямоугольник поверх первого, он спорит с ней за внимание, а
     сказать ему нечего. Высота по картинке, а не квадрат: снимки у WB
     вертикальные (3:4), и в квадрате по бокам оставались пустые поля. */
  /* Рамка по картинке, а не картинка по рамке: ширина задаётся снимком, иначе
     у высокого кадра сверху и снизу оставались бы прозрачные поля, а тень
     обводила бы пустоту. */
  width: max-content; height: auto; padding: 0;
  background: none; border: 0;
  /* Тень нужна: без неё светлый снимок сливается с белой карточкой под ним и
     теряет края. Скругление на самой картинке, а не на пустой оправе. */
  border-radius: 18px; box-shadow: 0 18px 48px rgba(15, 23, 42, .28);
  pointer-events: none;                 /* иначе мышь «зацепится» и снимок замигает */
  animation: photo-zoom-in .13s cubic-bezier(.2, .9, .3, 1);
}
.photo-zoom img {
  display: block; width: auto; height: auto;
  max-width: 232px; max-height: 340px;
  border-radius: inherit;
}
:root:not([data-theme="light"]) .photo-zoom,
[data-theme="dark"] .photo-zoom { box-shadow: 0 18px 48px rgba(0, 0, 0, .7); }
@keyframes photo-zoom-in { from { opacity: 0; transform: scale(.94); } }

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

@media (prefers-reduced-motion: reduce) { .photo-zoom { animation: none; } }

/* Просроченная задача. Она тянется в сегодняшний список, пока её не отметят,
   но выдавать её за сегодняшнюю нельзя — человек должен видеть, что она висит
   со вчера. Красным помечаем саму пометку, а не весь текст: перекрашенная
   строка читается как ошибка системы, а не как невыполненное дело. */
.task-overdue {
  display: inline-block; margin-left: 6px; padding: 1px 7px;
  border-radius: 99px; background: var(--red-weak);
  color: var(--red); font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  vertical-align: middle; white-space: nowrap;
}
.task-item.overdue { border-left: 2px solid var(--red); }
.tip-row.tip-overdue b { color: var(--red); }

/* Откуда взялась задача. «Алерт» появился сам, по срабатыванию триггера —
   не отличив его от своего дела, человек не понимает, откуда взялась работа,
   которую он не заводил.
   Своя задача подписана приглушённо: это норма, а не событие. */
.task-src {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 99px; background: var(--surface3); color: var(--text-2);
  font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  vertical-align: middle; white-space: nowrap;
}
.task-src-alert {
  background: var(--yellow-weak); color: var(--yellow); font-weight: var(--fw-semi);
  /* Значок и слово в одной строке, по центру: без этого огонь съезжает вниз
     относительно текста, потому что у него своя базовая линия. */
  display: inline-flex; align-items: center; gap: 4px;
  /* Подсказка при наведении обязательна, поэтому курсор должен на это
     намекать: иначе никто не узнает, что здесь есть пояснение. */
  cursor: help;
}

/* ⚠️ ОГОНЬ — ЕДИНСТВЕННАЯ ЦВЕТНАЯ ИКОНКА В ПРОЕКТЕ, И ЭТО НАМЕРЕННО.
 *
 * Остальные контурные и наследуют цвет текста (см. .ic). Огонь обязан
 * бросаться в глаза: на доске в двадцать задач серая подпись «алерт» в ряду
 * одинаковых серых подписей не читается — её замечают только когда ищут.
 *
 * Отсюда сброс наследуемых правил: fill идёт из градиента в самом symbol,
 * stroke не нужен вовсе. Без этих двух строк .ic перекрасил бы огонь в серый
 * контур и смысл значка исчез бы. */
.ic-flame {
  fill: initial; stroke: none;
  width: 1.05em; height: 1.05em; vertical-align: middle;
}
/* В подсказке карточки огонь стоит вплотную перед названием задачи. В бейдже
   отступ даёт flex-gap, а здесь его нет — и текст прилипал к пламени. */
.tip-row .ic-flame { margin-right: .3em; }

/* Воронка FBS: поступило / обработано / в доставке. Три числа в одну строку —
   это путь заказа, и провал виден по тому, где ряд обрывается. Те же числа,
   разнесённые по карточке, пришлось бы сопоставлять глазами. */
/* Кнопка: по цифрам проваливаются в список заданий этого кабинета. Выглядит
   как цифры, ведёт себя как кнопка — поэтому отклик на наведение обязателен,
   иначе о том, что сюда можно нажать, никто не догадается. */
.fbs-funnel {
  display: flex; align-items: baseline; gap: 6px;
  /* По содержимому, а не во всю ширину карточки. Кнопка шириной в блок ловит
     нажатия там, где смотреть не на что: человек метит в цифры, а подсветка
     загорается через всю карточку и выглядит как промах. */
  align-self: flex-start; width: fit-content;
  /* Отрицательный левый отступ гасит паддинг кнопки: ряд должен стоять под
     чипами, а не с приступочкой. */
  margin-left: -6px;
  padding: 5px 8px 5px 6px; border: none; background: none;
  border-radius: var(--radius-sm); cursor: pointer; font: inherit;
  font-variant-numeric: tabular-nums;
  transition: background var(--t-fast) var(--ease);
}
.fbs-funnel:hover { background: var(--surface3); }
.fbs-funnel:active { transform: translateY(1px); }
/* Разделитель — тонкая вертикальная черта, а не косая: косая читается как
   «одно из другого» (дробь), а здесь три независимых числа. */
.fbs-funnel i {
  display: inline-block; width: 1px; height: 15px;
  background: var(--border-strong); margin: 0 2px;
  align-self: center;
}
.fbs-num {
  font-size: var(--fs-title3); font-weight: 800; letter-spacing: -.02em;
  color: var(--text-2);
}
/* Поступившие — единственное число, требующее действия прямо сейчас.
   Обработанные и отгруженные уже сделаны, они справочные и остаются серыми. */
.fbs-num.fbs-hot { color: var(--accent-text); }
/* Прочерк — «не смогли спросить», а не ноль. Приглушаем, чтобы не выглядел
   как значение. */
.fbs-num.fbs-none { color: var(--text-3); font-weight: var(--fw-medium); }


/* Карточка кабинета — точка отсчёта для кнопок в углу */
.cabinet-card { position: relative; }

/* --- Шкала жизни заказа FBS ------------------------------------------------
   0 — 13 — 42 — 48 — 54 — 60 часов и метка «сейчас». Одна шкала вместо трёх
   колонок («сколько ждёт», «полоса», «правило»): это одно и то же знание, и
   разнесённое по колонкам оно заставляло читать три раза то, что видно
   с одного взгляда. */
/* Шкала короткая: 125px под саму полосу плюс место под число часов справа.
   Длинная полоса занимала треть таблицы, а информации в ней ровно столько же. */
.fbs-bar { position: relative; width: 165px; padding-right: 40px; cursor: help; }
.fbs-bar-track {
  position: relative; height: 6px; border-radius: 3px;
  background: var(--surface3); overflow: hidden;
}
/* Заливаем ПРОЙДЕННОЕ, а не раскрашиваем всю шкалу зонами: так полоса сама
   отвечает на вопрос «где заказ сейчас», без чтения цифр. Цвет — по порогу,
   который заказ уже перешёл. */
.fbs-fill {
  position: absolute; left: 0; top: 0; height: 100%;
  border-radius: 3px;
  transition: width var(--t-slow, .3s) var(--ease);
}
/* Светофор: зелёный → жёлтый → оранжевый → красный. Раньше в середине стоял
   серый (42–48 ч, «база»), и лестница обрывалась именно там, где решение ещё
   можно принять. */
.fbs-fill-ok    { background: var(--accent); }
.fbs-fill-watch { background: var(--fbs-watch); }
.fbs-fill-soon  { background: var(--fbs-warn); }
.fbs-fill-bad   { background: var(--red); }

/* ── Подсветка строки целиком ───────────────────────────────────────────────
   Полоса 6 пикселей внутри одной ячейки — весь сигнал, который был у строки.
   На двух десятках строк его не видно: глаз ищет цвет в ряду, а не в колонке.
   Поэтому цвет уходит на всю строку — слева полоска зоны, фон лёгкой заливкой.

   Заливка только там, где на кону деньги (жёлтая, оранжевая, красная).
   У зелёных строк одна полоска слева: их большинство, и залить их значит
   закрасить таблицу целиком и снова ничего не выделить. */
/* Полоска — внутренняя тень, а не border-left. У таблицы border-collapse:
   collapse, и при нём граница крайней ячейки встаёт серединой на край
   таблицы: половина ширины уходит за пределы и не видна. Тень рисуется
   внутри ячейки целиком и от схлопывания не зависит. */
.fbs-row-ok    > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.fbs-row-watch > td:first-child { box-shadow: inset 3px 0 0 var(--fbs-watch); }
.fbs-row-soon  > td:first-child { box-shadow: inset 3px 0 0 var(--fbs-warn); }
.fbs-row-bad   > td:first-child { box-shadow: inset 3px 0 0 var(--red); }

.fbs-row-watch > td { background: var(--fbs-watch-bg); }
.fbs-row-soon  > td { background: var(--fbs-soon-bg); }
.fbs-row-bad   > td { background: var(--fbs-bad-bg); }

/* Наведение должно быть заметно и на залитой строке: без своего правила
   общий ховер таблицы прячется под заливкой, и строка перестаёт откликаться.
   Тег + класс — вес выше, чем у заливки выше по файлу. */
tr.fbs-row:hover > td { background: var(--surface-hover); }

/* Пороги — тонкие риски прямо на шкале. Их цифры на узкой полосе сливались,
   но сами границы видеть нужно: между 47 и 49 часами разница в деньгах. */
.fbs-mark {
  position: absolute; top: 0; width: 1px; height: 100%;
  background: var(--surface); opacity: .85;
}

.fbs-bar-axis { position: relative; height: 13px; margin-top: 3px; }
.fbs-tick { position: absolute; top: 0; transform: translateX(-50%); }
.fbs-tick b {
  font-size: 9px; line-height: 1; font-weight: var(--fw-medium);
  color: var(--text-3); font-variant-numeric: tabular-nums;
  /* Подписи 42/48/54/60 стоят близко: без запрета переноса они складывались
     в нечитаемое «42485460». */
  white-space: nowrap;
}
/* Крайние метки не выносим за пределы шкалы */
.fbs-tick:first-child { transform: none; }
.fbs-tick:last-child { transform: translateX(-100%); }
.fbs-bar-val {
  position: absolute; right: 0; top: -4px;
  font-weight: 800; font-size: var(--fs-footnote);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Цвет комиссии говорит, что с ней происходит, до чтения суммы: скидка —
   зелёная, база — обычная, штраф — красная. Иначе «сэкономил» и «переплатил»
   выглядят одинаково. */
.fbs-comm-good { color: var(--accent-text); }
.fbs-comm-bad  { color: var(--red); }

/* Правила — один раз над таблицей. В строках они повторялись бы дословно
   и раздували таблицу вчетверо. */
.fbs-legend {
  padding: 12px 14px; margin-bottom: var(--sp-4);
  border-radius: var(--radius-md);
  background: var(--surface2); border: 1px solid var(--border);
}
.fbs-legend-title {
  font-size: var(--fs-caption); font-weight: var(--fw-semi);
  color: var(--text-2); margin-bottom: 8px;
}
.fbs-legend-rows {
  display: flex; flex-wrap: wrap; gap: 6px var(--sp-4);
  font-size: var(--fs-caption); color: var(--text-2);
}
.fbs-legend-rows span { display: inline-flex; align-items: center; gap: 6px; }
.fbs-dot { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.fbs-dot-fast { background: var(--accent); }
/* Жёлтая ступень: та же, что у полосы в строке. Легенда и таблица обязаны
   говорить одно и то же — иначе легенда учит одному цвету, а строка красится
   другим, и доверия нет ни к той, ни к другой. */
.fbs-dot-watch { background: var(--fbs-watch); }
.fbs-dot-warn { background: var(--fbs-warn); }
.fbs-dot-bad  { background: var(--red); }

/* Категория — текстом вправо, как остальные колонки. Фото вынесено в свою
   колонку следом: внутри одной ячейки картинка и название толкали друг друга,
   и строки начинались с разного места. Отдельная колонка ставит все снимки на
   одну вертикаль — их можно просматривать сверху вниз, не читая названий. */
.fbs-subject { text-align: right; }
/* Фото — под своим заголовком, а не у левого края ячейки. `td.` в селекторе
   потому же, почему и везде: `.stock-table td` весит больше голого класса. */
td.fbs-photo { width: 1%; padding-left: var(--sp-3); text-align: center; }
.fbs-photo .ui-row-photo {
  display: block; width: 34px; height: 34px; border-radius: 9px;
}
/* Заголовок над снимками — по центру колонки, над самими картинками */
/* `.stock-table th` задаёт выравнивание по левому краю и весит больше голого
   класса — «Фото» так и стояло слева, над колонкой, выровненной по центру. */
th.fbs-photo-h { text-align: center; }


/* ── Кнопка обновления кабинета ────────────────────────────────────────────
   Погашенная кнопка не должна выглядеть сломанной: приглушаем, но оставляем
   различимой, и НЕ ставим курсор «запрещено» — под курсором она объясняет,
   когда оживёт, а перечёркнутый круг отбивает желание навести. */
.cab-refresh.is-cooling {
  opacity: .38;
  cursor: help;
  pointer-events: auto;          /* иначе подсказка не покажется */
}
.cab-refresh.is-cooling .ic { color: var(--text-3); }

/* Пока идёт запрос — значок крутится. Это единственный отрезок, когда человек
   действительно ждёт, и без движения кажется, что нажатие не сработало. */
.cab-refresh.is-spinning .ic { animation: cab-refresh-spin .8s linear infinite; }
@keyframes cab-refresh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .cab-refresh.is-spinning .ic { animation: none; opacity: .5; }
}


/* Свёрнутое меню: иконка по центру полосы. Без этого правила сдвиг паддинга
   влево (сделанный ради подписей) увёл бы иконки от середины, и ряд значков
   перестал бы выглядеть ровным столбиком. */
.sidebar:not(:hover):not(.pinned) .nav-btn { padding-left: 14px; }


/* Подпись «FBS» перед счётчиками. Три числа сами по себе не говорят, откуда
   они: на карточке рядом живут и задачи, и триггеры, и прибыль. */
.fbs-funnel-label {
  font-size: var(--fs-caption2); font-weight: var(--fw-heavy);
  letter-spacing: .6px; color: var(--text-2);
  margin-right: 2px;
}

/* ── FBS: карточка склада ──────────────────────────────────────────────────
   Склад — это не строка таблицы, а место: у него имя, регион, схема доставки
   и свой список товара. Поэтому карточка, а не ещё одна колонка в общем
   списке остатков. */
/* Склады в две колонки на широком экране.
   Карточка склада — узкая по природе: пять коротких колонок в таблице и две
   строки заголовка. В один столбец она занимала всю ширину монитора ради
   460 пикселей содержимого, и второй склад приходилось искать прокруткой.
   Два в ряд — это вдвое больше информации на экран без единого лишнего клика.
   min(520px, 100%) вместо 520px: на узком экране колонка не должна распирать
   страницу — эту ловушку мы уже ловили на карточках кабинетов. */
.wh-grid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(min(520px, 100%), 1fr));
  align-items: start;
}
.wh-card { margin-bottom: 0; }
/* В колонке таблица уже не «половина страницы» — там своя ширина по месту */
.wh-grid table.wh-stock-table { max-width: 100%; min-width: 0; }
.wh-grid .stock-table.table-framed td,
.wh-grid .stock-table.table-framed th { white-space: normal; }

.wh-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-3);
}
.wh-name {
  /* Имя склада — заголовок карточки, а не подпись к ней: на экране их
     несколько, и глаз должен цепляться за название, а не пересчитывать
     карточки сверху. */
  font-size: var(--fs-title3); font-weight: var(--fw-heavy); color: var(--text);
  letter-spacing: -.02em;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.wh-sub {
  font-size: var(--fs-caption); color: var(--text-2); margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
/* Схема доставки: FBS, DBS и EDBS живут рядом, но правила у них разные, и
   «пустой склад» значит для каждой своё. */
.wh-scheme {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi); letter-spacing: .4px;
  padding: 2px 7px; border-radius: 99px;
  background: var(--surface3); color: var(--text-2);
  cursor: help;
}
.wh-deleting {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  padding: 2px 7px; border-radius: 99px;
  background: var(--red-weak); color: var(--red);
}
/* Итог склада и кнопка удаления — одной группой справа. Кнопка приглушена и
   краснеет только под курсором: она рядом с числом остатка, и постоянно
   красный крест около «358 шт» читался бы как тревога, а не как действие. */
.wh-head-right { display: flex; align-items: flex-start; gap: var(--sp-2); }
.wh-del { color: var(--text-3); }
.wh-del:hover { background: var(--red-weak); color: var(--red); border-color: transparent; }

.wh-total {
  font-size: var(--fs-title3); font-weight: var(--fw-heavy);
  white-space: nowrap; text-align: right; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.wh-total span { font-size: var(--fs-caption); font-weight: var(--fw-semi); color: var(--text-2); }
/* Пустой склад — состояние, а не поломка: приглушаем, но не красим тревогой.
   Красный здесь означал бы «сломалось», а склад может стоять пустым намеренно. */
.wh-total-empty { font-size: var(--fs-callout); color: var(--text-3); font-weight: var(--fw-semi); }
.wh-skus { display: block; font-size: var(--fs-caption); font-weight: var(--fw-semi); color: var(--text-2); }


/* Свёрнутое меню: подписи групп схлопываются, значки поднимаются выше.
   При наведении раскрываются обратно — и значки съезжают на свои места. */
.sidebar:not(:hover):not(.pinned) .nav-group-label,
.sidebar:not(:hover):not(.pinned) .nav-head {
  height: 0;
  margin-bottom: 0;
}
.nav-head {
  overflow: hidden;
  transition: height .26s cubic-bezier(.32, .72, 0, 1),
              margin-bottom .26s cubic-bezier(.32, .72, 0, 1);
}
/* Кому анимация мешает — оставляем место под подписи всегда: без движения
   перестановка значков превращается в тот самый прыжок, ради ухода от
   которого всё и делалось. */
@media (prefers-reduced-motion: reduce) {
  .sidebar:not(:hover):not(.pinned) .nav-group-label { height: 14px; margin-bottom: 18px; }
  .sidebar:not(:hover):not(.pinned) .nav-head { height: 16px; margin-bottom: 18px; }
}

/* ── Юнит-калькулятор ──────────────────────────────────────────────────────
   Поля в четыре колонки по смыслу: товар, габариты, расходы, кабинет.
   Один длинный столбец заставлял бы прокручивать форму, чтобы увидеть, как
   правка вверху меняет итог внизу, — а весь смысл калькулятора в том, чтобы
   видеть это сразу. */
.calc-grid {
  display: grid; gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
}
.calc-group-title {
  font-size: var(--fs-caption2); font-weight: var(--fw-heavy);
  letter-spacing: .8px; text-transform: uppercase;
  color: var(--text-2); margin-bottom: var(--sp-3);
}
.calc-field { margin-bottom: var(--sp-3); }
.calc-field label {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: var(--fs-caption); color: var(--text-2); margin-bottom: 4px;
}
/* Единица стоит внутри поля, у правого края — см. .calc-unit ниже. Раньше она
   была подписью сверху и читалась как часть названия: «Налог %» и «Налог ₽»
   глазом различаются плохо, а ошибиться тут дорого. */
.calc-field input, .calc-field select {
  width: 100%; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  font: inherit; font-size: var(--fs-body);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.calc-field input:focus, .calc-field select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
.calc-hint { font-size: var(--fs-caption2); color: var(--text-3); margin-top: 3px; }

/* Строка расхода: название, полоса, сумма. Полоса нужна, чтобы «что съедает
   больше всего» читалось до чтения цифр. */
.calc-row {
  display: grid; grid-template-columns: minmax(120px, 1.2fr) 1fr auto;
  align-items: center; gap: var(--sp-3);
  padding: 7px 0; border-bottom: 1px solid var(--border);
}
.calc-row:last-child { border-bottom: none; }
.calc-row-name { font-size: var(--fs-footnote); color: var(--text-2); }
.calc-row-bar { height: 6px; border-radius: 3px; background: var(--surface3); overflow: hidden; }
.calc-row-bar i { display: block; height: 100%; background: var(--text-3); opacity: .55; }
.calc-row-val {
  font-weight: var(--fw-bold); font-variant-numeric: tabular-nums;
  white-space: nowrap; text-align: right;
}
.calc-row-total {
  border-top: 1px solid var(--border-strong); border-bottom: none;
  margin-top: 4px; padding-top: 10px;
}
.calc-row-total .calc-row-name { color: var(--text); font-weight: var(--fw-semi); }
.calc-row-total .calc-row-val { font-size: var(--fs-callout); }

/* ── Калькулятор: главные поля ─────────────────────────────────────────────
   Шесть полей, которые действительно набирают для нового товара. Остальное
   свёрнуто: значения по умолчанию разумные, и держать их на виду значит
   заставлять читать четыре колонки там, где нужно ответить на один вопрос. */
.calc-main {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  align-items: start;
}
/* Категория шире прочих: названия предметов у WB длинные, и в узком поле от
   «Молокоотсосы электрические» остаётся «Молокоотсо…». */
.calc-field-wide { grid-column: span 2; min-width: 220px; }
.calc-field-wide .ui-trigger { width: 100%; }

/* «Уточнить расчёт» — не спрятанное, а свёрнутое: видно, что там что-то есть. */
.calc-more { margin-top: var(--sp-5); border-top: 1px solid var(--border); padding-top: var(--sp-4); }
.calc-more summary {
  cursor: pointer; font-size: var(--fs-footnote); font-weight: var(--fw-semi);
  color: var(--text-2); list-style: none; display: inline-flex; align-items: center; gap: 6px;
  user-select: none;
}
.calc-more summary::-webkit-details-marker { display: none; }
.calc-more summary::before {
  content: ''; width: 6px; height: 6px; flex: none;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg); transition: transform var(--t-base) var(--ease);
}
.calc-more[open] summary::before { transform: rotate(45deg); }
.calc-more summary:hover { color: var(--text); }
.calc-more .calc-grid { margin-top: var(--sp-4); }

/* Процент рядом с названием категории: он и есть причина, по которой её
   выбирают, — прятать его до нажатия незачем. */
.calc-cat-pct {
  margin-left: auto; padding-left: var(--sp-3);
  font-variant-numeric: tabular-nums; font-weight: var(--fw-semi);
  color: var(--text-2); white-space: nowrap;
}

/* Значение, которое задаёт WB, а не человек. Выглядит как поле по ритму, но
   не притворяется вводом: рамки нет, курсор обычный. */
.calc-readonly { margin-bottom: var(--sp-3); }
.calc-readonly-label {
  display: block; font-size: var(--fs-caption);
  color: var(--text-2); margin-bottom: 4px;
}
.calc-readonly-val {
  display: block; padding: 8px 0;
  font-size: var(--fs-body); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}

/* ── Калькулятор: режим «много товаров» ────────────────────────────────────
   Кнопка стоит рядом с переключателем видов и работает как выключатель: не
   «перейти куда-то», а «показать то же самое иначе». Поэтому кружок, а не
   ещё одна вкладка. */
.calc-batch-toggle {
  display: inline-flex; align-items: center; gap: 8px;
  margin-left: var(--sp-4); padding: 6px 12px 6px 8px;
  border: 1px solid var(--border); border-radius: 99px;
  background: none; cursor: pointer; font: inherit;
  font-size: var(--fs-footnote); font-weight: var(--fw-semi); color: var(--text-2);
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  vertical-align: middle;
}
.calc-batch-toggle:hover { border-color: var(--border-strong); color: var(--text); }
.calc-batch-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  border: 1.6px solid var(--border-strong); background: none;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.calc-batch-toggle.is-on { border-color: var(--accent); color: var(--accent-text); }
.calc-batch-toggle.is-on .calc-batch-dot { background: var(--accent); border-color: var(--accent); }

/* Два листа, как в таблице: что ввели и что вышло */
.calc-sheets {
  display: flex; align-items: center; gap: 4px;
  margin-bottom: var(--sp-4); flex-wrap: wrap;
}
.calc-sheets-right {
  margin-left: auto; display: flex; gap: var(--sp-3); align-items: flex-end;
}
.calc-sheets-right .calc-field { margin-bottom: 0; width: 96px; }

.calc-sheet-scroll { overflow-x: auto; }
/* Ширина по содержимому. Со `width: 100%` таблица растягивалась на всю
   страницу и раздавала лишнее место колонкам поровну: «Д» и «Ш» получали
   столько же, сколько «Себестоимость», строка расползалась метра на полтора,
   и глазу приходилось ехать от названия товара до его маржи через пустоту.
   Итоговый лист остаётся во всю ширину — там колонок вдвое меньше и они
   заполнены. */
.calc-table {
  /* min-content, а не auto. У таблицы `width: auto` ширина считается по
     «предпочтительной», а она у нас заведомо больше экрана — и таблица честно
     занимала всё, что дают, раздавая лишнее колонкам поровну. min-content
     означает «ровно столько, сколько нужно колонкам», то есть те самые
     минимумы, что заданы ячейкам ниже. Не влезло — прокрутка контейнера. */
  width: min-content; border-collapse: collapse;
  font-size: var(--fs-footnote); font-variant-numeric: tabular-nums;
}
/* Заголовки переносятся по словам. Именно они и растягивали таблицу: «Цена до
   СПП · РРЦ» в одну строку — это 150 пикселей под колонку, в которой стоит
   четырёхзначное число. Ширину должны задавать данные, а не подписи к ним. */
.calc-table th {
  text-align: left; padding: 6px 8px; white-space: normal;
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  letter-spacing: .4px; text-transform: uppercase; color: var(--text-2);
  border-bottom: 1px solid var(--border);
}
.calc-table td { padding: 3px 2px; border-bottom: 1px solid var(--border); }
.calc-table-result { width: 100%; }
.calc-table-result td { padding: 8px; white-space: nowrap; text-align: right; }
.calc-table-result td:first-child, .calc-table-result th:first-child { text-align: left; }
.calc-table-result th { text-align: right; }
.calc-table-result th:first-child { text-align: left; }

/* Ячейка ввода: без рамки, пока в неё не встали. Сетка из полутора сотен
   рамок читается как забор, а не как таблица. */
.calc-cell {
  width: 100%; padding: 6px 5px; font: inherit; font-size: var(--fs-footnote);
  border: 1px solid transparent; border-radius: 7px;
  background: none; color: var(--text); font-variant-numeric: tabular-nums;
}
/* Общее правило для input (`input:not([type=checkbox]):not([type=radio])`)
   весит больше одного класса, и до сих пор именно оно задавало ячейкам размер
   шрифта, отступы и высоту — а `.calc-cell` рядом молча не работал. Отсюда и
   разнобой: поля ввода были на девять пикселей выше кнопки категории, хотя в
   разметке им прописан один и тот же класс.
   Селектор ниже весит ровно столько же и стоит позже — теперь размеры ячейки
   задаёт та строка, которая про ячейку и написана. */
.calc-table td > .calc-cell {
  padding: 6px 6px; min-height: 34px; font-size: var(--fs-footnote);
}
.calc-cell:hover { border-color: var(--border); }
.calc-cell:focus { outline: none; border-color: var(--accent); background: var(--surface); }
.calc-cell-num { text-align: right; min-width: 62px; }
.calc-cell-name { min-width: 104px; }
/* Категория — кнопка, а не поле ввода, и потому мимо неё проходило общее
   правило для input, которое рисует всем соседям рамку и подложку. Выходило,
   что в ряду одинаковых на вид ячеек одна — просто текст на пустом месте.
   Повторяем оформление поля явно. */
.calc-cell-cat {
  text-align: left; cursor: pointer; min-width: 104px;
  background: var(--surface2); border: 1px solid var(--border); min-height: 34px;
}
.calc-cell-cat:hover { border-color: var(--border-strong); }
.calc-cell-empty { color: var(--text-3); }
.calc-cell-pct {
  display: inline-block; padding-left: 6px;
  font-size: var(--fs-caption2); color: var(--text-3); font-weight: var(--fw-semi);
}
/* Длина, ширина, высота — числа из двух-трёх цифр. В колонках шириной с
   «Себестоимость» они выглядели как ошибка вёрстки и растягивали таблицу. */
.calc-cell-dim { min-width: 46px; }
/* Ширина колонки — по числу, а не по слову в шапке: «Себестоимость» длиннее
   любой суммы, которую туда впишут. Заголовок переносится, число — нет.

   ⚠️ Потолок считается от САМОГО ДЛИННОГО значения, а не от самого частого.
   В прошлый раз он был занижен, и «2000» показывалось как «200», а «20» — как
   «2(»: поле молча обрезало то, что человек в него вписал. Цифра, которой не
   видно, хуже широкой колонки. */
.calc-table th { max-width: 96px; }
.calc-table th.calc-th-dim { max-width: 58px; }

/* Стрелки-крутилки съедали у числа 16 пикселей справа — ровно они и обрезали
   значение. В таблице на полтораста ячеек крутилками не пользуются: цифры
   вводят с клавиатуры. */
.calc-cell-num::-webkit-outer-spin-button,
.calc-cell-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-cell-num { -moz-appearance: textfield; appearance: textfield; }
.calc-cell-sel { min-width: 62px; }
.calc-cell-actions { width: 32px; }
.calc-cell-del {
  width: 24px; height: 24px; border: none; background: none; cursor: pointer;
  color: var(--text-3); font-size: 18px; line-height: 1; border-radius: 6px;
}
.calc-cell-del:hover { background: var(--red-weak); color: var(--red); }

.calc-res-name { font-weight: var(--fw-semi); }
.calc-res-name span { display: block; font-weight: 400; font-size: var(--fs-caption); color: var(--text-2); }
.calc-res-margin.is-loss { color: var(--red); }
.calc-row-empty td { color: var(--text-3); }

.calc-status {
  display: inline-block; padding: 2px 9px; border-radius: 99px;
  font-size: var(--fs-caption2); font-weight: var(--fw-semi); white-space: nowrap;
}
.calc-status-good { background: var(--green-weak); color: var(--accent-text); }
.calc-status-warn { background: var(--yellow-weak); color: var(--fbs-warn-text); }
.calc-status-bad  { background: var(--red-weak);   color: var(--red); }

.calc-sheet-foot {
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-footnote); color: var(--text-2);
}


/* Единица измерения внутри поля. Поле получает отступ справа ровно под неё,
   чтобы длинное число не заезжало под знак. */
.calc-input-wrap { position: relative; }
.calc-unit {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font-size: var(--fs-footnote); font-weight: var(--fw-semi);
  color: var(--text-3); pointer-events: none;
}
.calc-field.has-unit input { padding-right: 28px; }
/* Стрелки счётчика съели бы место под знак — они здесь и не нужны:
   значения набирают, а не подкручивают. */
.calc-field input[type=number]::-webkit-outer-spin-button,
.calc-field input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-field input[type=number] { -moz-appearance: textfield; }

/* Маржа — ответ на вопрос, ради которого пришли. Зелёная всегда, кроме
   настоящего убытка: покрасить минус зелёным значило бы похвалить за то, за
   что хвалить нельзя. */
.calc-res-margin.is-ok b { color: var(--accent-text); }
.calc-res-margin.is-loss b { color: var(--red); }

/* Маржа — единственное число, ради которого открывают калькулятор. Красим её
   цветом, а не просто делаем крупной: среди трёх одинаково чёрных показателей
   глаз ищет нужный, вместо того чтобы находить.

   Отдельный тон, а не общий kpi-ok: тот же класс стоит на карточках остатков
   и заказов, и покрасить их все значило бы объявить хорошей новостью любое
   число на экране. */
.kpi-margin .kpi-num { color: var(--accent-text); }
.kpi-loss .kpi-num { color: var(--red); }

/* Остаток FBS — кнопка перехода к складам. Выглядит как число, а не как
   кнопка: подчёркивание и рамка в плотной таблице создали бы шум, а то, что
   на него можно нажать, подсказывает курсор и подсказка. */
.mon-fbs-link {
  border: none; background: none; padding: 0; font: inherit;
  color: var(--accent-text); font-weight: var(--fw-semibold);
  cursor: pointer; border-bottom: 1px dashed transparent;
}
.mon-fbs-link:hover { border-bottom-color: currentColor; }

/* Склад в строке задания: имя и регион под ним */
.fbs-wh { white-space: nowrap; }
.fbs-wh span { display: block; font-size: var(--fs-caption); color: var(--text-2); }

/* Фильтр стоит В ЗАГОЛОВКЕ колонки, а не рядом кнопок над таблицей: чтобы
   отфильтровать столбец, глаз сначала находит столбец. */
.fbs-th-filter { padding: 0 !important; }
.fbs-wh-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 8px; border: none; background: none; cursor: pointer;
  font: inherit; font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  letter-spacing: .4px; text-transform: uppercase; color: var(--text-2);
  border-radius: 7px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.fbs-wh-btn:hover { background: var(--surface3); color: var(--text); }
.fbs-wh-btn.is-on { color: var(--accent-text); }
.fbs-wh-btn .ui-chev { width: 8px; height: 8px; opacity: .7; }
.fbs-wh-count {
  margin-left: auto; padding-left: var(--sp-3);
  font-variant-numeric: tabular-nums; font-weight: var(--fw-semi); color: var(--text-3);
}

/* Галочка в столбце полей: выравниваем по левому краю поля, а не по центру —
   иначе она выпадает из вертикали, по которой глаз идёт вниз. */
.calc-check {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-footnote); color: var(--text-2);
  cursor: pointer; margin: -4px 0 4px;
}
.calc-check input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

/* ── Добавление остатков FBS ───────────────────────────────────────────────
   Сначала склад, потом количества. Матрица «товар × склад» не годилась: у
   одежды на товар приходится десяток размеров, а складов у WB 181 — столько
   колонок не бывает, и читать такую таблицу невозможно. */

/* Выбор склада: свои сверху, следом пункты WB по округам */
/* ── Таблица пунктов приёмки ───────────────────────────────────────────────
   Одна сетка на шапку и на все строки — тогда столбцы стоят друг под другом.
   Раньше каждая строка была отдельным flex-контейнером и раскладывалась по
   своему содержимому: «столбцы» съезжали, и таблицей это только называлось.

   Первое правило колонок задаётся ОДНОЙ переменной: разъехаться они теперь
   могут только все сразу, а не по одной. */
.wh-tab { --wh-cols: minmax(0, 1fr) minmax(0, 1.3fr) 62px 84px; }
.wh-tab-head, .wh-tab-row {
  display: grid; grid-template-columns: var(--wh-cols);
  gap: var(--sp-3); align-items: center;
}
.wh-tab-head {
  position: sticky; top: 0; z-index: 1;
  padding: 6px 10px; background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  letter-spacing: .4px; text-transform: uppercase; color: var(--text-2);
}
.wh-tab-city {
  padding: 12px 10px 4px;
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  letter-spacing: .4px; text-transform: uppercase; color: var(--text-3);
}
.wh-tab-row {
  padding: 7px 10px; border-radius: var(--radius-sm);
  border: 1px solid transparent; font-size: var(--fs-footnote);
}
.wh-tab-row:hover { background: var(--surface3); }
.wh-tab-row.is-on { border-color: var(--accent); background: var(--accent-weak); }
/* Пункт со своим складом приглушён не потому, что недоступен, а потому что
   решение по нему уже принято — глаз должен цепляться за свободные. */
.wh-tab-row.is-have .wh-tab-name { color: var(--text-2); }
.wh-tab-name { font-weight: var(--fw-medium); color: var(--text); min-width: 0; }
.wh-tab-addr {
  font-size: var(--fs-caption); color: var(--text-2); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wh-tab-cargo {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  color: var(--text-3); letter-spacing: .3px;
}

/* Действие в строке: создать, выбран, удалить. Одна ширина у всех, иначе
   колонка дышит при каждом переключении. */
.wh-act {
  display: inline-block; width: 100%; padding: 4px 0;
  border-radius: 99px; border: 1px solid var(--border);
  background: var(--surface2); color: var(--text-2);
  font: inherit; font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  cursor: pointer; text-align: center;
}
.wh-act:hover { border-color: var(--border-strong); color: var(--text); }
.wh-act-on {
  background: var(--accent-weak); border-color: var(--accent); color: var(--accent-text);
}
.wh-act-del { color: var(--red); border-color: var(--red-weak); }
.wh-act-del:hover { background: var(--red-weak); border-color: var(--red); color: var(--red); }
/* Не кнопка, а пометка: действия нет, и вид не должен обещать нажатие */
.wh-act-off { border-style: dashed; cursor: default; color: var(--text-3); }
.wh-act-off:hover { border-color: var(--border); color: var(--text-3); }

/* Фильтр по габаритам — те же три метки, что у WB на карте. Без него список
   из полутора сотен пунктов приходится читать целиком, чтобы понять, куда
   вообще можно везти крупногабарит. */
.sa-cargo { display: flex; gap: 6px; margin-bottom: var(--sp-3); }
.sa-cargo-chip {
  padding: 4px 12px; border-radius: 99px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface2);
  color: var(--text-2); font: inherit; font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
}
.sa-cargo-chip:hover { border-color: var(--border-strong); color: var(--text); }
.sa-cargo-chip.is-on {
  background: var(--accent-weak); border-color: var(--accent); color: var(--accent-text);
}
/* Метка габарита в строке пункта: приглушённая, но всегда на месте — по ней
   видно, что везти сюда можно, ещё до выбора. */
.sa-cargo-mark {
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  color: var(--text-3); white-space: nowrap; letter-spacing: .3px;
}

/* ── Шапка складов: две цифры в строку вместо двух плит ─────────────────── */
.wh-bar {
  display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  padding: 10px var(--sp-4); margin-bottom: var(--sp-3);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.wh-stat { display: flex; align-items: baseline; gap: 7px; font-size: var(--fs-footnote); }
.wh-stat .ic { color: var(--text-3); align-self: center; }
.wh-stat b { font-size: var(--fs-title3); font-weight: var(--fw-bold); letter-spacing: -.02em; }
.wh-stat span { color: var(--text-2); }
.wh-bar-actions { margin-left: auto; display: flex; gap: var(--sp-2); }

/* ── Ход создания складов ─────────────────────────────────────────────────
   Счётчик двигается после ответа WB на очередной склад, полоска — за ним.
   Ни один пиксель здесь не рисуется «пока идёт запрос»: по этой полоске
   решают, можно ли уходить со страницы. */
.wh-progress, .wh-hud { padding: var(--sp-3) 0; }
.wh-hud-num { font-size: var(--fs-callout); font-weight: var(--fw-semi); margin-bottom: 8px; }
.wh-hud-track {
  height: 6px; border-radius: 99px; background: var(--surface3); overflow: hidden;
}
.wh-hud-track i {
  display: block; height: 100%; border-radius: 99px;
  background: var(--accent-fill-a); transition: width .25s var(--ease);
}
.wh-hud-sub { margin-top: 8px; font-size: var(--fs-caption); color: var(--text-2); }
.wh-hud-stop { margin-top: var(--sp-3); }
/* Что уже создано — списком, а не только числом. Число говорит «сколько», а
   человеку в этот момент важнее «что именно»: по названиям городов сразу
   видно, туда ли пошло, и есть ли смысл жать «Остановить». */
.wh-hud-list { display: flex; flex-wrap: wrap; gap: 5px; margin-top: var(--sp-3); }
.wh-hud-chip {
  padding: 3px 9px; border-radius: 99px; font-size: var(--fs-caption2);
  background: var(--surface3); color: var(--text-2);
}
.wh-hud-chip.is-ok  { background: var(--accent-weak); color: var(--accent-text); }
.wh-hud-chip.is-bad { background: var(--red-weak); color: var(--red); }

/* Выбранный пункт и занятый пункт различаются рамкой, а не только подписью:
   в списке на полторы сотни строк подпись читают, а рамку видят. */
.sa-wh-row.is-on { border-color: var(--accent); background: var(--accent-weak); }
.sa-wh-row.is-taken { opacity: .55; cursor: default; }
.sa-wh-row.is-taken:hover { background: none; }

.sa-wh-pick { display: flex; flex-direction: column; }
.sa-find {
  width: 100%; padding: 9px 12px; margin-bottom: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit;
}
.sa-find:focus { outline: none; border-color: var(--accent); }
/* Сколько СЦ в этом городе. Без счётчика «Казань» читается как один адрес
   отгрузки, хотя за ним их два, и продавцу выбирать между ними. */
.sa-wh-count {
  margin-left: 8px; padding: 1px 7px; border-radius: 99px;
  background: var(--surface3); color: var(--text-2);
  font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  letter-spacing: 0; text-transform: none;
}
/* Подпись под списком: чей это список и сколько из него отсеяно. Молча
   урезанный список выглядит как «WB даёт мало СЦ», хотя дело в нашем фильтре. */
.sa-foot-note {
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-caption); color: var(--text-2);
}
.sa-wh-group {
  font-size: var(--fs-caption2); font-weight: var(--fw-heavy);
  letter-spacing: .8px; text-transform: uppercase; color: var(--text-3);
  padding: var(--sp-3) 2px 6px;
}
.sa-wh-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  width: 100%; padding: 9px 10px; border: none; border-radius: var(--radius-sm);
  background: none; cursor: pointer; font: inherit; text-align: left;
  transition: background var(--t-fast) var(--ease);
}
.sa-wh-row:hover { background: var(--surface3); }
.sa-wh-name { min-width: 0; font-size: var(--fs-body); color: var(--text); }
.sa-wh-name i { display: block; font-style: normal; font-size: var(--fs-caption); color: var(--text-2); }
.sa-wh-tag {
  flex: none; font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  padding: 2px 8px; border-radius: 99px;
  background: var(--green-weak); color: var(--accent-text);
}
/* Пункт без своего склада — возможность, а не готовое место. Приглушаем, но
   оставляем в списке: не показать его значит скрыть половину карты. */
/* ⚠️ Не --text-3: на подложке --surface3 он не проходит по контрасту, и это
   уже второй такой случай в проекте. Приглушённый значит бледнее соседа, а не
   нечитаемый. */
.sa-wh-tag.is-muted { background: var(--surface3); color: var(--text-2); }
.sa-wh-row.is-office { cursor: help; }
.sa-wh-row.is-office .sa-wh-name { color: var(--text-2); }

/* Шапка со складом и поиском */
.sa-wh-bar { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.sa-wh-current {
  display: inline-flex; align-items: center; gap: 8px; flex: none;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: none; cursor: pointer; font: inherit; font-size: var(--fs-footnote);
  color: var(--text);
}
.sa-wh-current:hover { border-color: var(--accent); }
.sa-wh-current span { color: var(--text-3); font-size: var(--fs-caption); }
.sa-wh-bar .sa-find { margin-bottom: 0; }

.sa-scroll { overflow: auto; max-height: 46vh; }

/* Товар с размерами. Одна строка на товар, размеры вложенно — иначе шесть
   товаров превращаются в шестьдесят строк. */
.sa-item { border-bottom: 1px solid var(--border); padding: var(--sp-3) 0; }
.sa-item:last-child { border-bottom: none; }
.sa-item-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.sa-item-head .ui-row-photo { width: 30px; height: 30px; border-radius: 7px; flex: none; }
.sa-item-name { flex: 1; min-width: 0; }
.sa-item-name b { display: block; font-size: var(--fs-footnote); font-weight: var(--fw-semi); }
.sa-item-name i { font-style: normal; font-size: var(--fs-caption); color: var(--text-2); }
.sa-item-total { font-size: var(--fs-caption); color: var(--text-2); white-space: nowrap; }
.sa-item-total.is-never { color: var(--text-3); }

.sa-sizes { display: flex; flex-direction: column; gap: 4px; padding-left: 38px; }
.sa-size-row { display: flex; align-items: center; gap: 8px; }
.sa-size-name { flex: 1; font-size: var(--fs-footnote); color: var(--text-2); }
.sa-size-was { font-size: var(--fs-caption); color: var(--text-3); white-space: nowrap; }
/* Размер, которого на складе не было: приглушаем подпись, но поле оставляем
   обычным — класть сюда можно ровно так же. */
.sa-size-row.is-never .sa-size-was { color: var(--text-3); font-style: italic; }
/* Поле количества — узкое. `width: 76px` здесь уже стояло и не работало:
   общее правило для input весит больше одного класса и растягивало поле на всю
   строку. Из-за этого на «блузку 40 размера» приходилось поле шириной с пол-
   экрана, куда вписывают две цифры. Селектор ниже весит столько же и стоит
   позже — теперь ширину задаёт он. (Та же ловушка была в калькуляторе.) */
.sa-size-row > input.sa-input {
  width: 84px; padding: 5px 8px; text-align: right; flex: none;
  min-height: 30px; font-size: var(--fs-footnote);
  border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface); color: var(--text);
  font-variant-numeric: tabular-nums;
}
.sa-input::-webkit-outer-spin-button,
.sa-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.sa-input { -moz-appearance: textfield; appearance: textfield; }
.sa-input:focus { outline: none; border-color: var(--accent); }

/* Подтверждение: было — обычным, станет — зелёным. Взгляд должен без чтения
   находить новое значение, а не сравнивать два одинаковых числа. */
.sa-confirm-wh { margin-bottom: var(--sp-4); }
.sa-confirm-head {
  font-weight: var(--fw-bold); font-size: var(--fs-callout); margin-bottom: var(--sp-2);
}
.sa-confirm-head span { font-weight: 400; font-size: var(--fs-caption); color: var(--text-2); margin-left: 8px; }
.sa-size { text-align: center; color: var(--text-2); white-space: nowrap; }
.sa-was-cell { text-align: right; color: var(--text); font-variant-numeric: tabular-nums; }
.sa-arrow { text-align: center; color: var(--text-3); width: 32px; }
.sa-will {
  text-align: right; font-weight: var(--fw-bold);
  color: var(--accent-text); font-variant-numeric: tabular-nums;
}

.sa-warn {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--yellow-weak); color: var(--fbs-warn-text);
  font-size: var(--fs-footnote); line-height: 1.5;
}
.sa-warn-token { background: var(--surface3); color: var(--text-2); margin-top: var(--sp-3); }

/* Штраф и скидка — одна величина с разным знаком, поэтому одна колонка.
   Держать под них два столбца, из которых один всегда пуст, значит тратить
   половину ширины на пустоту. Различает цвет: красный — платим, зелёный —
   сэкономили. */
/* Штраф и скидка — по центру своей колонки. Это не суммы, которые складывают
   столбиком: их читают построчно, как пометку «сколько эта строка стоила».
   По правому краю они прижимались к самому краю таблицы и висели в воздухе. */
/* Штраф и скидка — по центру своей колонки. Это не суммы, которые складывают
   столбиком: их читают построчно, как пометку «сколько эта строка стоила».
   По правому краю они прижимались к самому краю таблицы и висели в воздухе. */
.fbs-adj { text-align: center; }

/* Центр по колонке. Тег в селекторе обязателен: `.stock-table th` и
   `.mon-sum` задают выравнивание и весят не меньше голого класса — без него
   правило снова оказалось бы написанным и нерабочим. */
td.ta-c, th.ta-c { text-align: center; }
/* Полоса возраста внутри центрированной ячейки — тоже по центру */
td.ta-c .fbs-bar { margin-left: auto; margin-right: auto; }

/* Пять плиток в ряд. auto-fit с минимумом 210px раскладывал их как придётся:
   на широком экране получалось 5, на среднем 3+2 — и «Отгружено» уезжало во
   второй ряд, отрываясь от остальных. Фиксируем пять колонок там, где они
   помещаются, и честно переносим, где нет. */
@media (min-width: 1100px) {
  .kpi-row-5 { grid-template-columns: repeat(5, 1fr); }
}

/* Переключатель этапа внутри карточки заданий: он мельче, чем переключатель
   раздела, потому что и решение мельче — не «куда я иду», а «на что смотрю». */
.fbs-stage .an-view { padding: 5px 14px; font-size: var(--fs-caption); }
.fbs-stage-note {
  margin-bottom: var(--sp-3); padding: 9px 12px;
  background: var(--surface3); border-radius: var(--radius-sm);
  font-size: var(--fs-caption); color: var(--text-2); line-height: 1.5;
}

/* ── Знак решает цвет ──────────────────────────────────────────────────────
   +сумма красным — заплатил, −сумма зелёным — заработал. Правило одно на всё
   приложение, поэтому и классы общие, а не «для этой таблицы». Цвет здесь
   дублирует знак, а не заменяет его: на чёрно-белой печати и при дальтонизме
   остаётся плюс с минусом. */
/* `span.` в селекторе — не украшение. Рядом живёт `.kpi-num span`, правило для
   мелкого суффикса («шт», «ч»), и оно весит больше одного класса. Наша сумма
   попадала под него целиком: показывалась 13-м кеглем серым вместо 24-го
   цветом — то есть выглядела как подпись, а не как деньги. */
.num-fine, span.num-fine { color: var(--red); }
.num-earn, span.num-earn { color: var(--accent-text); }
.num-flat, span.num-flat { color: var(--text-3); }
/* Размер и вес берём у блока, а не у правила для суффикса */
.kpi-num > span.num-fine,
.kpi-num > span.num-earn,
.kpi-num > span.num-flat { font-size: inherit; font-weight: inherit; }

/* Стоимость и комиссия — обычным начертанием, как штраф со скидкой. Жирным в
   строке было выделено три числа из четырёх, а когда выделено почти всё,
   выделено ничего: глаз перестаёт различать, что здесь главное. Главное —
   возраст задания и штраф, они и остаются заметными за счёт цвета. */
.fbs-live-table .mon-sum { font-weight: var(--fw-regular); }

/* Выгрузка за период. Рядом с показателями, а не в шапке раздела: скачивают
   то, на что сейчас смотрят. */
.fbs-export {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
.fbs-export-label { font-size: var(--fs-footnote); color: var(--text-2); }
.fbs-export-btn {
  padding: 5px 12px; border-radius: 99px;
  border: 1px solid var(--border); background: none; cursor: pointer;
  font: inherit; font-size: var(--fs-footnote); color: var(--text-2);
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.fbs-export-btn:hover { border-color: var(--accent); color: var(--accent-text); }
/* Подпись о выбранном складе: без неё человек скачает отфильтрованное,
   а решит, что скачал всё. */
.fbs-export-note { font-size: var(--fs-caption); color: var(--text-3); }

/* Пометка «только чтение» рядом с погашенными кнопками. Без неё погашенная
   кнопка выглядит сломанной: видно, что нажать нельзя, и неясно почему. */
.wh-nowrite {
  align-self: center; padding: 3px 10px; border-radius: 99px;
  background: var(--surface3); color: var(--text-2);
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
  cursor: help; white-space: nowrap;
}

/* ── Полоса «режим администратора» ───────────────────────────────────────────
   Появляется, только когда кабинет открыт админом через админку. Экран в
   этом режиме ничем не отличается от собственного кабинета продавца, а
   кнопки на нём живые — цены, остатки, реклама. Полоса существует ровно для
   того, чтобы забыться было невозможно.

   Цвет янтарный, не красный: красный в интерфейсе занят ошибками и опасными
   действиями. Здесь ничего не сломано — просто нужно всё время помнить, где
   ты находишься. */
.imp-bar {
  /* Высота живёт в переменной: от неё же считаются отступы меню и шапки.
     Разъедутся два числа — полоса накроет шапку, и заметит это не тот, кто
     правил. */
  --imp-h: 40px;
  position: fixed; inset: 0 0 auto 0; z-index: 300;
  height: var(--imp-h);
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-4);
  background: #7a4a05; color: #ffe9c2;
  font-size: 13px; font-weight: var(--fw-medium, 500);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .18);
}
/* ⚠️ Иначе `display: flex` выше побеждает браузерное `[hidden]`, и полоса
   висит у всех подряд: правило разработчика всегда сильнее браузерного. */
.imp-bar[hidden] { display: none; }

/* Точка — единственная деталь, которую видно боковым зрением, когда текст
   уже примелькался. Мигает медленно; тем, кто просил убрать движение,
   не мигает вовсе. */
.imp-dot {
  width: 8px; height: 8px; flex: none; border-radius: 50%;
  background: #ffb020;
  animation: imp-pulse 2.4s ease-in-out infinite;
}
@keyframes imp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .imp-dot { animation: none; } }

.imp-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.imp-txt b { font-weight: 700; }

.imp-out {
  margin-left: auto; flex: none;
  /* 28px по высоте — нижняя граница для указателя мыши; ниже в полосу
     высотой 40px кнопка уже не попадает пальцем. */
  min-height: 28px; padding: 5px 12px;
  /* 28px — нижняя граница для мыши. На касании этого мало, и ниже в разделе
     для узких экранов кнопка вырастает до 44px вместе с самой полосой. */
  border: 1px solid rgba(255, 233, 194, .45); border-radius: 7px;
  background: transparent; color: inherit;
  font: inherit; font-size: 12.5px; cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.imp-out:hover { background: rgba(255, 233, 194, .16); border-color: #ffe9c2; }
.imp-out:focus-visible { outline: 2px solid #ffe9c2; outline-offset: 2px; }

/* Полоса лежит поверх всего, поэтому всё, что прибито к верху экрана,
   опускаем ровно на её высоту — иначе она накроет меню и шапку. */
.has-imp-bar { --imp-h: 40px; }
.has-imp-bar .sidebar { top: var(--imp-h); min-height: calc(100vh - var(--imp-h)); }
.has-imp-bar .topbar { top: var(--imp-h); }
.has-imp-bar body { padding-top: var(--imp-h); }

/* Короткие подписи существуют только для узкого экрана. */
.imp-lbl-s, .imp-out-s { display: none; }

@media (max-width: 720px) {
  /* Полная подпись съедает всю строку, и обрезается ровно то, ради чего
     полоса нужна: чей кабинет открыт. Меняем подпись на короткую — почта
     остаётся видна целиком. */
  .imp-lbl, .imp-out-l { display: none; }
  .imp-lbl-s, .imp-out-s { display: inline; }
  .imp-bar { padding: 0 var(--sp-3); gap: var(--sp-2); font-size: 12px; }

  /* ⚠️ Полоса вырастает с 40 до 48px не ради вида, а ради кнопки: на касании
     цель меньше 44px промахивается, и «выйти из чужого кабинета» — последнее,
     по чему стоит промахиваться. 44 внутрь 40 не влезает, поэтому растёт вся
     полоса, а вместе с ней и отступ содержимого под ней. */
  .imp-bar, .has-imp-bar { --imp-h: 48px; }
  .imp-out { min-height: 44px; padding: 5px 14px; }
}

/* ── Колокольчик уведомлений ─────────────────────────────────────────────────
   Уведомления на сайте приходят всегда и настройками не отключаются:
   мессенджер — способ поторопить, а не способ сообщить. Значок поэтому живёт
   в шапке постоянно, а не появляется, когда что-то настроили. */
.bell-wrap { position: relative; }

/* Кнопке нужен свой контекст позиционирования — счётчик крепится к её углу. */
.btn.bell { position: relative; }

/*
 * ⚠️ ПАЛЬЦУ НУЖНО 44px, ГЛАЗУ — 32.
 *
 * Кнопка в шапке 41×32: рядом такая же кнопка темы, и вырастить одну значит
 * сломать ряд. Но 32px — промах через раз на телефоне, а колокольчик на
 * телефоне и нажимают.
 *
 * Поэтому растёт не кнопка, а область нажатия: невидимый прямоугольник
 * поверх. Выглядит как прежде, попадает как надо. `pointer-events` вешать не
 * нужно — псевдоэлемент их и так наследует.
 */
.btn.bell::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}

/* Счётчик, а не просто точка: «есть новое» и «новых восемнадцать» —
   это решения разной срочности. */
.bell-dot {
  position: absolute; top: -4px; right: -4px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px;
  background: var(--red); color: var(--danger-ink);
  font-size: 10px; font-weight: 700; line-height: 1;
  /* Обводка цветом шапки: без неё цифра сливается с иконкой под собой. */
  box-shadow: 0 0 0 2px var(--glass-bg);
}
/* ⚠️ Иначе `display: flex` побеждает браузерное [hidden], и счётчик висит
   даже когда читать нечего. */
.bell-dot[hidden] { display: none; }

.bell-panel {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 200;
  width: min(380px, calc(100vw - 24px));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .16);
  overflow: hidden;
  animation: bell-in .16s var(--ease);
}
.bell-panel[hidden] { display: none; }
@keyframes bell-in { from { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) {
  /* Движение заменяем проявлением, а не оставляем как есть. */
  .bell-panel { animation: bell-fade .16s var(--ease); }
  @keyframes bell-fade { from { opacity: 0; } }
}

.bell-head, .bell-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); padding: 10px var(--sp-4);
}
.bell-head { border-bottom: 1px solid var(--border); }
.bell-foot { border-top: 1px solid var(--border); }

/* Высота ограничена: список из сорока штук иначе занимает весь экран, и
   закрыть его нечем — кнопки уезжают за нижний край. */
.bell-list { max-height: min(420px, 60vh); overflow-y: auto; }
.bell-empty { padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--text-3); }

.bell-item {
  /* position — ради полоски слева: она абсолютная и цепляется за строку. */
  position: relative;
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: 11px var(--sp-4);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.bell-item:last-child { border-bottom: none; }

/*
 * ⚠️ ПОДСВЕТКА ТА ЖЕ, ЧТО У ПУНКТОВ МЕНЮ, И ЭТО НЕ СОВПАДЕНИЕ.
 *
 * И там, и здесь наведение отвечает на один и тот же вопрос: «вот эта строка
 * откликнется на нажатие». Разные эффекты для одного смысла заставляют
 * запоминать два языка вместо одного — а язык интерфейса должен быть один.
 *
 * Полоска слева, а не рамка вокруг: рамка сдвинула бы содержимое на свою
 * толщину, и строка дёргалась бы под курсором. Полоска лежит поверх и не
 * занимает места.
 */
.bell-item::before {
  content: '';
  position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--accent);
  /* Растёт из ничего по высоте — движение короткое и вдоль строки, а не
     поперёк: поперечное дёргает взгляд сильнее, чем помогает. */
  transform: scaleY(0); transform-origin: center;
  opacity: 0;
  transition: transform .18s var(--ease), opacity .18s var(--ease);
}
.bell-item:hover { background: var(--accent-weak); }
.bell-item:hover::before,
.bell-item:focus-visible::before { transform: scaleY(1); opacity: 1; }

/* Заголовок под курсором темнеет: строка «поднимается» к переднему плану
   целиком, а не только фоном. */
.bell-item:hover .bell-title { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  /* Движение убираем, различимость оставляем: полоска просто появляется. */
  .bell-item::before { transition: opacity .18s var(--ease); transform: scaleY(1); }
}
.bell-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Прочитанное гасим, но не прячем: «я это уже видел» и «этого не было» —
   разные вещи, и вторая пугает. */
.bell-item.is-read { opacity: .55; }

/* Уровень — цветом И положением, а не одним цветом: дальтоник различит
   срочное по расположению точки в общем ряду, но не по оттенку. */
.bell-lvl {
  width: 8px; height: 8px; margin-top: 6px; flex: none; border-radius: 50%;
  background: var(--text-3);
}
.bell-lvl-warn { background: #d9820b; }
.bell-lvl-alert { background: var(--red); }
.bell-lvl-info { background: var(--accent); }

.bell-body { min-width: 0; }
.bell-title { font-weight: 600; font-size: 13.5px; }
.bell-text { font-size: 12.5px; color: var(--text-2); margin-top: 2px; }
.bell-when { font-size: 11.5px; color: var(--text-3); margin-top: 4px; }

@media (max-width: 560px) {
  /*
   * ⚠️ НА УЗКОМ ЭКРАНЕ ПАНЕЛЬ КРЕПИТСЯ К ОКНУ, А НЕ К КНОПКЕ.
   *
   * `position: absolute` отсчитывается от колокольчика, а он стоит у правого
   * края. Панель шириной почти во весь экран, выросшая влево от него,
   * упиралась левым краем за границу окна: первые буквы каждой строки просто
   * пропадали — «…мения» вместо «Уведомления». Ширину при этом задавала
   * `100vw`, то есть формально всё сходилось, а читать было нечего.
   *
   * `fixed` с отступами от обоих краёв снимает вопрос: панель занимает окно
   * минус поля, где бы ни стояла кнопка.
   */
  .bell-panel {
    position: fixed;
    top: 56px; left: 8px; right: 8px;
    width: auto;
  }
  /* Полоса «режим администратора» сдвигает шапку — панель едет вместе с ней. */
  .has-imp-bar .bell-panel { top: calc(56px + var(--imp-h)); }
}

/* ── Настройки уведомлений ───────────────────────────────────────────────────
   «Подключено» и «включено» показываются отдельно. Галочка при неподключённом
   боте выглядела бы как «сообщения идут», а они никуда не идут — адреса нет.
   Человек ждал бы и не дождался, не увидев причины. */
.notify-head { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.notify-ico {
  width: 36px; height: 36px; flex: none;
  /* ⚠️ Тёмная зелень, а не яркая. На #10b981 белый значок давал 2,54:1 —
     проверка контраста поймала. Нужно 4,5; здесь 5,4 и 7,2 по концам. */
  background: linear-gradient(135deg, #0b7a52, #0a6357);
  border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
}
.notify-title { font-size: var(--fs-callout); font-weight: 700; }
.notify-sub { font-size: var(--fs-caption); color: var(--text-muted); }

.notify-rows { display: flex; flex-direction: column; }
.notify-row {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.notify-row:first-child { border-top: none; }
/* Неподключённый канал гасим: строка остаётся читаемой, но видно, что
   трогать пока нечего. */
.notify-row.is-off .notify-row-name { color: var(--text-muted); }

.notify-row-main { min-width: 0; }
.notify-row-name { font-size: var(--fs-footnote); font-weight: 600; }
.notify-row-hint { font-size: var(--fs-caption); color: var(--text-muted); margin-top: 2px; }

/* «всегда» вместо выключателя: место в строке занято, и сразу видно, что
   решение тут не принимают. */
.notify-always {
  margin-left: auto; flex: none;
  padding: 3px 10px; border-radius: 999px;
  background: var(--surface2); color: var(--text-muted);
  font-size: var(--fs-caption); font-weight: 600;
}

/* Переключатель. Область нажатия 44px по высоте — на телефоне промахиваться
   по настройке уведомлений особенно обидно: результат промаха не виден. */
.notify-sw {
  margin-left: auto; flex: none; position: relative;
  display: inline-flex; align-items: center;
  width: 44px; height: 44px; cursor: pointer;
}
.notify-sw input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.notify-sw span {
  position: absolute; top: 50%; right: 0; transform: translateY(-50%);
  width: 40px; height: 24px; border-radius: 12px;
  background: var(--border);
  transition: background var(--t-fast) var(--ease);
  pointer-events: none;
}
.notify-sw span::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
  transition: transform var(--t-fast) var(--ease);
}
.notify-sw input:checked + span { background: var(--accent); }
.notify-sw input:checked + span::after { transform: translateX(16px); }
.notify-sw input:disabled + span { opacity: .45; }
.notify-sw input:disabled { cursor: not-allowed; }
.notify-sw input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Раздел «Уведомления» ────────────────────────────────────────────────────
 *
 * ⚠️ ЛЕНТА И НАСТРОЙКИ РАЗНЕСЕНЫ ПО СТОРОНАМ, ПОТОМУ ЧТО ЖИВУТ В РАЗНОМ РИТМЕ.
 * Настройки трогают один раз и забывают; ленту читают каждый раз, когда
 * заходят. Пока они стояли друг под другом, чтение ленты означало прокрутку
 * мимо переключателей, а до нижних настроек надо было прокрутить всю историю.
 *
 * Слева настройки, справа лента — а не наоборот: в разделе больше настроек, и
 * узкая колонка им тесна. Лента же состоит из коротких однотипных строк и в
 * узкой колонке читается даже лучше.
 */
.nt-layout {
  /*
   * ⚠️ ШИРИНА ЛЕНТЫ — minmax, А НЕ ОДНО ЧИСЛО. Событие это строка «что, где,
   * когда», и она длиннее, чем кажется: «остаток товара LIL-PLT-002 упал ниже
   * порога на складе Коледино» плюс время справа. При 380px обрезался хвост
   * каждой строки, при 460px — у длинных. 560px хватает.
   *
   * Верхняя граница 42% не даёт ленте съесть настройки: без потолка на
   * широком мониторе она заняла бы половину экрана под короткие строки.
   */
  display: grid; grid-template-columns: 1fr minmax(560px, 42%); gap: 20px;
  /* Без этого колонки тянутся по высоте самой длинной, и лента растягивается
     на пустоту под собой. */
  align-items: start;
}
/* Ниже 1100px две колонки по 380 и 1fr перестают помещаться: настройки
   сжимаются до нечитаемого. Складываем в одну — лента первой, как раньше. */
/* Порог растёт вместе с шириной ленты: чем шире колонка, тем раньше две
   перестают помещаться и настройки сжимаются до нечитаемого. */
@media (max-width: 1320px) {
  .nt-layout { grid-template-columns: 1fr; }
  .nt-side { order: -1; }
}
.nt-main > .card { max-width: 780px; }

/*
 * ⚠️ ЧЕРТА СЛЕВА, А НЕ ПРОСТО ОТСТУП. Лента — другой род содержимого: то, что
 * уже произошло, против того, что будет происходить. Одинаковые карточки
 * подряд эту границу не показывали, и лента читалась как первый пункт
 * настроек. В одну колонку (на узком экране) черта слева бессмысленна —
 * там её роль играет порядок, поэтому она снимается вместе с сеткой.
 */
/* top: 72px — под липкой шапкой сайта, как у панели задач. При top: 0 лента
   прилипала бы ПОД шапкой и её первые строки оказывались бы закрыты. */
.nt-side { position: sticky; top: 72px; padding-left: 20px; border-left: 1px solid var(--border); }
/* Карточка ленты занимает колонку целиком: ограничение в 780px, общее для
   настроек, в узкой колонке только мешает. Нижний отступ не нужен — под ней
   ничего нет. */
.nt-history-card { max-width: none; margin-bottom: 0; }
@media (max-width: 1320px) {
  .nt-side { position: static; padding-left: 0; border-left: none; }
}

/*
 * ⚠️ ЛЕНТА ЛИСТАЕТСЯ ВНУТРИ СЕБЯ, А НЕ УВОЗИТ СТРАНИЦУ. Сотня событий
 * растягивала вкладку на несколько экранов: чтобы вернуться к настройкам,
 * надо было прокрутить всю историю обратно. Теперь двигается только лента, а
 * шапка карточки и настройки слева остаются на месте.
 *
 * Высота в vh, а не в пикселях: на ноутбуке и на большом мониторе «сколько
 * влезает» — разные числа, а смысл один. 60vh оставляет место шапке раздела и
 * не упирается в низ экрана.
 */
.nt-history {
  display: flex; flex-direction: column;
  max-height: 60vh; overflow-y: auto;
  /* Прокрутка внутри не должна перекидываться на страницу, когда лента
     докручена до конца: иначе колесо «проваливается» и уезжает весь раздел. */
  overscroll-behavior: contain;
  /* Место под полосу прокрутки, чтобы текст не дёргался, когда она появляется
     и исчезает. */
  scrollbar-gutter: stable;
}

.nt-item {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: 11px 0;
  border-top: 1px solid var(--border);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.nt-item:first-child { border-top: none; }

/* Тот же приём, что в панели колокольчика и в меню — см. пояснение у
   .bell-item::before. Здесь строки шире, поэтому подложка уводится под
   отрицательные поля: иначе подсветка обрывалась бы по краю текста, а не по
   краю карточки. */
.nt-item {
  position: relative;
  padding-left: 8px; padding-right: 8px;
  margin-left: -8px; margin-right: -8px;
  border-radius: 8px;
  transition: background var(--t-fast) var(--ease);
}
.nt-item::before {
  content: '';
  position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--accent);
  transform: scaleY(0); opacity: 0;
  transition: transform .18s var(--ease), opacity .18s var(--ease);
}
.nt-item:hover { background: var(--accent-weak); }
.nt-item:hover::before { transform: scaleY(1); opacity: 1; }
.nt-item:hover .nt-item-title { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .nt-item::before { transition: opacity .18s var(--ease); transform: scaleY(1); }
}
/*
 * ⚠️ РУКА ТОЛЬКО ТАМ, КУДА ДЕЙСТВИТЕЛЬНО МОЖНО ПЕРЕЙТИ.
 *
 * cursor: pointer стоял на ВСЕХ событиях, включая те, у которых ссылки нет
 * (data-link не проставлен). Курсор обещал переход, нажатие ничего не
 * делало — и это выглядело как поломка, хотя переходить было некуда.
 *
 * Событие без ссылки остаётся кликабельным для отметки «прочитано», но
 * показывать его как дверь мы не должны.
 */
.nt-item:not([data-link]) { cursor: default; }
.nt-item:not([data-link]):hover::before { opacity: 0; transform: scaleY(0); }

/* Стрелка у тех, кто ведёт куда-то: до нажатия видно, что это дверь, а не
   просто строка. Появляется при наведении — постоянная стрелка в каждой
   строке превращает ленту в частокол. */
.nt-item[data-link] .nt-item-title::after {
  content: '→';
  margin-left: 6px; color: var(--accent-text);
  opacity: 0; transition: opacity var(--t-fast) var(--ease);
}
.nt-item[data-link]:hover .nt-item-title::after { opacity: 1; }

/* Прочитанное гасим, но не прячем: «я это видел» и «этого не было» — разные
   вещи, и вторая пугает. */
.nt-item.is-read { opacity: .5; }

.nt-item-body { min-width: 0; flex: 1; }
.nt-item-title { font-size: var(--fs-footnote); font-weight: 600; }
.nt-item-text { font-size: var(--fs-caption); color: var(--text-2); margin-top: 2px; }
/* Время не переносится и не сжимается: колонка справа должна оставаться
   колонкой, иначе список перестаёт читаться сверху вниз. */
.nt-item-when {
  flex: none; font-size: var(--fs-caption); color: var(--text-3);
  white-space: nowrap; padding-top: 1px;
}

.nt-group { padding: 4px 0; }
.nt-group + .nt-group { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 12px; }
.nt-group-name {
  font-size: var(--fs-caption); font-weight: 700;
  color: var(--text-3); text-transform: uppercase; letter-spacing: .04em;
  margin-bottom: 2px;
}
/* Внутри группы строки уже разделены заголовком — вторая линия сверху лишняя. */
.nt-group .notify-row:first-of-type { border-top: none; }

/* Ожидание подключения бота: подключение заканчивается в мессенджере, и без
   этой строки человек вернулся бы на страницу, где написано «не подключён». */
.nt-wait {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-top: 10px; padding: 10px 12px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--radius-md, 10px);
}
.nt-wait-spin {
  width: 16px; height: 16px; flex: none;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%;
  animation: nt-spin .8s linear infinite;
}
@keyframes nt-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Вращение заменяем миганием: движение убрано, а «идёт работа» осталось. */
  .nt-wait-spin { animation: nt-blink 1.4s ease-in-out infinite; border-top-color: var(--accent); }
  @keyframes nt-blink { 50% { opacity: .35; } }
}

@media (max-width: 560px) {
  /* Время уезжает под текст: в одну строку с заголовком на узком экране оно
     съедает половину ширины у самого текста. */
  .nt-item { flex-wrap: wrap; }
  .nt-item-when { width: 100%; padding-left: 20px; }
}

/* ── Пароль и вход ───────────────────────────────────────────────────────────
   Смена и восстановление стоят рядом: человек приходит сюда в одном из двух
   состояний — «хочу поменять» и «не помню», — и разводить это по разным
   экранам значит заставить его сначала понять, в каком он состоянии. */
.pwd-form { display: flex; flex-direction: column; gap: var(--sp-3); max-width: 420px; }

.pwd-field { display: flex; flex-direction: column; gap: 5px; }
.pwd-field > span { font-size: var(--fs-caption); color: var(--text-muted); font-weight: 600; }
/* ⚠️ Вес поднят: в style.css есть сброс `input:not([type="checkbox"]):not([type="radio"])`
   весом (0,2,1), и голый класс ему проигрывает — поле осталось бы без рамки
   и отступов. Селектор с родителем даёт (0,2,1) плюс элемент. */
.pwd-form .pwd-field > input {
  padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-md, 10px);
  background: var(--surface); color: var(--text);
  font: inherit; font-size: var(--fs-footnote);
  transition: border-color var(--t-fast) var(--ease);
}
.pwd-form .pwd-field > input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

.pwd-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Сообщение о результате. Пустое не занимает места — иначе форма дёргается
   вверх-вниз при каждой попытке. */
.pwd-note { font-size: var(--fs-caption); line-height: 1.5; }
.pwd-note:empty { display: none; }
.pwd-note.is-плохо { color: var(--red); }
.pwd-note.is-хорошо { color: var(--accent); }

.pwd-recover {
  margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: var(--sp-2);
  max-width: 420px;
}
/* ⚠️ Иначе `display: flex` побеждает браузерное [hidden], и блок висит
   раскрытым у всех подряд. */
.pwd-recover[hidden] { display: none; }
.pwd-recover-head { font-size: var(--fs-footnote); font-weight: 700; }

/* ── Утренний брифинг: поля настройки ────────────────────────────────────────
   Подписи спрашивают о деле («во сколько присылать»), а не называют
   переменную из кода («порог oosDays»). Под числом — живое пояснение того,
   что получится: цифру в поле человек меняет, глядя на результат, а не на
   определение. */
.brief-row {
  display: flex; flex-wrap: wrap; gap: var(--sp-4);
  align-items: flex-end; margin-bottom: 16px;
}
.brief-field { display: flex; flex-direction: column; gap: 5px; }
.brief-field > span { font-size: var(--fs-caption); color: var(--text-muted); font-weight: 600; }

/* ⚠️ Вес поднят: в style.css есть сброс `input:not([type="checkbox"]):not([type="radio"])`
   весом (0,2,1), и голый класс ему проигрывает — поля остались бы без рамки. */
.brief-row .brief-field > input,
.brief-row .brief-num > input {
  padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text);
  font: inherit; font-size: var(--fs-body);
  transition: border-color var(--t-fast) var(--ease);
}
.brief-row .brief-field > input:focus,
.brief-row .brief-num > input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Единица рядом с числом, а не в подписи: «14» без «дней» читается как
   что угодно — штуки, проценты, номер. */
.brief-num { display: flex; align-items: center; gap: 8px; }
.brief-num > input { width: 84px; }
.brief-unit { font-size: var(--fs-footnote); color: var(--text-muted); }


/* ── Кнопки-ссылки в панели уведомлений ──────────────────────────────────────
   ⚠️ Класс `.link-btn` пришёл из админки, а её стилей в кабинете нет — и
   кнопки рисовались браузером по умолчанию: серые прямоугольники с рамкой
   посреди аккуратной панели. Ошибка тихая: разметка верная, поведение верное,
   но выглядит как чужое.

   Здесь они — текст в цвете акцента, без рамки и фона. Это второстепенные
   действия рядом со списком: рамка притягивала бы к ним внимание, которое
   должно достаться самим уведомлениям. */
.bell-panel .link-btn,
.bell-head .link-btn,
.bell-foot .link-btn {
  padding: 4px 6px;
  border: none; background: none;
  color: var(--accent);
  font: inherit; font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
  border-radius: 6px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.bell-panel .link-btn:hover {
  /* Подложка вместо подчёркивания: подчёркивание в интерфейсе читается как
     ссылка «уйти со страницы», а эти кнопки никуда не уводят. */
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.bell-panel .link-btn:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.bell-panel .link-btn:disabled { opacity: .5; cursor: default; }

/* ── Раздел «Реклама» ────────────────────────────────────────────────────────
   Три уровня: кампания → артикул → фразы. Каждый следующий раскрывается из
   предыдущего, а не живёт отдельным экраном: решение о ставке принимают,
   держа перед глазами и расход кампании, и то, за какие фразы он ушёл. */

.ads-select {
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text);
  font: inherit; font-size: var(--fs-footnote); width: auto; min-height: 34px;
}

/* Итог по кабинету. Плитки, а не строка текста: четыре числа, которые
   считывают боковым зрением, прежде чем углубляться. */
.ads-total {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-4);
}
.ads-tile {
  flex: 1 1 150px; padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
}
.ads-tile-v { font-size: var(--fs-title3); font-weight: 700; }
.ads-tile-l { font-size: var(--fs-caption); color: var(--text-muted); margin-top: 2px; }
/* Цвет — только там, где он что-то решает: ДРР. Раскрашивать все плитки
   значит обесценить цвет. */
.ads-tile.ok .ads-tile-v { color: var(--accent); }
.ads-tile.warn .ads-tile-v { color: #d9820b; }
.ads-tile.bad .ads-tile-v { color: var(--red); }

.ads-note {
  flex: 1 1 100%; font-size: var(--fs-caption); color: var(--text-muted);
  padding: 8px 12px; background: var(--surface2); border-radius: 8px;
}

/* ── Карточка кампании ───────────────────────────────────────────────────── */
.ads-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
  margin-bottom: 10px; overflow: hidden;
}
.ads-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  width: 100%; padding: 12px 14px;
  border: none; background: none; color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.ads-head:hover { background: var(--accent-weak); }
.ads-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Уголок — единственное, что до нажатия говорит «здесь можно раскрыть». */
.ads-chev {
  width: 0; height: 0; flex: none;
  border-left: 5px solid var(--text-3);
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform var(--t-fast) var(--ease);
}
.ads-card.is-open .ads-chev { transform: rotate(90deg); }

.ads-name { font-weight: 600; }
.ads-type { font-size: var(--fs-caption); color: var(--text-3); }
/* Числа вправо: слева «что это», справа «сколько» — строка читается как
   предложение, а не как каша. */
.ads-nums { margin-left: auto; display: flex; gap: 12px; align-items: baseline; }
.ads-nums i { font-style: normal; font-size: var(--fs-caption); color: var(--text-muted); }
.ads-drr.ok { color: var(--accent); }
.ads-drr.warn { color: #d9820b; }
.ads-drr.bad { color: var(--red); }

.ads-body { padding: 0 14px 12px; }

/* ── Артикул ─────────────────────────────────────────────────────────────── */
.ads-art { border-top: 1px solid var(--border); }
.ads-art-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 0;
  cursor: pointer; transition: background var(--t-fast) var(--ease);
}
.ads-art-row:hover { background: var(--accent-weak); }
.ads-art-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Фото обязательно: по картинке товар узнают мгновенно, по числу вроде
   194512883 — вспоминают. */
.ads-photo {
  width: 40px; height: 52px; flex: none;
  object-fit: cover; border-radius: 6px; background: var(--surface2);
}
.ads-photo-none { display: block; }

.ads-art-main { min-width: 0; flex: 1; }
.ads-art-name {
  font-size: var(--fs-footnote); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ads-art-sub { font-size: var(--fs-caption); color: var(--text-3); margin-top: 2px; }

/*
 * ⚠️ СЕТКА, А НЕ FLEX. С `flex` каждая колонка подгонялась под своё
 * содержимое, и подписи в шапке не вставали над числами: «21 073» шире слова
 * «Показы», «10 ₽» уже слова «Корзина» — колонки разъезжались, и таблица
 * читалась как набор цифр. Проверено: ширины шапки и строки не совпадали.
 *
 * `grid-template-columns` задан ОДИН раз и наследуется обоими — шапкой и
 * строками. Разъехаться они теперь не могут: определение общее.
 *
 * `minmax(0, 1fr)` вместо `1fr`: без нуля длинное число распирает свою колонку
 * и ломает остальные — известная ловушка сетки.
 */
.ads-art-nums {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
  gap: 10px; flex: none; width: 600px;
}
.ads-art-nums > span {
  text-align: right;
  font-size: var(--fs-caption); font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ads-art-nums .ok { color: var(--accent); }
.ads-art-nums .warn { color: #d9820b; }
.ads-art-nums .bad { color: var(--red); }

/* ── Фразы ───────────────────────────────────────────────────────────────── */
.ads-phrases {
  /* Таблица шире окна на узком экране — пусть прокручивается внутри себя,
     иначе либо обрежется, либо поедет вбок вся страница. */
  overflow-x: auto;
  background: var(--surface2); border-radius: 8px;
  margin: 0 0 10px;
}
.ads-ph-table { width: 100%; border-collapse: collapse; font-size: var(--fs-caption); }
.ads-ph-table th, .ads-ph-table td {
  padding: 7px 10px; text-align: left; white-space: nowrap;
}
.ads-ph-table th { color: var(--text-3); font-weight: 600; }
.ads-ph-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.ads-ph-table tbody tr:hover { background: var(--accent-weak); }

@media (max-width: 900px) {
  /* На узком экране числа артикула уходят под название: в строку они не
     помещаются и режут имя товара до неузнаваемости. */
  .ads-art-row { flex-wrap: wrap; }
  /* На узком экране колонок девять — они не помещаются. Разбиваем на три
     ряда по три: подписи остаются над своими числами, потому что сетка одна
     и та же у шапки и у строк. */
  .ads-art-nums { width: 100%; padding-left: 52px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ads-nums { width: 100%; margin-left: 0; }
}

/* Шапка колонок для строк артикулов.
   ⚠️ Ширины совпадают со строками намеренно: и здесь, и там числа лежат в
   .ads-art-nums > span с одной min-width. Задать их по отдельности значит
   гарантировать, что однажды они разъедутся. */
.ads-art-head {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 0 4px;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-caption); color: var(--text-3);
  font-weight: 600;
  /*
   * ⚠️ БЕЗ КАПСА. Он расширяет текст примерно на десятую часть, и «Корзина»
   * с «Заказы» переставали помещаться в колонку: на экране оставалось
   * «КОРЗ…» и «ЗАКА…». Подпись, которую надо угадывать, не лучше её
   * отсутствия — а ради чего терпеть, если капс здесь ничего не добавляет.
   */
}
.ads-art-head .ads-art-main { flex: 1; min-width: 0; }
.ads-art-head .ads-photo-none { background: none; }

/* Поле поиска в шапке рекламы. Шире остальных: в него вводят артикулы вроде
   LIL-PLT-002 и названия кампаний, а не выбирают из двух вариантов. */
.ads-search { width: 260px; max-width: 100%; }

@media (max-width: 720px) {
  /* На узком экране поиск занимает свою строку целиком: рядом с двумя
     выпадающими списками от него остаётся полоска, в которую не влезает
     даже артикул. */
  .ads-search { width: 100%; order: -1; }
}

/* Кнопка пополнения бюджета.
   ⚠️ Единственная кнопка в кабинете, которая тратит деньги. Поэтому она НЕ
   похожа на остальные: отдельный кружок, а не пункт в ряду. Слиться с
   соседними «обновить» и «раскрыть» ей нельзя — по ним жмут не глядя. */
.ads-plus {
  position: absolute; top: 9px; right: 12px;
  width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface); color: var(--accent);
  font: inherit; font-size: 17px; line-height: 1; font-weight: 700;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
/* ⚠️ --accent-ink, а не #fff. В тёмной теме акцент — яркий зелёный #17b871,
   и белый на нём даёт 2,58:1 при норме 4,5. Токен заведён ровно для этого:
   он тёмный там, где фон светлый, и наоборот. Проверка контраста поймала. */
.ads-plus:hover {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.ads-plus:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Карточке нужен свой контекст: кнопка прижата к её углу. */
.ads-card { position: relative; }
/* Место под кнопку, чтобы длинное название не заезжало под неё. */
.ads-head { padding-right: 46px; }

@media (max-width: 720px) {
  /* На касании 26px — промах через раз, а промах здесь открывает диалог
     о деньгах. Область нажатия растим невидимым слоем поверх. */
  .ads-plus::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
}

/* ═══════════ А/Б ТЕСТЫ ═══════════
   Раздел про выбор между вариантами фото. Всё оформление подчинено одному:
   варианты должны сравниваться глазами, а не по числам подряд. Поэтому
   фотографии стоят полосой в шапке карточки — до всяких показателей видно,
   что вообще сравнивается, — а победитель обведён.

   ⚠️ Обводка победителя дублируется галочкой в строке показателей: цвет как
   единственный носитель смысла отсекает тех, кто зелёный от серого не
   отличает. */
.ab-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md, 12px);
  margin-bottom: 10px; overflow: hidden;
}

.ab-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.ab-head[hidden] { display: none; }
.ab-head:hover { background: var(--accent-weak); }
.ab-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.ab-head-main { min-width: 0; flex: 1; }
.ab-name { font-weight: 600; }
.ab-sub { font-size: var(--fs-caption); color: var(--text-3); margin-top: 2px; }

.ab-card.open .ads-chev { transform: rotate(90deg); }

/* Состояние — словом, не точкой: «на паузе» и «остановлен» точкой не
   различишь, а разница между ними решает, можно ли применять победителя. */
.ab-state {
  flex: none; font-size: var(--fs-caption); font-weight: 600;
  padding: 3px 10px; border-radius: var(--radius-full);
  background: var(--surface3); color: var(--text-2);
}
.ab-state-ok   { background: var(--accent-weak); color: var(--accent-text); }
/* ⚠️ Не свой оранжевый: собственный #a9600a на своей же подложке давал
   4,10:1 в светлой теме и 3,12:1 в тёмной — страж контраста поймал. Готовая
   пара --yellow-weak / --fbs-warn-text переопределена под обе темы. */
.ab-state-warn { background: var(--yellow-weak); color: var(--fbs-warn-text); }
.ab-state-done { background: var(--surface3); color: var(--text-2); }

/* ── Полоса вариантов ─────────────────────────────────────────────────── */
.ab-strip {
  display: flex; gap: var(--sp-2); flex-wrap: wrap;
  padding: 0 var(--sp-4) var(--sp-3);
}
.ab-strip[hidden] { display: none; }

.ab-thumb {
  position: relative; width: 64px; height: 84px; flex: none;
  border-radius: var(--radius-xs); overflow: hidden;
  border: 2px solid var(--border);
  background: var(--surface2);
}
.ab-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ab-thumb.win { border-color: var(--accent); }

/* Номер варианта. Он же — единственная связь между фотографией наверху и
   строкой показателей внизу: без него в таблице «вариант 3» не с чем
   соотнести. */
.ab-thumb-n {
  position: absolute; left: 3px; top: 3px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-caption2); font-weight: 700;
  border-radius: var(--radius-full);
  background: rgba(0,0,0,.62); color: #fff;
}
.ab-thumb-n[hidden] { display: none; }
.ab-thumb.win .ab-thumb-n { background: var(--accent); color: var(--accent-ink); }

/* ── Показатели ───────────────────────────────────────────────────────── */
.ab-body { padding: 0 var(--sp-4) var(--sp-3); border-top: 1px solid var(--border); }
.ab-body[hidden] { display: none; }

/* ⚠️ СЕТКА, А НЕ ФЛЕКС. Шапка и строки — отдельные элементы; флекс подогнал
   бы каждую колонку под своё содержимое, и подпись «ЗАКАЗЫ» над числом 1
   оказалась бы шире такой же колонки над числом 128. Общая
   grid-template-columns разъехаться не может по устройству. */
.ab-table { margin-top: var(--sp-3); overflow-x: auto; }
.ab-row {
  display: grid;
  grid-template-columns: 92px repeat(8, minmax(58px, 1fr));
  align-items: center; gap: var(--sp-2);
  padding: 6px var(--sp-2); min-width: 620px;
  font-variant-numeric: tabular-nums;
}
.ab-row[hidden] { display: none; }
.ab-row > div { text-align: right; }
.ab-row > div:first-child { text-align: left; }

.ab-row-head {
  font-size: var(--fs-caption); color: var(--text-3); font-weight: 600;
  border-bottom: 1px solid var(--border);
}
.ab-row + .ab-row { border-top: 1px solid var(--border); }
.ab-row-head + .ab-row { border-top: 0; }

/* Победитель: фон, рамка и галочка. Три носителя вместо одного цвета —
   иначе строку не найдут ни при дальтонизме, ни при печати. */
.ab-row.win { background: var(--accent-weak); border-radius: var(--radius-xs); }
.ab-row.win .ab-row-img::after {
  content: '✓'; color: var(--accent-text); font-weight: 700; margin-left: 4px;
}

.ab-row-img { display: flex; align-items: center; gap: 6px; }
.ab-row-img[hidden] { display: none; }
.ab-row-img img {
  width: 26px; height: 34px; object-fit: cover;
  border-radius: 4px; border: 1px solid var(--border); flex: none;
}

.ab-score { font-weight: 700; }
.ab-sum {
  margin-top: var(--sp-2); font-size: var(--fs-caption); color: var(--text-3);
}

/* ── Действия ─────────────────────────────────────────────────────────── */
.ab-acts {
  display: flex; gap: var(--sp-2); flex-wrap: wrap;
  padding: 0 var(--sp-4) var(--sp-3);
}
.ab-acts[hidden] { display: none; }
.ab-acts:empty { display: none; }

@media (max-width: 720px) {
  /* Кнопки на всю ширину: 28px по высоте на касании — промах через раз, а
     промах здесь может означать остановленный тест. */
  .ab-acts .btn { flex: 1 1 100%; min-height: 44px; }
}

/* ── Форма запуска ──────────────────────────────────────────────────────
   ⚠️ Свёрнута по умолчанию (атрибут hidden в разметке). Открытая настройка
   над списком провоцирует нажать «Запустить», не дочитав, — а нажатие
   начинает менять фотографии на живой карточке. */
.ab-new { margin-bottom: var(--sp-4); }
.ab-new[hidden] { display: none; }

.ab-new-title { font-weight: 600; margin-bottom: 4px; }
.ab-new-hint { max-width: 68ch; line-height: 1.55; margin: 0 0 var(--sp-4); }

/* Поля сеткой: на широком экране в два столбца, на узком в один.
   auto-fit с minmax делает это без единого медиазапроса. */
/* ⚠️ ОДНА КОЛОНКА, А НЕ СЕТКА ПО ШИРИНЕ.
 *
 * Было `repeat(auto-fit, minmax(260px, 1fr))`: на широком экране поля
 * раскладывались в три колонки, и взгляд при заполнении прыгал зигзагом —
 * вправо, вниз, влево. Настройка теста читается сверху вниз (режим → объём →
 * пауза → когда), и порядок должен быть виден глазами, а не угадываться.
 *
 * Ширину ограничиваем: поле на весь монитор — это строка ввода длиной в метр,
 * в которой не видно ни начала, ни конца.
 */
.ab-fields {
  display: flex; flex-direction: column; gap: var(--sp-3);
  max-width: 460px; margin-bottom: var(--sp-3);
}
.ab-fields[hidden] { display: none; }

.ab-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ab-field[hidden] { display: none; }
.ab-field > span { font-size: var(--fs-caption); color: var(--text-3); font-weight: 600; }
/* ⚠️ Ширину задаём здесь: у .ads-select её нет, а в сетке без min-width:0
   длинная подпись варианта растянет колонку и разорвёт вторую. */
.ab-field > select { width: 100%; min-width: 0; }

.ab-check {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-4); cursor: pointer;
  /* 28px — нижняя граница попадания курсором; для галочки с подписью этого
     хватает, потому что нажимается вся строка, а не квадратик. */
  min-height: 28px;
}
.ab-check[hidden] { display: none; }

.ab-upload { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.ab-upload[hidden] { display: none; }
.ab-upload-hint { font-size: var(--fs-caption); }

.ab-picked { padding: var(--sp-3) 0 0; }
.ab-picked:empty { display: none; }

/* Крестик на превью. Появляется всегда, а не по наведению: на касании
   наведения нет вовсе, и на телефоне вариант стало бы не убрать. */
.ab-thumb-x {
  position: absolute; right: 3px; top: 3px;
  width: 18px; height: 18px; padding: 0; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-full); cursor: pointer;
  background: rgba(0,0,0,.62); color: #fff; font-size: 13px;
}
.ab-thumb-x[hidden] { display: none; }
.ab-thumb-x:hover { background: var(--red, #bd2c29); }
.ab-thumb-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (max-width: 720px) {
  /* На касании 18px — промах через раз. Область нажатия растим невидимым
     слоем поверх, не раздувая сам крестик. */
  .ab-thumb-x::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
}

.ab-new-acts { padding: var(--sp-4) 0 0; }

.ab-msg {
  margin-top: var(--sp-2); font-size: var(--fs-caption);
  color: var(--text-2);
}
.ab-msg[hidden] { display: none; }
.ab-msg.bad { color: var(--red); }

/* ── Бегунок-переключатель на несколько положений ───────────────────────────
 *
 * ⚠️ ПОЧЕМУ НЕ ОБЫЧНЫЙ ТУМБЛЕР НА ДВА ПОЛОЖЕНИЯ. Фильтр «с остатками» и
 * «без остатков» — это не «включено/выключено»: между ними есть третье
 * состояние «показать всё», и оно нужно чаще обоих. Тумблер такое состояние
 * либо прячет, либо подменяет одним из двух — и список молча оказывается
 * урезанным, а человек об этом не знает.
 *
 * Поэтому положений три, а вид — тот же скользящий бегунок: подложка едет под
 * выбранный пункт, а не подсвечивает его рамкой. Движение и показывает, что
 * это один переключатель, а не три отдельные кнопки.
 */
.seg-slider {
  position: relative; display: inline-flex; align-items: center;
  background: var(--surface3); border-radius: 999px; padding: 3px;
  border: 1px solid var(--border);
  /* ⚠️ Не растягиваться, даже если родитель окажется flex-контейнером. У нас
     body именно такой, и без этой строки переключатель, вынесенный из карточки,
     вытягивается во всю высоту экрана. Проверено — выглядит как поломка. */
  align-self: flex-start; flex: 0 0 auto;
}
.seg-slider__thumb {
  position: absolute; top: 3px; bottom: 3px; left: 3px;
  border-radius: 999px; background: var(--accent);
  /* Едет по обеим осям одним переходом: ширина у пунктов разная, и без
     анимации ширины бегунок прыгал бы, а не ехал. */
  transition: transform .22s cubic-bezier(.32,.72,0,1), width .22s cubic-bezier(.32,.72,0,1);
  pointer-events: none;
}
.seg-slider button {
  position: relative; z-index: 1; border: 0; background: none; cursor: pointer;
  padding: 6px 14px; border-radius: 999px; white-space: nowrap;
  font-size: var(--fs-footnote); font-weight: 600; color: var(--text-2);
  transition: color .18s;
}
.seg-slider button.on { color: var(--accent-ink); }
/* ⚠️ Пока бегунок не встал на место (первая отрисовка), он спрятан: иначе он
   на мгновение появляется слева и уезжает — выглядит как сбой. */
.seg-slider__thumb[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .seg-slider__thumb { transition: none; }
}

/* ── Авто-пополнение рекламы ────────────────────────────────────────────────
 *
 * ⚠️ Кнопка стоит РЯДОМ с «+», но выглядит иначе. Соседство не случайно: обе
 * про деньги кампании. Разный вид тоже не случаен: «+» тратит один раз и по
 * нажатию, «авто» разрешает тратить каждый день без нажатий. Одинаковые кнопки
 * для этих двух действий были бы ловушкой.
 */
.ads-auto {
  position: absolute; top: 9px; right: 44px;
  height: 26px; padding: 0 10px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text-2);
  font: inherit; font-size: var(--fs-caption); font-weight: 700; line-height: 1;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.ads-auto:hover { border-color: var(--accent); color: var(--accent); }
/* Включённое правило видно с карточки, не открывая ничего: списание, о котором
   не знаешь, — худшее, что здесь может быть. */
.ads-auto-on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

.topup-scrim {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
  background: rgba(0, 0, 0, .45);
  overflow-y: auto;
}
.topup-box {
  width: 100%; max-width: 560px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px;
  padding: var(--sp-5);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .28);
}
.topup-head { display: flex; align-items: baseline; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.topup-head b { font-size: var(--fs-callout); }
/* ⚠️ Объяснение стоит ПЕРЕД полями, а не под ними. Это не форма настроек, а
   согласие: что произойдёт, надо прочитать до того, как заполнять. */
.topup-lead { margin: 0 0 var(--sp-4); color: var(--text-2); font-size: var(--fs-footnote); }

.topup-switch { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4);
  font-weight: 600; cursor: pointer; }
.topup-switch input { width: 18px; height: 18px; accent-color: var(--accent); cursor: pointer; }

.topup-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.topup-grid label { display: flex; flex-direction: column; gap: 6px;
  font-size: var(--fs-caption); color: var(--text-2); }
.topup-inp { display: flex; align-items: center; gap: 6px; }
.topup-inp input, .topup-grid select {
  flex: 1; min-width: 0; padding: 7px 9px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface2); color: var(--text); font: inherit;
}
.topup-inp i { font-style: normal; color: var(--text-3); white-space: nowrap;
  font-size: var(--fs-caption); }

/* ⚠️ Потолок суток — жирным и отдельной строкой. «500 ₽ три раза» читается как
   «пятьсот рублей», а это полторы тысячи в сутки и сорок пять в месяц. Число
   должно стоять перед глазами, а не в уме. */
.topup-cap { margin-top: var(--sp-4); padding: 10px 12px; border-radius: 10px;
  background: var(--surface2); font-size: var(--fs-footnote); }

.topup-warn {
  margin-top: var(--sp-3); padding: 12px 14px; border-radius: 10px;
  /* ⚠️ color-mix от --red, а не зашитый rgba. У нас красный РАЗНЫЙ в светлой
     и тёмной теме (#bd2c29 против #ff8098), и зашитое значение в тёмной теме
     дало бы подложку не того оттенка — предупреждение выглядело бы чужим
     элементом, приклеенным к интерфейсу. */
  background: color-mix(in srgb, var(--red) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--red) 35%, transparent);
  font-size: var(--fs-footnote); line-height: 1.45;
}
.topup-warn[hidden] { display: none; }
.topup-warn b { display: block; margin-bottom: 4px; }
.topup-ack { display: flex; align-items: center; gap: var(--sp-2); margin-top: 10px;
  font-weight: 600; cursor: pointer; }
.topup-ack input { width: 18px; height: 18px; accent-color: var(--red); cursor: pointer; }
/* Сервер отказал из-за неподтверждённого риска — подсвечиваем блок, а не
   показываем сухую ошибку: человек должен увидеть, ЧТО именно не подтвердил. */
@keyframes topup-flash { 0%, 100% { transform: none; } 25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); } }
.topup-warn-flash { animation: topup-flash .35s 2; }
@media (prefers-reduced-motion: reduce) { .topup-warn-flash { animation: none; } }

.topup-foot { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-5); }

@media (max-width: 560px) { .topup-grid { grid-template-columns: 1fr; } }

/* ── А/Б тест: польза в деньгах ─────────────────────────────────────────────
 *
 * ⚠️ ЗАЧЕМ ЭТОТ БЛОК СУЩЕСТВУЕТ. Какое фото победило по CTR — видно и так, из
 * таблицы. Но «CTR 2,8% против 2,05%» не отвечает на вопрос, ради которого
 * тест затевали: СТОИТ ЛИ МЕНЯТЬ ОБЛОЖКУ. Здесь тот же результат сказан в
 * рублях — и это единственная величина, по которой решение принимается.
 *
 * ⚠️ ДЕНЬГИ ДИАПАЗОНОМ, А НЕ ОДНИМ ЧИСЛОМ, И ЭТО ВИДНО В ВЁРСТКЕ. Одна крупная
 * цифра читается как обещание; два числа через тире — как оценка, чем она и
 * является. Поэтому тире здесь не «оформление», а часть смысла, и убирать его
 * нельзя.
 */
.abf {
  border: 1px solid color-mix(in srgb, var(--green) 30%, transparent);
  background: color-mix(in srgb, var(--green) 7%, transparent);
  border-radius: 14px; padding: var(--sp-4) var(--sp-5);
}
.abf-head {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-footnote); color: var(--text-2); margin-bottom: 6px;
}
/* Сумма — самое крупное в блоке. Всё остальное здесь объясняет её. */
.abf-money {
  font-size: 28px; font-weight: var(--fw-bold); line-height: 1.15;
  color: var(--green); font-variant-numeric: tabular-nums;
}
.abf-money small { font-size: 17px; font-weight: var(--fw-semi); }
.abf-sub { font-size: var(--fs-caption); color: var(--text-2); margin-top: 4px; }

/* Сравнение двух фото. Сетка, а не флекс: колонки обязаны совпадать с
   подписями, а у флекса они разъезжаются при разной длине чисел. */
.abf-vs {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.abf-card { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.abf-card img {
  width: 54px; height: 72px; object-fit: cover; border-radius: 8px;
  background: var(--surface3); flex-shrink: 0;
}
.abf-card-body { min-width: 0; }
.abf-label { font-size: var(--fs-caption2); color: var(--text-3); white-space: nowrap; }
.abf-per-k { font-size: 19px; font-weight: var(--fw-semi); font-variant-numeric: tabular-nums; }
.abf-per-k i { font-style: normal; font-size: var(--fs-caption); color: var(--text-3); font-weight: 400; }
.abf-arrow { color: var(--text-3); font-size: 19px; }
.abf-win .abf-per-k { color: var(--green); }
.abf-gain {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 99px;
  background: var(--green-weak); color: var(--green);
  font-size: var(--fs-caption2); font-weight: var(--fw-semi);
}

/* Числа теста. Мелко и в одну строку: это доказательство, а не главное
   сообщение — но без него крупная сумма выше ничем не подтверждена. */
.abf-facts {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
  margin-top: var(--sp-3); font-size: var(--fs-caption);
  color: var(--text-2); font-variant-numeric: tabular-nums;
}
.abf-facts b { color: var(--text); font-weight: var(--fw-semi); }

/* ⚠️ ОГОВОРКА ОБЯЗАТЕЛЬНА И НЕ ПРЯЧЕТСЯ. Прогноз без границ применимости
   читается как гарантия, а потом «ваш сервис наврал». Здесь прямо сказано, на
   чём он основан и чего не учитывает. */
.abf-note {
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-caption); color: var(--text-3); line-height: 1.5;
}

/* Данных мало — тот же блок, но без денег и другого цвета: не «плохо», а
   «ещё не время». Жёлтый, а не красный: ошибки здесь нет. */
.abf-wait {
  border-color: color-mix(in srgb, var(--yellow) 35%, transparent);
  background: color-mix(in srgb, var(--yellow) 8%, transparent);
}
.abf-wait .abf-money { font-size: 19px; color: var(--yellow); }

/* Разница есть, но ещё не подтверждена объёмом. Отдельное состояние, а не
 * «почти польза»: сумма показана, но подписана как непроверенная, и рядом
 * стоит понятная задача — докрутить тест до такого-то числа заказов.
 *
 * ⚠️ Сумма здесь НЕ зелёная. Зелёный в этом интерфейсе означает «деньги
 * подтверждены»; покрасив непроверенную оценку так же, мы стёрли бы разницу
 * между «заработаете» и «возможно, заработаете». */
.abf-maybe {
  border-color: color-mix(in srgb, var(--accent) 28%, transparent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.abf-maybe .abf-money { color: var(--text); }
/* ⚠️ Обычный блок, а не flex. При flex строка «нужно ещё около 84 300
   показов» рвалась в неожиданном месте: жирное число становилось отдельным
   flex-элементом и уезжало на свою строку, а текст обтекал его по бокам.
   Здесь нужен поток текста, а не раскладка элементов. */
.abf-todo {
  margin-top: var(--sp-3); padding: 9px 12px; border-radius: 10px;
  background: var(--surface2); font-size: var(--fs-footnote); line-height: 1.5;
}
/* Число с единицей не разрывается: «84 300» на одной строке, «показов» на
   другой читается как две разные величины. */
.abf-todo b { white-space: nowrap; }

@media (max-width: 560px) {
  .abf-vs { grid-template-columns: 1fr; }
  .abf-arrow { display: none; }
}

/* Подсказка под полем настройки теста. Мелко и серым: объясняет размен, но не
   спорит за внимание с самим полем. */
.ab-hint {
  display: block; margin-top: 4px;
  font-size: var(--fs-caption2); color: var(--text-3); line-height: 1.45;
}
.ab-hint:empty { display: none; }
/* ⚠️ Без этого [hidden] проигрывает авторскому display у .ab-field — поле
   скрытого режима осталось бы на экране. В этом проекте такое уже ловили
   несколько раз, см. проверки в tools/test-abtest-ui.js. */
.ab-field[hidden], .ab-field-inline[hidden] { display: none; }
.ab-field-inline { margin-top: var(--sp-2); }
/* Поле поиска кампании стоит над списком: это не «ещё один select», а способ
   до него добраться, поэтому отступ меньше обычного. */
#ab-campaign-find { margin-bottom: 6px; }

/* ── Списки с пояснением в две строки ──────────────────────────────────────
 *
 * ⚠️ Вариант вида «По просмотрам — каждое фото набирает порог» в одну строку
 * не влезает и обрезается многоточием ровно на пояснении — то есть на том, для
 * чего пояснение и написано. Здесь название остаётся на первой строке,
 * пояснение уходит на вторую и переносится целиком.
 *
 * Отмена обрезки касается только строк с пояснением: у обычных списков (имена
 * кабинетов) обрезка нужна, иначе длинное название разорвёт панель.
 */
.ui-row-wrap { white-space: normal; text-overflow: clip; overflow: visible; line-height: 1.35; }
.ui-row-wrap .ui-row-note { margin-top: 2px; line-height: 1.35; }
/* Строка с двумя уровнями текста выше обычной — выравниваем по верху, иначе
   галочка и название «плывут» относительно друг друга. */
.ui-row:has(.ui-row-wrap) { align-items: flex-start; padding-top: 8px; padding-bottom: 8px; }
.ui-row:has(.ui-row-wrap) .ui-tick { margin-top: 2px; }

/* ⚠️ ВО ВСЮ ШИРИНУ — ТОЛЬКО В ПОЛЕ ФОРМЫ. Там над списком стоит подпись, и
   кнопка по содержимому выглядела бы обрывком.
   А в панели инструментов «Рекламы» списки стоят в ряд, и width:100% растянул
   бы каждый на всю панель — ряд развалился в столбик (проверено глазами:
   «Выберите кабинет» и «14 дней» стали по 528 пикселей друг под другом).
   Поэтому селектор строго по .ab-field, без соседского `.ads-select +`. */
.ab-field .ui-trigger { width: 100%; }

/* ── А/Б тесты: список РК ───────────────────────────────────────────────────
 *
 * ⚠️ СПИСОК СТРОКАМИ, А НЕ СЕТКА ПЛИТОК. Плитки с крупным фото разваливались
 * на реальном магазине: тридцать кампаний по нескольку товаров — это больше
 * сотни плиток, десять экранов прокрутки, по которым нужную кампанию не
 * найти. Тридцать строк читаются целиком.
 */
.ab-picker-card { margin-bottom: var(--sp-4); }
.ab-picker-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3);
}
.ab-picker-head[hidden] { display: none; }
/* ⚠️ Поиск СПРАВА, как все панели инструментов в проекте: управление держим с
   одной стороны, чтобы взгляд не бегал по экрану. */
.ab-picker-find { width: 260px; max-width: 100%; }

.ab-rk-count {
  font-size: var(--fs-caption); color: var(--text-3); margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.ab-rk-list {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
}
.ab-rk-list[hidden] { display: none; }
.ab-rk { border-bottom: 1px solid var(--border); background: var(--surface); }
.ab-rk:last-child { border-bottom: none; }
.ab-rk[hidden] { display: none; }

/* ⚠️ Сетка, а не флекс: колонки строк обязаны совпадать между собой, иначе
   номер РК в одной строке встаёт под названием в другой, и список читается
   как набор случайных слов. */
.ab-rk-head {
  display: grid; align-items: center; gap: var(--sp-3);
  grid-template-columns: 14px 96px 1fr 92px 150px 132px;
  padding: 9px 12px;
  transition: background var(--t-fast) var(--ease);
}
.ab-rk-head[hidden] { display: none; }
.ab-rk-head[role="button"] { cursor: pointer; }
.ab-rk-head[role="button"]:hover { background: var(--surface-hover); }
.ab-rk-id { font-size: var(--fs-caption); color: var(--text-2);
  font-variant-numeric: tabular-nums; }
.ab-rk-name { font-size: var(--fs-footnote); font-weight: var(--fw-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-rk-goods { font-size: var(--fs-caption); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Место кнопки у кампаний с несколькими товарами: без него колонки строк
   разъезжаются между «одним товаром» и «несколькими». */
.ab-rk-spacer { display: block; }
/* У кампании с одним товаром раскрывать нечего — шеврон гасим, но место
   оставляем, иначе строки съезжают друг относительно друга. */
.ab-chev-off { visibility: hidden; }

.ab-rk-body {
  padding: 4px 12px 10px 40px; background: var(--surface2);
  border-top: 1px solid var(--border);
}
.ab-rk-body[hidden] { display: none; }
.ab-prod {
  display: grid; align-items: center; gap: var(--sp-3);
  grid-template-columns: 34px 1fr 110px 132px;
  padding: 6px 0;
}
.ab-prod[hidden] { display: none; }
.ab-prod-img { width: 34px; height: 45px; object-fit: cover; border-radius: 6px;
  background: var(--surface3); }
/* Пустая рамка вместо битой картинки: у товара может не быть фото в
   справочнике, и знак «нет фото» честнее сломанного изображения. */
.ab-prod-none {
  display: flex; align-items: center; justify-content: center;
  font-size: 8px; color: var(--text-3); text-align: center; line-height: 1.1;
}
.ab-prod-none[hidden] { display: none; }
.ab-prod-art { font-size: var(--fs-footnote); font-weight: var(--fw-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-prod-nm { font-size: var(--fs-caption); color: var(--text-3);
  font-variant-numeric: tabular-nums; }

/* На узком экране лишние колонки убираем: шесть колонок в 360 пикселей не
   помещаются, а сжимать их значит сделать нечитаемыми все шесть. */
@media (max-width: 820px) {
  .ab-rk-head { grid-template-columns: 14px 1fr auto; }
  .ab-rk-id, .ab-rk-goods, .ab-rk-spacer { display: none; }
  .ab-prod { grid-template-columns: 34px 1fr auto; }
  .ab-prod-nm { display: none; }
  .ab-rk-body { padding-left: 12px; }
}

/* Что выбрано для теста — шапкой формы. Без неё после закрытия и повторного
   открытия формы непонятно, к какому товару она относится. */
.ab-chosen {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 12px; margin-bottom: var(--sp-3);
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface2);
}
.ab-chosen[hidden] { display: none; }
.ab-chosen:empty { display: none; }
.ab-chosen img { width: 44px; height: 58px; object-fit: cover; border-radius: 8px;
  background: var(--surface3); flex: none; }
.ab-chosen b { display: block; font-size: var(--fs-footnote); }
.ab-chosen span { font-size: var(--fs-caption2); color: var(--text-3);
  font-variant-numeric: tabular-nums; }

/* ── Область перетаскивания файлов ─────────────────────────────────────────
 *
 * ⚠️ ПУНКТИРНАЯ РАМКА — НЕ УКРАШЕНИЕ. Это единственный способ показать, что
 * сюда можно бросить файл: без неё область ничем не отличается от обычного
 * блока, и никто не догадается.
 */
.ab-drop {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 26px 18px; max-width: 460px;
  border: 2px dashed var(--border-strong); border-radius: 14px;
  background: var(--surface2); color: var(--text-2);
  text-align: center; cursor: pointer;
  transition: border-color .14s, background .14s, color .14s;
}
.ab-drop:hover, .ab-drop:focus-visible {
  border-color: var(--accent); color: var(--text); outline: none;
}
/* Отклик на занесённый файл обязателен: без него человек не понимает, попал ли
   он в область, и отпускает мимо. */
.ab-drop-over {
  border-color: var(--accent); background: var(--accent-weak); color: var(--text);
}
.ab-drop-ico { width: 40px; height: 40px; color: var(--text-3); }
.ab-drop-over .ab-drop-ico, .ab-drop:hover .ab-drop-ico { color: var(--accent); }
.ab-drop-title { font-size: var(--fs-footnote); }
.ab-drop-note { font-size: var(--fs-caption2); color: var(--text-3); }

/* ⚠️ [hidden] у блоков с авторским display гасим явно. Атрибут задаёт
   display:none в стилях браузера, а любое наше display:flex/grid его
   перебивает — элемент остаётся на экране. В этом проекте такое ловили
   несколько раз, поэтому проверка в tools/test-abtest-ui.js требует правило
   для каждого такого класса. */
.ab-picker-head[hidden] { display: none; }
.ab-picker[hidden] { display: none; }
.ab-tile[hidden] { display: none; }
.ab-tile-nophoto[hidden] { display: none; }
.ab-tile-foot[hidden] { display: none; }
.ab-chosen[hidden] { display: none; }
.ab-drop[hidden] { display: none; }

/* Переключатель видов раздела А/Б. Отдельный отступ: он относится к разделу
   целиком, а не к блоку под ним. */
.ab-views { margin-bottom: var(--sp-4); }

/* ── Заголовки групп в списке тестов ───────────────────────────────────────
 *
 * ⚠️ Идущие и завершённые — разные блоки, а не одна сортировка. У них разная
 * роль: идущий тест СМОТРЯТ (сколько набрал, не пора ли остановить),
 * завершённый ИЩУТ (чем кончилось, что поставить главным). Отсюда и разный
 * вид, и заголовки, отделяющие одно от другого.
 */
.ab-group-head {
  display: flex; align-items: center; gap: 8px;
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-footnote); font-weight: var(--fw-semi); color: var(--text-2);
}
.ab-group-head:first-child { margin-top: 0; }
.ab-group-head[hidden] { display: none; }
.ab-group-n {
  padding: 1px 8px; border-radius: 99px; background: var(--surface3);
  color: var(--text-3); font-size: var(--fs-caption2); font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}

/* ── Завершённый тест: строка ──────────────────────────────────────────────
 *
 * ⚠️ СТРОКА, А НЕ КАРТОЧКА. К законченному тесту возвращаются за одним: чем
 * кончилось и что поставить главным. Сто карточек истории — это сто экранов
 * прокрутки, сквозь которые не видно идущих тестов, то есть главного.
 */
.ab-rows {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
  background: var(--surface);
}
.ab-rows[hidden] { display: none; }
.ab-row-card { border-bottom: 1px solid var(--border); }
.ab-row-card:last-child { border-bottom: none; }
.ab-row-card[hidden] { display: none; }

/* ⚠️ Сетка, а не флекс: колонки строк обязаны совпадать между собой, иначе
   номер товара в одной строке стоит под названием кампании в другой, и
   список читается как набор случайных слов. */
.ab-row-head {
  display: grid; align-items: center; gap: var(--sp-3);
  grid-template-columns: 14px 34px 110px 1fr 120px 92px auto;
  padding: 9px 12px; cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.ab-row-head[hidden] { display: none; }
.ab-row-head:hover { background: var(--surface-hover); }
.ab-row-img { width: 34px; height: 45px; object-fit: cover; border-radius: 6px;
  background: var(--surface3); }
.ab-row-img-none { display: block; }
.ab-row-nm { font-size: var(--fs-caption); font-variant-numeric: tabular-nums;
  color: var(--text-2); }
.ab-row-name { font-size: var(--fs-footnote); font-weight: var(--fw-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ab-row-win { font-size: var(--fs-caption); color: var(--text-2); white-space: nowrap; }
.ab-row-when { font-size: var(--fs-caption); color: var(--text-3);
  font-variant-numeric: tabular-nums; white-space: nowrap; }

/* На узком экране строка превращается в две: семь колонок в 360 пикселей не
   помещаются, и сжимать их значит сделать нечитаемыми все семь. */
@media (max-width: 760px) {
  .ab-row-head { grid-template-columns: 14px 34px 1fr auto; }
  .ab-row-win, .ab-row-when { display: none; }
}

/* ── Окно настройки теста ───────────────────────────────────────────────────
 *
 * ⚠️ ОКНО, А НЕ БЛОК НА СТРАНИЦЕ. Раньше настройка стояла под списком
 * кампаний, и на экране снова оказывалось две вещи одновременно: список, по
 * которому ещё выбирают, и форма, которую уже заполняют. Окно отвечает на это
 * прямо: пока оно открыто, выбор сделан и делать больше нечего.
 */
.ab-scrim {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: flex-start; justify-content: center;
  padding: var(--sp-5) var(--sp-4);
  background: rgba(0, 0, 0, .45);
  /* Прокрутка у подложки, а не у окна: на низком экране форма длиннее
     видимой области, и прокручивать надо её целиком вместе с полями. */
  overflow-y: auto;
}
.ab-scrim[hidden] { display: none; }
/* Запираем фон от прокрутки: иначе колесо уводит страницу, окно остаётся на
   месте, и связь со списком, из которого его открыли, теряется. */
body.ab-modal-open { overflow: hidden; }

/* Внутри окна карточка формы теряет внешние отступы страницы и получает свою
   максимальную ширину: поля стоят в одну колонку, и растягивать окно на весь
   монитор незачем. */
.ab-scrim .ab-new {
  width: 100%; max-width: 560px; margin: 0;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .3);
  animation: ab-modal-in .16s cubic-bezier(.2, .9, .3, 1);
}
@keyframes ab-modal-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } }
@media (prefers-reduced-motion: reduce) {
  .ab-scrim .ab-new { animation: none; }
}

.ab-new-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin-bottom: 6px;
}
.ab-new-head[hidden] { display: none; }
/* Крестик — третий способ закрыть окно, рядом с Esc и нажатием по подложке.
   Какой из трёх человек попробует первым, зависит от привычки. */
.ab-x {
  border: 0; background: none; cursor: pointer;
  width: 30px; height: 30px; flex: none; border-radius: 8px;
  color: var(--text-3); font-size: 22px; line-height: 1;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.ab-x:hover { background: var(--surface3); color: var(--text); }

/* ── Шапка раздела А/Б: инструменты слева, обновление справа ────────────────
 *
 * ⚠️ Управление, к которому человек возвращается (поиск, фильтр), стоит с той
 * же стороны, что и заголовок: взгляд идёт сверху вниз по левому краю и не
 * прыгает через весь экран. Обновление — действие редкое и отдельное, поэтому
 * прижато к правому краю и не мешается среди полей.
 */
.ab-header,
.ads-header {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  /* Заголовка внутри раздела нет — он в шапке страницы. Поэтому панель стоит
     первой строкой и не отодвигает содержимое вниз. */
  margin-bottom: var(--sp-4);
}
/* Переключатель стоит в одном ряду с панелью, а не отдельной строкой: свой
   нижний отступ ему больше не нужен. */
.ab-header .ab-views { margin-bottom: 0; }
.ab-header[hidden], .ads-header[hidden] { display: none; }
/* ⚠️ Панель ТЯНЕТСЯ, а перенос происходит внутри неё. Без flex:1 переносилась
   сама панель целиком, и кнопка обновления уезжала на вторую строку — шапка
   становилась вдвое выше (замерено: 136 пикселей вместо 68). */
.ab-tools,
.ads-tools {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: 1 1 auto; min-width: 0;
}
.ab-tools[hidden], .ads-tools[hidden] { display: none; }

/* ⚠️ Подменённым спискам задаём ширину явно. Сам .ui-trigger растянут во всю
   ширину — он делался под выбор кабинета, который занимает всю строку формы.
   В панели инструментов это разваливает ряд: один список забирает всё место,
   остальные уходят вниз. */
.ab-header .ab-tools .ui-trigger,
.ads-header .ads-tools .ui-trigger { width: 200px; flex: none; }

/* ⚠️ ТРИ КЛАССА В СЕЛЕКТОРЕ — НЕ ПЕРЕСТРАХОВКА, А НЕОБХОДИМОСТЬ.
 *
 * Базовый сброс форм задаёт `input:not([type="checkbox"]):not([type="radio"]),
 * select, textarea { width: 100% }`, и его вес (0,2,1) ВЫШЕ, чем у пары
 * классов (0,2,0). Поэтому `.ab-tools .ads-search { width: 240px }` проигрывал
 * молча: поле растягивалось на всю панель (замерено 799 пикселей вместо 240),
 * выталкивало соседей на другие строки, и кнопка обновления уезжала вниз —
 * шапка выходила вдвое выше.
 *
 * Три класса дают 0,3,0 и перекрывают сброс. Это же объясняет, почему поле
 * поиска и в «Рекламе» шире, чем задумано, — там селектор одноклассовый. */
/* ⚠️ «Реклама» здесь не за компанию: её поле поиска растягивалось ровно по
   той же причине — селектор был одноклассовым и проигрывал сбросу форм. На
   снимке это видно как поле во всю правую половину шапки. */
.ab-header .ab-tools .ads-search,
.ads-header .ads-tools .ads-search { width: 240px; max-width: 100%; flex: none; }

/* Отодвигает обновление к правому краю, сколько бы полей ни стояло слева. */
.ab-refresh,
.ads-refresh { margin-left: auto; flex: none; }

/* ⚠️ ГАСИМ, А НЕ УБИРАЕМ ИЗ ПОТОКА.
 *
 * Элемент с `hidden` исчезает и забирает с собой своё место — шапка меняет
 * высоту, и при переключении видов всё под ней прыгает: переключатель уезжает
 * вверх, форма вниз. Погашенный занимает своё место, и на экране не двигается
 * ничего, кроме содержимого.
 *
 * pointer-events отключаем отдельно: невидимое поле всё ещё ловит нажатия, и
 * человек попадал бы курсором в пустоту, которая почему-то отзывается. */
.ab-invisible {
  visibility: hidden; pointer-events: none;
}

/* ── Телефон: кнопки «Создать тест» обязаны быть видны ──────────────────────
 *
 * ⚠️ В узкой сетке кнопка сжималась до полоски и практически исчезала:
 * колонок шесть, места нет, и `auto` отдавал ей остаток, которого не было.
 * На телефоне строка превращается в два уровня, а кнопка получает свою
 * ширину — иначе главное действие раздела найти невозможно.
 */
@media (max-width: 820px) {
  .ab-rk-head {
    grid-template-columns: 14px 1fr;
    grid-template-areas: "chev name" "btn btn";
    row-gap: 8px;
  }
  .ab-rk-head > .ab-chev { grid-area: chev; }
  .ab-rk-head > .ab-rk-name { grid-area: name; }
  .ab-rk-head > .btn { grid-area: btn; width: 100%; }
  .ab-rk-head > .pill { display: none; }

  .ab-prod {
    grid-template-columns: 34px 1fr;
    grid-template-areas: "img art" "btn btn";
    row-gap: 8px;
  }
  .ab-prod > .ab-prod-img { grid-area: img; }
  .ab-prod > .ab-prod-art { grid-area: art; }
  .ab-prod > .btn { grid-area: btn; width: 100%; }
}

/* ── Деньги рекламы в окне настройки ────────────────────────────────────────
 *
 * ⚠️ Два числа, а не одно: бюджет кампании — сколько осталось у неё, единый
 * счёт — сколько есть у кабинета. Пополнение берёт со второго и добавляет к
 * первому; не видя оба, человек не понимает, откуда возьмутся деньги.
 */
.ab-money {
  padding: 10px 12px; margin-bottom: var(--sp-3);
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface2);
}
.ab-money[hidden] { display: none; }
.ab-money-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3); padding: 3px 0;
}
.ab-money-row[hidden] { display: none; }
.ab-money-label { font-size: var(--fs-caption); color: var(--text-2); }
.ab-money-val { font-size: var(--fs-footnote); font-variant-numeric: tabular-nums; }
.ab-money-val i { font-style: normal; font-size: var(--fs-caption2); color: var(--text-3);
  font-weight: 400; }
/* Красным — только когда бюджета почти нет: 500 ₽ это минимальное пополнение
   у WB, ниже него докинуть одной суммой уже нельзя. */
.ab-money-val.bad { color: var(--red); }
.ab-money .btn { margin-top: 8px; }
.ab-money-warn {
  margin-top: 8px; font-size: var(--fs-caption); color: var(--red); line-height: 1.45;
}
/* Прочерк означает «не знаем», а не «нет денег» — и это надо сказать словами,
   иначе продавец побежит пополнять то, что полно. */
.ab-money-note {
  margin-top: 8px; font-size: var(--fs-caption2); color: var(--text-3); line-height: 1.45;
}

/* ── Переобмер габаритов: окно на главном экране ──────────────────────────
 *
 * ⚠️ ОКНО ПОЯВЛЯЕТСЯ ТОЛЬКО ПРИ ИЗМЕНЕНИИ. Нет переобмера — нет ни окна, ни
 * строки, ни зелёной галочки «всё хорошо». Блок, который в обычный день
 * сообщает «изменений нет», через неделю перестают читать — и не прочитают
 * ровно в тот день, когда он скажет обратное.
 */
.dims-modal {
  position: fixed; inset: 0; z-index: 620;
  background: rgba(0, 0, 0, .58); backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
}
/* ⚠️ [hidden] проигрывает авторскому display — без этого правила окно висело
   бы на экране всегда. Тот же капкан ловили в ленте уведомлений. */
.dims-modal[hidden] { display: none; }

.dims-panel {
  border-radius: var(--radius-lg); padding: var(--sp-5);
  width: 560px; max-width: 96vw; max-height: 90vh;
  display: flex; flex-direction: column; gap: var(--sp-4);
}

.dims-head { display: flex; align-items: flex-start; gap: var(--sp-3); }
.dims-ico {
  width: 34px; height: 34px; flex: none; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-weak); color: var(--accent-text);
}
.dims-ico .ic { width: 18px; height: 18px; }
.dims-title { font-size: var(--fs-callout); font-weight: var(--fw-bold); }
.dims-sub { font-size: var(--fs-caption); color: var(--text-3); margin-top: 2px; }
.dims-head .btn { margin-left: auto; }

.dims-views { margin: 0; }

/* Список листается внутри окна: перемерить могли и сорок товаров, а окно
   выше экрана не откроешь. */
.dims-list {
  display: flex; flex-direction: column; gap: 8px;
  max-height: 46vh; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.dims-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 12px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface2);
}
/* Место под фото держим всегда: без него строка без картинки съезжает
   относительно соседних, и список выглядит сломанным. */
.dims-photo {
  width: 48px; height: 48px; flex: none; border-radius: 8px;
  object-fit: cover; background: var(--surface3); border: 1px solid var(--border);
}
.dims-photo-none { display: block; }

.dims-body { flex: 1; min-width: 0; }
.dims-subject { font-size: var(--fs-caption); color: var(--text-3); }
.dims-nums {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: 2px; font-variant-numeric: tabular-nums;
}
.dims-was { color: var(--text-3); text-decoration: line-through; }
.dims-now { font-weight: var(--fw-bold); font-size: var(--fs-callout); }
.dims-arrow { width: 13px; height: 13px; color: var(--text-3); }
.dims-diff { font-weight: var(--fw-semi); }
.dims-diff.is-up { color: var(--red); }
.dims-diff.is-down { color: var(--green); }
.dims-pct { font-size: var(--fs-caption); color: var(--text-3); }

/*
 * ⚠️ ГЛАВНАЯ СТРОКА — В РУБЛЯХ. «Объём вырос с 7,88 до 8,4 л» это данные, а
 * не ответ. Ответ — «каждая доставка дороже на 7,28 ₽», и он должен быть
 * набран так, чтобы его прочли первым.
 */
.dims-money {
  display: flex; align-items: center; gap: 5px; margin-top: 4px;
  font-size: var(--fs-caption); font-weight: var(--fw-semi);
}
.dims-money .ic { width: 13px; height: 13px; flex: none; }
.dims-money.is-up { color: var(--red); }
.dims-money.is-down { color: var(--green); }

.dims-art {
  font-size: var(--fs-caption2); color: var(--text-3); flex: none;
  align-self: flex-start;
}

.dims-note {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface2); color: var(--text-2);
  font-size: var(--fs-caption); line-height: 1.5;
}
.dims-note .ic { width: 14px; height: 14px; flex: none; margin-top: 2px; }

.dims-actions { display: flex; gap: var(--sp-3); }
.dims-actions .btn { flex: 1; }

/* ── РНП: показатель с двумя базами одним блоком ──────────────────────────
 *
 * ⚠️ ДРР считается от двух разных баз, и обе нужны. От ЗАКАЗОВ — в колонке
 * дня: расход и заказы это события одного дня. От ВЫКУПОВ — в итоге за
 * период: выкуп приходит с задержкой 3–7 дней, и за день такое деление
 * бессмысленно, а за месяц задержка усредняется.
 *
 * Две строки подряд без склейки читались бы как два разных показателя.
 * Подпись группы сверху и отсутствие рамки между ними превращают их в один
 * показатель с двумя базами.
 */
.rnp-table tr.rnp-grouped > td,
.rnp-table tr.rnp-grouped > th { border-top: none; }
.rnp-table tr.rnp-group-top > td,
.rnp-table tr.rnp-group-top > th { border-top: 1px solid var(--border); }

/*
 * ⚠️ ПОДПИСЬ ГРУППЫ СТОИТ ПО ТОЙ ЖЕ ЛЕВОЙ ГРАНИЦЕ, ЧТО И ОСТАЛЬНЫЕ НАЗВАНИЯ.
 *
 * Сначала я сдвинул её отрицательным отступом, чтобы «вытащить» из-под
 * вложенной подписи, — и она вылезла за край колонки: все имена метрик стоят
 * в 12 пикселях от границы, а «ДРР» оказалась в нуле. Строка читалась как
 * съехавшая.
 *
 * Отступ теперь на вложенной подписи, а не на ячейке: группа стоит как все,
 * а уточнения базы сдвинуты вправо под ней.
 */
.rnp-group-name {
  display: block;
  font-weight: var(--fw-bold); color: var(--text);
}
.rnp-sub-label { display: block; padding-left: 14px; color: var(--text-2); }

/* ── Оцифровка ───────────────────────────────────────────────────────────
 *
 * Двенадцать плиток одной сеткой. Порядок читается сверху вниз как отчёт:
 * сколько пришло → что из этого забрали → что осталось. Последняя плитка —
 * чистая прибыль, ради неё всё остальное.
 */
.digi-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.digi-bar .ads-select { width: 150px; flex: none; }
/* Кабинет шире периода: имена магазинов длиннее «30 дней». */
.digi-bar #digi-cabinet.ads-select { width: 220px; }
/* Календарь: подменённая кнопка, ширина под «15 сентября 2026». */
.digi-bar .ui-trigger-date { width: 170px; flex: none; }
.digi-dash { color: var(--text-3); }
/*
 * ⚠️ СЕЛЕКТОР С ИМЕНЕМ ТЕГА — НЕОБХОДИМОСТЬ, А НЕ ПЕРЕСТРАХОВКА.
 *
 * Базовый сброс форм задаёт `input:not([type=checkbox]):not([type=radio]),
 * select, textarea { width: 100% }`, и его вес (0,2,1) БОЛЬШЕ пары классов
 * (0,2,0). `.digi-bar .digi-date { width: 160px }` проигрывал молча: поля дат
 * растягивались на всю строку, и календарь с периодом уезжали под них.
 *
 * Это третий раз за день: тот же сброс уже ломал поиск в «Рекламе», в А/Б
 * тестах и в РНП. `input.digi-date` даёт (0,2,2) и перекрывает его.
 */
.digi-bar input.digi-date { width: 160px; flex: none; }

/*
 * ⚠️ «НЕ ЗАДАНА» — КНОПКА, А НЕ ПОДПИСЬ. Сообщить о пропуске и не дать его
 * закрыть — это полработы: человек видит проблему, соглашается с ней и идёт
 * дальше, потому что искать, где её править, дороже, чем потерпеть.
 */
button.digi-nocost {
  background: none; border: none; padding: 0; font: inherit;
  cursor: pointer; color: var(--yellow); font-weight: var(--fw-semi);
  border-bottom: 1px dashed currentColor;
}
button.digi-nocost:hover { color: var(--text); }
/* ⚠️ Три класса: базовый сброс форм (0,2,1) бьёт пару классов и растягивает
   поле на всю строку. Тот же капкан, что в «Рекламе», А/Б и РНП. */
.digi-items-head .digi-search.ads-select { width: 240px; max-width: 100%; flex: none; }

/* Сравниваемые периоды и дата данных — прижаты вправо и мелко: их читают
   один раз, убедиться, а не каждый раз. */
.digi-when {
  margin-left: auto; text-align: right;
  font-size: var(--fs-caption); color: var(--text-3); line-height: 1.5;
}
.digi-when b { color: var(--text-2); font-weight: var(--fw-semi); }
/* ⚠️ «Финотчёт не загружен» — предупреждение, а не подпись. Без него пустые
   плитки выглядят как «продаж не было», хотя это «мы ещё не спросили». */
.dg-warn { color: var(--red); font-weight: var(--fw-semi); }

.digi-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  margin-bottom: 20px;
}
.dg-tile { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
/* Расходные плитки тёплым фоном, прибыльная зелёным: группы видно боковым
   зрением, без чтения подписей. */
.dg-cost { background: linear-gradient(180deg, var(--yellow-weak), transparent 60%); }
.dg-net  { background: linear-gradient(180deg, var(--green-weak), transparent 60%); }

.dg-head { display: flex; align-items: center; gap: 8px; }
/* Размер как у значков навигации: мельче — значок превращается в точку,
   крупнее — спорит с цифрой за внимание. */
.dg-ico { display: inline-flex; color: var(--text-3); }
.dg-ico .ic { width: 17px; height: 17px; }
.dg-title { font-size: var(--fs-footnote); font-weight: var(--fw-semi); color: var(--text-2); }
.dg-note {
  margin-left: auto; font-size: var(--fs-caption); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.dg-main { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dg-val {
  font-size: var(--fs-title2); font-weight: 800; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

/*
 * ⚠️ ЦВЕТ ИЗМЕНЕНИЯ — ПО СМЫСЛУ, А НЕ ПО ЗНАКУ. Рост расхода это плохо, рост
 * выручки — хорошо, и оба приходят с плюсом. Какой считать плохим, решает
 * сервер: это решение не оформительское.
 */
.dg-delta {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: var(--fs-caption); font-weight: var(--fw-semi);
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* Высота строки явно — иначе её задаёт глиф значка, и плашки выходят
     разной высоты. Та же причина, что у кнопок. */
  line-height: 15px;
}
.dg-delta .ic { width: 11px; height: 11px; flex: none; }
.dg-delta.is-good { background: var(--green-weak); color: var(--green); }
.dg-delta.is-bad  { background: var(--red-weak);   color: var(--red); }

.dg-rows { display: flex; flex-direction: column; gap: 5px; margin-top: 2px; }
.dg-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: var(--fs-caption); border-top: 1px solid var(--border); padding-top: 5px;
}
.dg-row:first-child { border-top: none; padding-top: 0; }
.dg-row-l { color: var(--text-3); }
.dg-row-v {
  font-weight: var(--fw-semi); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.dg-row-v .dg-delta { font-size: var(--fs-caption2); padding: 1px 5px; }

.digi-items-head {
  display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-3);
}
.digi-sub { font-size: var(--fs-caption2); color: var(--text-3); font-weight: var(--fw-regular); }
/* Прибыль и маржа красятся: ради них разбивку и открывают. */
.rnp-v.is-bad  { color: var(--red); }
.rnp-v.is-good { color: var(--green); }
/*
 * ⚠️ «Не задана» вместо нуля. Ноль себестоимости означал бы «товар достался
 * даром», и прибыль по нему выглядела бы посчитанной. Это единственное место,
 * где пропуск данных виден продавцу построчно.
 */
.digi-nocost {
  color: var(--yellow); font-weight: var(--fw-semi); cursor: help;
  border-bottom: 1px dotted currentColor;
}

/* ── Подписка кончилась ────────────────────────────────────────────────────
   Кабинет остаётся на экране, но замылен: человек видит, что его данные на
   месте и вернутся после оплаты. Цифр под размытием НЕТ — сервер их не
   прислал, и достать их из кода страницы нельзя.

   Не размываем шапку и боковое меню: уйти в настройки, к оплате и в поддержку
   человек должен как обычно, иначе продлить будет нечем. */
body.sub-lock main,
body.sub-lock .content,
body.sub-lock .page {
  filter: blur(7px) saturate(.55);
  pointer-events: none;
  user-select: none;
}
.sub-lock-bar {
  position: fixed; left: 50%; top: 40%; transform: translate(-50%, -50%); z-index: 90;
  display: grid; justify-items: center; gap: 10px; text-align: center;
  width: min(460px, calc(100vw - 32px));
  padding: 24px 26px; border-radius: 18px;
  border: 1px solid var(--border);
  background: var(--surface); color: var(--text);
  box-shadow: 0 18px 48px rgba(0,0,0,.35);
}
.sub-lock-bar b { font-size: 17px; }
.sub-lock-bar span { font-size: 13.5px; color: var(--text-2); line-height: 1.5; }
.sub-lock-bar .btn { width: auto; padding: 10px 26px; margin-top: 4px; }
@media (max-width: 560px) { .sub-lock-bar { top: 45%; } }

/* ── Приглашения ───────────────────────────────────────────────────────────
   Ссылка и список приглашённых. Состояние каждого — словами: «зарегистрирован
   без оплаты» и «оплачено» означают разные действия, и цветом это не
   объяснить. */
/* Переносим кнопку на узком экране без отдельного медиазапроса: те же
   правила, повторённые под @media, — это два места, где легко разойтись. */
.ref-link { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 10px; }
.ref-link input {
  flex: 1; min-width: 0; padding: 11px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface2); color: var(--text);
  font: inherit; font-size: 14px;
}
.ref-link .btn { flex: 0 1 auto; width: auto; padding: 11px 20px; white-space: nowrap; }
.ref-list { margin-top: 14px; display: grid; gap: 2px; }
.ref-row {
  display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
  padding: 12px 0; border-top: 1px solid var(--border);
}
.ref-row > :first-child { flex: 1 1 200px; }
.ref-when { font-size: 12px; color: var(--text-3); margin-top: 2px; }
.ref-wait { font-size: 13px; color: var(--text-2); }
.ref-state .pill-ok {
  padding: 3px 10px; border-radius: 999px; font-size: 13px; font-weight: 600;
  background: var(--accent-weak); color: var(--accent-text);
}
.ref-done { font-size: 13px; color: var(--text-3); }
.ref-empty { font-size: 13.5px; color: var(--text-3); padding: 10px 0 2px; }
.ref-act .btn { width: auto; padding: 8px 14px; font-size: 13px; }

/* ── Окно оплаты ───────────────────────────────────────────────────────────
   Тариф, срок, промокод и две кассы. Порядок сверху вниз повторяет порядок
   решений: что беру → на сколько → есть ли код → чем плачу. */
.pay-back {
  position: fixed; inset: 0; z-index: 95; display: grid; place-items: center;
  padding: 20px; background: rgba(8, 12, 22, .55); backdrop-filter: blur(3px);
  overflow-y: auto;
}
.pay { width: min(560px, 100%); padding: 0; overflow: hidden; }
.pay-head {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 20px; border-bottom: 1px solid var(--border);
}
.pay-head b { font-size: 17px; }
.pay-x {
  margin-left: auto; width: 32px; height: 32px; border: 0; border-radius: 50%;
  cursor: pointer; background: var(--surface3); color: var(--text-2); font-size: 15px;
}
.pay-why {
  padding: 12px 20px; font-size: 13.5px; color: var(--text-2);
  background: var(--surface2); border-bottom: 1px solid var(--border);
}
.pay-body { padding: 18px 20px 20px; }
.pay-load { padding: 24px 0; text-align: center; color: var(--text-3); font-size: 14px; }

.pay-plans { display: grid; gap: 8px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.pay-plan {
  display: grid; gap: 3px; justify-items: start; text-align: left; cursor: pointer;
  padding: 12px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface2);
  color: var(--text); font: inherit;
}
.pay-plan.is-on { border-color: var(--accent); background: var(--accent-weak); }
.pay-plan-sum { font-size: 13px; color: var(--text-2); }
.pay-plan-cab { font-size: 12px; color: var(--text-3); }

.pay-terms { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.pay-term {
  display: inline-flex; align-items: center; gap: 6px; min-height: 40px;
  padding: 8px 14px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface2);
  color: var(--text-2); font: inherit; font-size: 13.5px; font-weight: 600;
}
.pay-term i { font-style: normal; font-size: 11.5px; color: var(--accent-text); }
/* Текст на акцентной заливке — переменной, а не белым: в тёмной теме акцент
   светлее, и белое по нему не читается (проверка контраста это ловит). */
.pay-term.is-on { background: var(--accent-fill-a); border-color: var(--accent-fill-a); color: var(--accent-ink); }
.pay-term.is-on i { color: var(--accent-ink); opacity: .85; }

.pay-promo { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 14px; }
.pay-promo input {
  flex: 1 1 160px; min-height: 40px; padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface2); color: var(--text);
  font: inherit; font-size: 14px;
}
.pay-promo .btn { width: auto; padding: 9px 16px; font-size: 13.5px; }
.pay-promo-msg { font-size: 13px; color: var(--accent-text); font-weight: 600; }

.pay-total {
  display: flex; align-items: center; gap: 12px; margin-top: 18px;
  padding-top: 14px; border-top: 1px solid var(--border);
}
.pay-total > div { display: grid; gap: 2px; font-size: 13.5px; color: var(--text-2); }
.pay-total s { font-size: 13px; color: var(--text-3); }
.pay-total > b { margin-left: auto; font-size: 24px; letter-spacing: -.03em; }
.pay-save { margin-top: 4px; font-size: 13px; color: var(--accent-text); font-weight: 600; text-align: right; }

.pay-where { display: grid; gap: 8px; margin-top: 16px; }
.pay-err { margin-top: 10px; font-size: 13px; color: var(--danger, #e05563); }
.pay-legal { margin: 12px 0 0; font-size: 12px; color: var(--text-3); line-height: 1.5; }

/* Что действует сейчас и до какого числа продлит оплата. Стоит первым:
   продление и первая покупка — разные вопросы в голове, и ответ на «что у
   меня сейчас» должен быть до того, как человек начнёт выбирать. */
.pay-now {
  margin-bottom: 14px; padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface2); font-size: 13.5px; color: var(--text-2); line-height: 1.5;
}
.pay-now b { color: var(--text); }

.pay-extra { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.pay-extra-h {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-3); margin-bottom: 8px;
}
.pay-extra-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px 0;
}
.pay-extra-row > div:first-child { flex: 1 1 210px; display: grid; gap: 2px; }
.pay-extra-row span { font-size: 12.5px; color: var(--text-3); }
.pay-extra-row .btn { width: auto; padding: 8px 14px; font-size: 13px; }
.pay-qty {
  width: 62px; min-height: 36px; padding: 6px 8px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface2); color: var(--text);
  font: inherit; font-size: 14px; text-align: center;
}

/* Поле порога в строке уведомления: число и единица рядом, ширина по
   содержимому — трёхзначных значений тут не бывает. */
.nt-thr { display: flex; align-items: center; gap: 8px; flex: none; }
.nt-thr input {
  width: 72px; min-height: 38px; padding: 7px 9px; text-align: center;
  border-radius: 8px; border: 1px solid var(--border);
  background: var(--surface2); color: var(--text); font: inherit; font-size: 14px;
}
.nt-thr input:focus { outline: none; border-color: var(--accent); }
.nt-thr span { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
