/* Дизайн-система «Одного кабинета».
   Основа — тёмная палитра личного дашборда маркетплейсов: те же плотность,
   скругления и панели, чтобы владельцу не приходилось переучиваться.
   Приметы «Одного кабинета»: янтарный акцент, ледяной для второстепенных чисел и
   моноширинный шрифт для всех цифр — колонки выравниваются по разрядам.

   25.08.2026 переписана по макету: свой шрифт вместо системного, шкала
   отступов и кеглей, липкая шапка таблицы, полоса доли в клетке, видимая
   рамка фокуса и настоящая раскладка для телефона. Разметка `app.html` не
   тронута ни в одном месте — здесь только вид, поэтому сломаться нечему,
   кроме внешнего вида. Прежний файл — theme.css.bak-design.

   Шрифт положен рядом файлом (frontend/fonts, отдаётся роутом /fonts/…):
   внешних CDN у сервиса нет и не будет — при переезде в РФ-контур они
   отвалились бы вместе с видом. */

/* ── Шрифты ───────────────────────────────────────────────────────────────
   Golos Text — кириллица родная, рисовалась под русский интерфейс.
   JetBrains Mono — только для цифр: у него табличные знаки, и колонка
   выравнивается по разрядам сама. Оба файла переменные, одно начертание на
   все веса. */
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic.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;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin.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;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic.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: 500;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin.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;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic.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: 600;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin.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;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/GolosText-400-cyrillic.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: 700;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/GolosText-400-latin.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;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-latin.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;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/JetBrainsMono-400-latin.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;
}

/* ── Две темы, один набор переменных ───────────────────────────────────────
   Тема выбирается человеком: тёмная, светлая или «как в системе». Выбор
   лежит на пользователе (`backend/prefs.py`), а на странице стоит атрибутом
   `<html data-theme="dark|light">`.

   «Как в системе» до разметки не доходит: страница сама превращает его в
   `dark` или `light` и слушает смену системной темы. Иначе светлые значения
   пришлось бы писать ДВАЖДЫ — в своём блоке и внутри `prefers-color-scheme`,
   — и вторая тема превратилась бы во второй набор правил.

   Правило одно: цвет на экране берётся из переменной. Прямой `#hex` в
   разметке живёт ровно в одной теме и во второй читается плохо.

   25.08.2026 добавлены ДВЕ ШКАЛЫ, которых здесь не было: отступы шагом
   4 пикселя и шесть ступеней кегля. До них размер и отступ выбирались на
   глаз в каждом месте отдельно — в разметке набралось десять разных
   вертикальных зазоров и десять кеглей, и страница не держала ритм. */
:root, html[data-theme="dark"] {
  color-scheme: dark;

  --bg:      #080b10;
  --panel:   #131822;
  --panel-2: #1a2029;
  --line:    #242e3d;   /* разделитель и рамка панели: оформление, порога нет */
  --line-2:  #1d2531;   /* волосяная линия внутри панели */
  --line-strong: #5f7289;   /* граница поля и тихой кнопки: 3.32 на панели-2 */
  --text:    #e9eef6;
  --dim:     #9aa8bb;
  --faint:   #818ea3;   /* 4.94:1 на панели-2 — норма WCAG */

  --accent:  #ff5a2d;   /* основное действие: белым по нему 5.09, сам на панели 3.49 */
  --accent-soft: rgba(255, 90, 45, .18);
  --amber:   #f5a524;   /* акцент «Одного кабинета»: то, что требует внимания */
  --amber-bg: rgba(245, 165, 36, .13);
  --ice:     #7dd3fc;   /* второстепенные числа и ссылки */
  --ice-bg:  rgba(125, 211, 252, .11);
  --ok:      #4ade80;
  --ok-bg:   rgba(74, 222, 128, .11);
  --warn:    #fbbf24;
  --bad:     #f87171;
  --bad-bg:  rgba(248, 113, 113, .11);

  /* ── Линии цветных плашек ───────────────────────────────────────────────
     У линии и у заливки РАЗНЫЕ пороги: рамке по WCAG нужно 3.0 к соседнему
     цвету, заливке порога нет вовсе. Поэтому одной прозрачностью они не
     обходятся: если делать рамку из того же цвета той же прозрачностью, что
     и фон, линия ложится на почти такой же фон и контрасту взяться неоткуда.
     Так и было — 2.05..2.21 в тёмной и 1.56..1.83 в светлой при пороге 3.0.

     Отсюда отдельный токен на каждую линию. Нейтральной линии на все плашки
     не делаем: у красной плашки серая рамка — цвет перестаёт помогать.
     Отказаться от рамок тоже нельзя: заливки дают 1.11..1.26, плашка
     сливается с панелью.

     Замер на отрисованной странице, рамка к своей же заливке, тёмная/светлая:
       --amber-line  3.48 / 3.06
       --bad-line    3.76 / 3.45
       --ice-line    3.93 / 3.29
     Меряется рамка к СВОЕЙ заливке, а не к тексту: это самая тесная из двух
     её границ, и она же самая честная. Цвет при замере складывает сам браузер
     на канве — разбирать строку регулярным выражением нельзя: Chrome отдаёт
     результат color-mix() записью color(srgb ...), старый разбор её не понимал
     и МОЛЧА выкидывал пару, печатая «провалов 0», не померив ничего.
     Прибор пока лежит вне дерева: /root/_design_probe/borders.py; его место —
     tools/design/, положить туда должен хозяин каталога. */
  --amber-line: rgba(245, 165, 36, .60);
  --bad-line:   rgba(248, 113, 113, .70);
  --ice-line:   rgba(125, 211, 252, .55);

  --ozon:    #7b61ff;
  --wb:      #cb11ab;
  --ym:      #ffcc00;

  /* Что написано ПОВЕРХ цветной заливки. Своя переменная, потому что на
     тёмной теме поверх янтарного идёт почти чёрный, а на светлой — белый. */
  --on-accent: #16150f;
  --on-amber:  #1a1204;
  /* Чернила поверх ПРОИЗВОЛЬНОГО цвета — например, цвета группы,
     который выбрал продавец. Светлые ставятся при яркости фона ниже
     0.1936, тёмные выше: это точка, где их контраст сравнивается. */
  --ink-light: #ffffff;
  --ink-dark:  #10131a;
  /* Чем пишется текст метки: сам цвет для заливки слишком светлый или
     слишком тёмный, в зависимости от темы. */
  --ozon-ink: #b3a4ff;
  --wb-ink:   #f183dd;
  --ym-ink:   #ffe08a;
  /* Шапка поверх прокрутки и подложка окна. */
  --top-bg:   rgba(8, 11, 16, .88);
  --veil:     rgba(0, 0, 0, .62);
  --rail:     #0d1119;
  --shadow:   none;
  --shadow-lg: 0 24px 64px rgba(0, 0, 0, .45);

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: "Golos Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Отступы. Другие значения в разметке не заводить: ритм держится тем, что
     зазоров всего семь, а не тем, что каждый подобран. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  /* Кегли. Шесть ступеней, у каждой своя работа:
     xs — подписи над колонками и мелкие пометки;
     sm — второстепенное: «за какой период», «откуда число»;
     md — обычный текст и таблицы;
     lg — заголовок панели и крупная строка списка;
     xl — заголовок страницы;
     2xl — главное число экрана. */
  --t-xs: 11px; --t-sm: 12px; --t-md: 14px;
  --t-lg: 16px; --t-xl: 20px; --t-2xl: 28px;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;

  /* Заливка тепловой карты и включённых клеток расписания. Тон живёт
     переменной, потому что читаемость у него разная: на тёмной теме заливка
     ложится на тёмную панель, на светлой — на белую, а поверх неё в обоих
     случаях стоит `--text`. */
  --heat:    #3563ea;
  --heat-on: #4ade80;

  /* Ширина левого меню. Меняется в одном месте: её знают и меню, и отступ
     содержимого. */
  --side-w: 244px;
}

/* Светлая тема. Здесь ТОЛЬКО значения переменных — ни одного нового правила:
   иначе вторая тема начнёт жить своей вёрсткой и разъедется с первой. */
html[data-theme="light"] {
  color-scheme: light;

  --bg:      #eef1f6;
  --panel:   #ffffff;
  --panel-2: #f4f6fa;
  --line:    #dde3ed;
  --line-2:  #e9edf4;
  --line-strong: #7d8ca6;   /* 3.14 на панели-2 */
  --text:    #101726;
  --dim:     #475064;
  --faint:   #5f6878;   /* 4.96:1 на фоне страницы */

  --accent:  #c8431c;   /* коралл, затемнённый для белого фона: белым по нему 4,91 */
  --accent-soft: rgba(200, 67, 28, .09);
  --amber:   #a45a06;   /* тот же смысл, читаемый на белом */
  --amber-bg: rgba(245, 165, 36, .15);
  --ice:     #0369a1;
  --ice-bg:  rgba(3, 105, 161, .09);
  --ok:      #157347;
  --ok-bg:   rgba(21, 115, 71, .10);
  --warn:    #a16207;
  --bad:     #c0273f;
  --bad-bg:  rgba(192, 39, 63, .09);

  /* Линии плашек светлой темы: те же три токена, свои числа. */
  --amber-line: rgba(164, 90, 6, .80);
  --bad-line:   rgba(192, 39, 63, .80);
  --ice-line:   rgba(3, 105, 161, .80);

  --ozon:    #6d4dff;
  --wb:      #b30f96;
  --ym:      #b45309;

  --on-accent: #16150f;
  --on-amber: #fff;
  --ink-light: #ffffff;
  --ink-dark:  #10131a;
  --ozon-ink: #4c1d95;
  --wb-ink:   #9d174d;
  --ym-ink:   #7c2d12;
  --top-bg:   rgba(255, 255, 255, .9);
  --veil:     rgba(15, 23, 42, .38);
  --rail:     #f7f9fc;
  --shadow:   0 1px 2px rgba(16, 24, 40, .05);
  --shadow-lg: 0 24px 64px rgba(16, 24, 40, .18);
  --heat:     #2456d8;
  --heat-on:  #157347;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--t-md)/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--ice); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Рамка фокуса видна на всём, что нажимается. Без неё сервисом нельзя
   пользоваться с клавиатуры, а это не украшение, а условие работы. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ── Шапка и навигация ──────────────────────────────────────────────────── */
header.top {
  position: sticky; top: 0; z-index: 30;
  background: var(--top-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.bar { display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); flex-wrap: wrap; }
.brand { display: flex; align-items: center; gap: var(--s2); font-weight: 700; font-size: var(--t-lg); letter-spacing: -.01em; }
.brand .mark { width: 20px; height: 23px; flex: none; }
/* Знак вставлен и вне шапки — на входе, лендинге и 404. Без своего размера
   SVG без width тянется во всю ширину контейнера: пропорция знака 68×78,
   а не 24×24, как у прежнего, и старые правила его не удерживали. */
.mark { width: 20px; height: 23px; flex: none; }
.logo .mark { width: 26px; height: 30px; }
/* Знак берёт цвета из темы: ледяной круг на белом фоне не виден совсем. */
nav.tabs { display: flex; gap: 2px; flex-wrap: wrap; }
nav.tabs button {
  background: none; border: 0; color: var(--dim);
  padding: var(--s2) var(--s3); border-radius: var(--r-sm); cursor: pointer; font: inherit;
}
nav.tabs button:hover { background: var(--panel-2); color: var(--text); }
nav.tabs button.on { background: var(--panel-2); color: var(--text); font-weight: 600; }
.spacer { flex: 1; }

/* ── Панели, плитки, таблицы ────────────────────────────────────────────── */
main { padding: var(--s5); max-width: 1560px; margin: 0 auto; }
.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--s4); margin-bottom: var(--s4);
  box-shadow: var(--shadow);
}
.panel > h2 { margin: 0 0 var(--s3); font-size: var(--t-lg); font-weight: 600; letter-spacing: -.01em; }
.panel > h2 .hint { font-weight: 400; color: var(--faint); font-size: var(--t-sm); margin-left: var(--s2); }

.tiles { display: grid; gap: var(--s3); grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.tile {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--s4); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: var(--s1); min-width: 0;
}
.tile .lbl { color: var(--dim); font-size: var(--t-sm); font-weight: 500; }
/* Главное число плитки. Вес 500, а не 700: моноширинный шрифт положен
   рядом файлом в начертаниях 400 и 500, и жирнее браузер начал бы дорисовывать
   его сам — цифры поплыли бы по ширине и колонка перестала бы выравниваться. */
.tile .val {
  font: 500 var(--t-2xl)/1.1 var(--mono);
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.tile .sub { color: var(--faint); font-size: var(--t-xs); }
.tile.accent {
  /* Рамка из того же янтаря, что и подсветка, разведённая прозрачностью,
     давала 2.92 в тёмной и 2.16 в светлой при пороге 3.0. Берёт общий
     --amber-line: 3.87 / 3.57. */
  border-color: var(--amber-line);
  background: linear-gradient(180deg, var(--amber-bg), transparent 62%), var(--panel);
}
.tile.accent .val { color: var(--amber); }

table { width: 100%; border-collapse: collapse; font-size: var(--t-md); }
th, td { padding: var(--s2) var(--s3); text-align: left; white-space: nowrap; }
td { border-bottom: 1px solid var(--line-2); }
/* Шапка таблицы держится наверху при прокрутке. На сотне строк колонки без
   этого становятся безымянными: продавец видит числа и не знает, чьи они. */
th {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel-2); color: var(--dim);
  font-size: var(--t-xs); font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  border-bottom: 1px solid var(--line);
}
td.num, th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
th.num { font-family: var(--sans); letter-spacing: .06em; }
tbody tr:hover td { background: var(--panel-2); }
tbody tr:last-child td { border-bottom: 0; }
tfoot td {
  position: sticky; bottom: 0; background: var(--panel-2);
  border-top: 1px solid var(--line); border-bottom: 0; font-weight: 600;
}
.scroll { overflow-x: auto; }
/* Обёртка С ТАБЛИЦЕЙ получает высоту — иначе липкая шапка липнет к
   контейнеру, который по вертикали не прокручивается, и толку от неё нет.
   Подробности — в tools/design и в журнале за 25.08.2026. */
.scroll:has(table) { max-height: min(72vh, 720px); overflow-y: auto; }
/* Внутри окна таблице высота ни к чему: окно и так прокручивается само,
   а две прокрутки одна в другой — это способ потерять строку. */
.sheet .scroll:has(table), .win-body .scroll:has(table) {
  max-height: none; overflow-y: visible;
}

/* Полоса доли прямо в клетке: глазу она говорит больше, чем само число,
   а число остаётся рядом — сравнивать по нему всё равно придётся. */
.meter { display: flex; align-items: center; gap: var(--s2); justify-content: flex-end; }
.meter .track { width: 56px; height: 5px; border-radius: 3px; background: var(--line); overflow: hidden; flex: none; }
.meter .fill { display: block; height: 100%; border-radius: 3px; background: var(--accent); }
.meter .fill.ok { background: var(--ok); }
.meter .fill.warn { background: var(--amber); }
.meter .fill.bad { background: var(--bad); }

/* ── Полоса главных чисел ──────────────────────────────────────────────────
   Первое, что читает продавец на экране: три-четыре числа крупно. Остальные
   показатели идут ниже мелкими плитками — иначе двенадцать одинаковых плиток
   не дают глазу зацепиться, и главное тонет среди второстепенного. */
.tiles-lead { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.tiles-lead .tile .val { font-size: var(--t-2xl); }

/* Мелкая плитка: то же устройство, вдвое тише. Ставится ПОСЛЕ полосы главных
   чисел, никогда вместо неё. */
.tile.small { padding: var(--s3); }
.tile.small .val { font-size: var(--t-xl); }
.tile.small .lbl { font-size: var(--t-xs); }

/* ── Список находок ────────────────────────────────────────────────────────
   Что сервис нашёл о магазине — строками, по деньгам вниз. Деньги стоят
   справа отдельной колонкой: по ним список и читают, а текст объясняет.
   Числами их писать моноширинным обязательно — иначе колонка не сходится. */
.con-list { display: flex; flex-direction: column; }
.con-row {
  display: flex; align-items: flex-start; gap: var(--s4);
  padding: var(--s3) 0; border-bottom: 1px solid var(--line-2);
}
.con-row:last-child { border-bottom: 0; }
.con-main { flex: 1; min-width: 0; line-height: 1.5; }
.con-main b { display: block; font-weight: 600; margin-bottom: var(--s1); }
.con-main .sub { display: block; margin-top: var(--s1); }
.con-money {
  flex: none; margin-left: auto; text-align: right;
  font: 500 var(--t-lg)/1.2 var(--mono); font-variant-numeric: tabular-nums;
}
/* Сама сумма не переносится, подпись под ней — переносится. Общий
   `nowrap` на всей клетке выносил подпись «прибавка выручки против прошлого
   периода» за правый край карточки на 390. */
.con-money .val { display: block; white-space: nowrap; }
.con-money .lbl, .con-money .why {
  display: block; font: 400 var(--t-xs)/1.35 var(--sans);
  color: var(--faint); white-space: normal;
}
/* Кто закроет находку. Владелец спросил дословно: «если есть автоведение,
   почему под угрозой? ИИ же должно это нивелировать» — и был прав: отмены
   заказов автоведение рекламы закрыть не может, это остатки и сроки сборки.

   Строка тихая и стоит последней: главное в находке — что случилось и на
   сколько рублей. Кегль тот же, что у подписи источника над ней, цвет
   основной, а не блёклый: это не примечание, а ответ на вопрос «что мне
   теперь делать». Кнопка раздела встаёт в ту же строку и переносится вместе
   с ней. */
.con-fix {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2);
  margin-top: var(--s2); font-size: var(--t-xs); color: var(--text);
}

@media (max-width: 640px) {
  /* На телефоне деньги встают под текст: в строку они ужимают объяснение до
     двух слов в колонке. */
  .con-row { flex-direction: column; gap: var(--s2); }
  .con-money { margin-left: 0; }
  /* В колонке `flex: 1` растягивает объяснение на всю высоту карточки, и
     между ним и суммой остаётся пустая треть экрана. Замер 28.08.2026 на
     390: в карточке «Заказы магазина» дыра была выше самого текста. */
  .con-main { flex: 0 1 auto; }
  .con-money { text-align: left; }
}

/* Длинный вариант в списке растягивал карточку кампании до 463 пикселей при
   экране 390 — то есть выносил её за край. Список обязан ужиматься по месту,
   а не по длине самого длинного варианта. */
select { max-width: 100%; }

/* ── Графики ───────────────────────────────────────────────────────────────
   Два вида на весь сервис, и оба показывают ДИНАМИКУ — то, чего числом в
   клетке не увидеть. Ленты, кольца и пироги не заводить: доля читается
   полосой `.meter`, а сравнение — таблицей.

   Цвет у обоих берётся из строки (`currentColor`): график стоит рядом со
   своим числом и обязан быть с ним одного цвета. Отдельного правила на цвет
   графика нет и не нужно — красным делается клетка.

   Образец разметки и готовые функции — `tools/design/charts.html` и
   `tools/design/charts.js`. */

/* Спарклайн в клетке таблицы. Размер постоянный: колонка не должна прыгать
   от того, сколько у товара суток истории. */
.spark { display: block; width: 76px; height: 20px; overflow: visible; }
.spark .line { fill: none; stroke: currentColor; stroke-width: 1.5;
               stroke-linecap: round; stroke-linejoin: round; }
/* Заливка под линией — тот же цвет прозрачностью, а не градиент: градиент
   требует идентификатора, а сто одинаковых идентификаторов в таблице
   браузер разбирает как один. */
.spark .area { fill: currentColor; opacity: .16; stroke: none; }
/* Последняя точка. Она отвечает на вопрос «а сейчас-то что», и без неё
   глаз ищет конец линии. */
.spark .dot  { fill: currentColor; }
/* Разрыв в ряду. Сутки без строки статистики и сутки с нулём — разное, и
   на графике они обязаны выглядеть по-разному: линия прерывается. */
.spark .gap  { stroke: currentColor; opacity: .28; stroke-dasharray: 2 2; }

/* Линейный график: расход и выручка по дням. Соотношение сторон постоянное,
   ширину задаёт колонка — растягивать по одной оси нельзя, иначе штрихи
   толстеют, а точки сплющиваются. */
.chart { display: block; width: 100%; height: auto; }
.chart .grid { stroke: var(--line); stroke-width: 1; }
.chart .axis { fill: var(--faint); font: 500 11px var(--sans); }
.chart .axis.right { text-anchor: end; }
.chart .area { opacity: .14; stroke: none; }
.chart .line { fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.chart .dot  { r: 4; }
/* Второй ряд — пунктиром, а не только другим цветом: два ряда обязаны
   различаться и на чёрно-белой печати, и у того, кто не видит красное. */
.chart .line.second { stroke-dasharray: 6 4; }
/* Порог — тонкая пунктирная поперечина: «сюда нельзя» или «столько надо». */
.chart .limit { stroke: var(--amber); stroke-width: 1.5; stroke-dasharray: 5 4; fill: none; }

/* Подпись рядов. Стоит ПОД графиком и словами — цвет линии сам по себе
   ничего не значит, пока не сказано, что он значит. */
.chart-legend { display: flex; gap: var(--s4); flex-wrap: wrap;
                margin-top: var(--s2); font-size: var(--t-sm); color: var(--dim); }
.chart-legend span { display: inline-flex; align-items: center; gap: var(--s2); }
.chart-legend i { width: 14px; height: 2px; flex: none; border-radius: 2px; background: currentColor; }
.chart-legend .dash { background: none;
  background-image: linear-gradient(90deg, currentColor 60%, transparent 60%);
  background-size: 5px 2px; height: 2px; }
/* Цвет ряда достаётся ОБРАЗЦУ, а не слову: у образца норма 3.0 как у
   нетекстового элемента, а у слова 4.5, и синий её не добирал. */
.chart-legend .c-accent > i { color: var(--accent); }
.chart-legend .c-amber  > i { color: var(--amber); }
.chart-legend .c-ice    > i { color: var(--ice); }
.chart-legend .c-ok     > i { color: var(--ok); }
.chart-legend .c-bad    > i { color: var(--bad); }
/* Пояснение к графику: за какой отрезок и откуда числа. Без него график —
   картинка, а не показание. */
.chart-legend .why { color: var(--faint); }

/* Пусто у графика выглядит пусто, а не пустым полотном: полотно читается
   как «ноль», а ноль и «нет данных» — разное. */
.chart-none { display: flex; align-items: center; justify-content: center;
              min-height: 120px; color: var(--faint); font-size: var(--t-sm);
              border: 1px dashed var(--line); border-radius: var(--r-md); }

/* Изменение к прошлому отрезку. Со знаком и стрелкой, а не одним цветом:
   восьмой мужчина из ста не отличает зелёное от красного. */
.delta {
  display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--mono); font-size: var(--t-sm); font-weight: 500;
  padding: 2px var(--s2); border-radius: 20px;
}
.delta.up   { color: var(--ok);    background: var(--ok-bg); }
.delta.down { color: var(--bad);   background: var(--bad-bg); }
.delta.warn { color: var(--amber); background: var(--amber-bg); }
.delta.flat { color: var(--faint); background: var(--panel-2); }

/* ── Метки и состояния ──────────────────────────────────────────────────── */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px var(--s2); border-radius: 20px;
  font-size: var(--t-sm); font-weight: 600; line-height: 1.6; white-space: nowrap;
}

/* Метка группы. Цвет выбирает ПРОДАВЕЦ, он приходит из базы, поэтому им
   нельзя заливать фон: белые чернила и тёмные сравниваются по контрасту при
   яркости фона 0.1936, и ровно там обе дают 4.31 при норме 4.5 — то есть
   существует полоса цветов, на которых метку не прочитать никакими
   чернилами. Полоса узкая (0.184–0.204), но выбрать цвет оттуда продавцу
   ничто не мешает, а показать ему нечитаемую метку сервис не имеет права.

   Поэтому цвет продавца — ТОЧКА, а фон и чернила метка берёт из темы. Точке
   как нетекстовому элементу нужен контраст 3.0, и она держит его на любом
   цвете, потому что стоит на своём фоне, а не на цветном.

       <span class="chip group" style="--group:#22c55e">Пледы</span>

   Цвета нет — переменную можно не ставить: точка станет тусклой, и группа
   без цвета будет выглядеть группой без цвета. */
.chip.group { background: var(--panel-2); color: var(--text); }
.chip.group::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--group, var(--faint));
  /* Обводка цветом темы: видна ФОРМА точки при любой заливке. Без неё
     жёлтая группа на светлой теме давала 1.42 к подложке метки, то есть
     пропадала — а точка нужна затем, чтобы узнать группу не читая. */
  box-shadow: 0 0 0 1px var(--line-strong);
}
.chip.ozon { background: color-mix(in srgb, var(--ozon) 16%, transparent); color: var(--ozon-ink); }
.chip.wb   { background: color-mix(in srgb, var(--wb) 16%, transparent);   color: var(--wb-ink); }
.chip.ym   { background: color-mix(in srgb, var(--ym) 16%, transparent);   color: var(--ym-ink); }
.chip.ok   { background: var(--ok-bg);    color: var(--ok); }
/* Янтарная метка держит ЧЕРНИЛА и отдаёт ЗАЛИВКУ.

   Янтарь у нас значит «требует внимания», и держится это на редкости: когда
   на экране семь янтарных пятен, цвет не значит ничего. А метка — существо
   мелкое и стайное: свежесть данных, «пробный», «ждёт оплаты», «сбора данных
   не было» в каждой строке таблицы. Семь заливок подряд — это не семь
   тревог, это фон.

   Гасить их нельзя: «сбора данных не было» — настоящее предупреждение, и
   ошибка в эту сторону тише и опаснее. Поэтому метка теряет только заливку:
   цвет остаётся в чернилах, текст остаётся весь. Заливка — привилегия
   плашки, то есть того единственного на экране, что стоит поперёк чтения.

   Заливка ушла у одного янтаря намеренно, а не у всех меток сразу: ok и bad
   не подчиняются правилу редкости — зелёного и красного на экране столько,
   сколько строк, и они не спорят друг с другом за внимание. */
.chip.warn { background: var(--panel-2); color: var(--amber); }
.chip.bad  { background: var(--bad-bg);   color: var(--bad); }
.chip.mute { background: var(--panel-2);  color: var(--dim); }

.muted { color: var(--dim); }
.faint { color: var(--faint); }
.good { color: var(--ok); }
.warnc { color: var(--amber); }
.badc { color: var(--bad); }
.empty { color: var(--dim); padding: var(--s7) var(--s5); text-align: center; }

/* Пустой экран, который предлагает действие, а не сообщает о пустоте.
   Разметку рисует одно место — emptyState() в app.html; здесь только вид. */
.empty.empty-offer { text-align: left; padding: var(--s5); }
.empty-title { color: var(--text); font-size: var(--t-lg); font-weight: 600; }
.empty-why { margin-top: var(--s2); max-width: 62ch; }
.empty-groups { display: flex; gap: var(--s5); flex-wrap: wrap; margin-top: var(--s4); }
.empty-group { min-width: 240px; }
.empty-group-title {
  color: var(--faint); font-size: var(--t-xs); text-transform: uppercase;
  letter-spacing: .09em; font-weight: 600; margin-bottom: var(--s2);
}
.empty-group button.empty-do { display: block; margin: 0 0 var(--s2); }

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

   Вид ОДИН на все такие блоки (`advBlock` в app.html): у автоведения он был
   первым, но «расширенные» не имеют права выглядеть по-разному в зависимости
   от того, на каком экране их открыли. */
#au-adv > summary, .adv-block > summary {
  cursor: pointer; padding: var(--s3) var(--s4); margin-bottom: var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel-2); color: var(--text); font-weight: 600;
}
#au-adv > summary:hover, .adv-block > summary:hover { border-color: var(--dim); }
#au-adv.locked > summary, .adv-block.locked > summary {
  color: var(--faint); cursor: not-allowed; background: none;
}

/* ── Рамка окна ────────────────────────────────────────────────────────────
   Одна на все окна сервиса: заголовок, объяснение под ним, содержимое и
   полоса кнопок внизу справа. Кнопка отказа стоит слева от кнопки действия —
   как у конкурента и как в системных окнах. */
.win-title { margin: 0 var(--s6) 0 0; font-size: var(--t-xl); font-weight: 600; letter-spacing: -.01em; }
.win-why { color: var(--faint); font-size: var(--t-sm); margin-top: var(--s2); max-width: 68ch; }
.win-body { margin-top: var(--s4); }
.win-foot {
  display: flex; justify-content: flex-end; gap: var(--s2); flex-wrap: wrap;
  margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--line);
}
.win-empty { padding: var(--s5); }

/* Объекта нет. Отдельное состояние, а не пустота: `win-empty` говорит «здесь
   пока ничего не происходило» и ничего не требует, а это — «вы пришли по
   ссылке в никуда», и без дороги назад человек остаётся в тупике.

   Годится и внутри окна, и целой страницей.

       <div class="gone">
         <div class="gone-title">Такой кампании нет</div>
         <div class="gone-why">Возможно, её удалили в кабинете площадки или
           она принадлежит другому магазину.</div>
         <button class="act" onclick="…">К списку кампаний</button>
       </div>

   Причину называть вероятной, а не точной: чужому объекту сервис отвечает
   404 намеренно, чтобы не подтверждать, что тот существует. Написать
   «принадлежит другому магазину» утвердительно — значит обойти эту защиту
   текстом. */
.gone {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s3);
  padding: var(--s6) var(--s5);
}
.gone-title { font-size: var(--t-lg); font-weight: 600; color: var(--text); }
.gone-why { color: var(--dim); max-width: 56ch; line-height: 1.55; }
/* Внутри окна рамка уже есть — своя лишняя, и отступ сверху меньше:
   заголовок окна стоит прямо над этим местом. */
.sheet .gone, .win-body .gone { padding: var(--s5) 0 var(--s2); }
/* Целой страницей — на своей панели, иначе текст висит в воздухе. */
main > .gone {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--shadow);
}

/* Быстрые кнопки у числового поля: «1 000 ₽ · 2 000 ₽ · 3 000 ₽ · 4 000 ₽».
   Они стоят В СТРОКУ с полем и мельче него — это заготовки ввода, а не
   отдельное действие. */
.quick { display: inline-flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }
.quick > button.quick-set { padding: var(--s1) var(--s2); font-size: var(--t-sm); }

/* Объяснение под пунктом меню действий: зачем он, одной строкой. */
.menu-note { font-size: var(--t-sm); margin: -2px 0 var(--s2) var(--s3); max-width: 42ch; }

/* Строка над таблицей: кнопка настройки и счёт колонок. Сама настройка живёт
   в окне — раскрытый блок отодвигал таблицу вниз на пол-экрана. */
.cols-bar { margin-bottom: var(--s3); gap: var(--s2); align-items: center; }
.cols-list > .row { padding: var(--s1) 0; border-bottom: 1px solid var(--line-2); }
.cols-list > .row:last-child { border-bottom: 0; }

/* Окно отбора: условия в две колонки, чтобы десяток списков не растягивал
   окно в вертикальную ленту. На узком экране колонка одна. */
.filter-grid { display: grid; gap: 0 var(--s5); grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .filter-grid { grid-template-columns: 1fr; } }
.filter-grid select, .filter-grid input { width: 100%; }

/* Меню действий, разбитое заголовками по цели. Заголовок группы — тот же,
   что у пустых экранов: один вид на все меню сервиса. */
.act-group { margin-bottom: var(--s4); }
.act-group:last-child { margin-bottom: 0; }
.act-group > .empty-group-title { margin-bottom: var(--s2); }

/* Переключатель вида списка «Плитка / Таблица». Нажатый вид виден нажатым. */
.view-switch { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.view-switch button { border-radius: 0; border: 0; background: var(--panel); color: var(--dim); }
.view-switch button + button { border-left: 1px solid var(--line); }
.view-switch button.on { background: var(--panel-2); color: var(--text); font-weight: 600; }

/* Кампании товара плитками: строка карточек, которая переносится сама. */
.camp-tiles {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.camp-tile { margin: 0; }
/* «Нет данных» и ноль — разное. Прочерк ставится тем же шрифтом, что и числа,
   чтобы колонка не сбивалась, но тусклым: это не величина. */
.nodata { color: var(--faint); font-family: var(--mono); font-style: normal; }

/* ── Управление ─────────────────────────────────────────────────────────── */
button.act, .act {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--accent); color: var(--on-accent); border: 1px solid transparent;
  border-radius: var(--r-sm); padding: 10px var(--s4); cursor: pointer;
  font: 600 var(--t-md)/1 var(--sans);
}
button.act:hover, .act:hover { filter: brightness(1.08); }
button.act:disabled { opacity: .45; cursor: default; filter: none; }
button.act.ghost, .act.ghost { background: var(--panel); border-color: var(--line-strong); color: var(--text); }
button.act.ghost:hover, .act.ghost:hover { border-color: var(--dim); filter: none; }
button.act.amber { background: var(--amber); color: var(--on-amber); }
button.act.small, .act.small { padding: var(--s2) var(--s3); font-size: var(--t-sm); }
/* Необратимое действие обязано выглядеть иначе, чем показывающее. Отличается
   оно ГРАНИЦЕЙ — заливки у него нет, кнопка стоит на панели. Значит граница
   тут не оформление, а единственный носитель смысла «назад не будет», и порог
   3.0 для неё обязателен: 2.65 в тёмной и 2.61 в светлой — это кнопка,
   неотличимая от показывающей. Берёт тот же --bad-line, что и плашка ошибки:
   один смысл — один цвет линии. Стало 3.72 / 4.21. */
button.act.danger, .act.danger {
  background: var(--panel); color: var(--bad);
  border-color: var(--bad-line);
}
button.act.danger:hover { background: var(--bad-bg); filter: none; }

input, select, textarea {
  background: var(--panel-2); border: 1px solid var(--line-strong); color: var(--text);
  border-radius: var(--r-sm); padding: 10px var(--s3); font: inherit;
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
label.field { display: block; margin-bottom: var(--s3); }
label.field > span { display: block; color: var(--dim); font-size: var(--t-sm); font-weight: 500; margin-bottom: var(--s1); }
label.field > input, label.field > select { width: 100%; }

.row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: var(--s2); }
.sep { width: 1px; height: 22px; background: var(--line); }

/* Три плашки объяснения. Ошибка, предупреждение и справка отличаются цветом
   И значком — цвета одного мало.

   Рамка берётся из своего токена `--*-line`, а НЕ разводится прозрачностью из
   того же цвета, что и заливка. Разведённая рамка лежала на почти таком же
   фоне и была невидима: 2.05..2.21 в тёмной, 1.56..1.83 в светлой при пороге
   3.0. Токены и замер — рядом с их объявлением. */
.err, .note, .info {
  display: block; border-radius: var(--r-md);
  padding: var(--s3) var(--s4); font-size: var(--t-md); line-height: 1.5;
}
.err {
  background: var(--bad-bg);
  border: 1px solid var(--bad-line);
  color: var(--bad);
}
.note {
  background: var(--amber-bg);
  border: 1px solid var(--amber-line);
  color: var(--text);
}
/* `.info` живая, хотя в разметке её не видно поиском по `class="info"`:
   экран входа ставит её из кода — `ok.className = "info"` в login.html, ветка
   «восстановить пароль». Класс, который собирается в строку, поиском по
   разметке не находится: удалять по такому замеру — снимать оформление с
   единственного спокойного сообщения на экране восстановления. */
.info {
  background: var(--ice-bg);
  border: 1px solid var(--ice-line);
  color: var(--text);
}

/* Сноска: то, что объясняет устройство, а не сообщает о беде.

   Отличать её от предупреждения на глаз не надо, признак измеримый:
   ПРЕДУПРЕЖДЕНИЕ появляется по условию, СНОСКА печатается всегда. В коде это
   видно рядом — `<div class="aside">${d.note}</div>` стоит безусловно, а
   соседнее `${d.write_enabled ? "" : `<div class="note">…`}` под условием.

   Такая сноска не имеет права быть янтарной: она висит на экране постоянно и
   съедает редкость янтаря, ничего не требуя взамен. Отсюда нейтральный вид —
   тише панели, но с границей, чтобы не слипаться с текстом вокруг. */
.aside {
  display: block; border-radius: var(--r-md);
  padding: var(--s3) var(--s4); font-size: var(--t-md); line-height: 1.5;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--dim);
}

/* ── Сверка двух чисел, которые не сошлись ────────────────────────────────
   Плашка расхождения. Появляется там, где рядом стоят две величины, обязанные
   совпасть, а они не совпали: разницу надо НАЗВАТЬ, а не подогнать одно число
   к другому и не промолчать — молчание читается как «они равны».

   Спокойная по замыслу: янтарь здесь значит «посмотрите», а не «авария».
   Деньги не теряются — просто про часть склада сервис ничего не знает.
   Поэтому красного тут нет и быть не должно, а янтарного ровно два пятна:
   значок и величина разницы.

   Слова приходят готовыми с сервера (`catalog.orphan_lines`). Экран не
   считает разницу и не сочиняет для неё вторую формулировку: у бота и у
   кабинета один источник, иначе они разойдутся к вечеру. */
.recon {
  display: flex; gap: var(--s3);
  margin-top: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--amber-bg);
  border: 1px solid var(--amber-line);
}
.recon-mark { width: 20px; height: 20px; flex: none; color: var(--amber); }
.recon-body { flex: 1; min-width: 0; }
/* Величина разницы и её название — в одной строке: «сколько» и «чего»
   читаются вместе, а не как два разных сообщения. */
.recon-head { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.recon-qty {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: var(--t-xl); font-weight: 600; line-height: 1.1;
  color: var(--amber); white-space: nowrap;
}
/* Единица мельче и тусклее самой величины — как у всех чисел сервиса. */
.recon-qty .u {
  font-family: var(--sans); font-size: var(--t-sm); font-weight: 500;
  color: var(--dim); margin-left: var(--s1);
}
.recon-title { font-size: var(--t-md); font-weight: 600; color: var(--text); }
/* Суть расхождения. */
.recon-say { margin: var(--s2) 0 0; font-size: var(--t-md); line-height: 1.5; max-width: 78ch; }
/* Причина — ВЕРСИЯ, а не факт, и тише сути: площадка присылает список того,
   что есть, а списка того, чего в нём нет, не присылает. */
.recon-why { margin: var(--s2) 0 0; color: var(--dim); font-size: var(--t-sm); line-height: 1.5; max-width: 78ch; }
/* Поимённо: что именно лежит на складе без карточки. */
.recon-list {
  margin: var(--s3) 0 0; padding: var(--s2) 0 0; list-style: none;
  border-top: 1px solid color-mix(in srgb, var(--amber) 28%, transparent);
  max-height: 168px; overflow-y: auto;
}
.recon-list li {
  color: var(--dim); font-size: var(--t-sm); line-height: 1.45;
  padding: var(--s1) 0; overflow-wrap: anywhere;
}
/* Плашка стоит между шапкой панели и полосой массовых действий. Полоса
   пуста, пока ничего не отмечено, и тогда её margin схлопывается сам; как
   только в списке что-то отмечено, без этой строки она прилипает к плашке. */
.recon + .row { margin-top: var(--s3); }
@media (max-width: 760px) {
  .recon { padding: var(--s3); gap: var(--s2); }
  .recon-say { font-size: var(--t-md); }
}

/* ── Модальное окно ─────────────────────────────────────────────────────── */
.modal {
  display: none; position: fixed; inset: 0; z-index: 40;
  background: var(--veil); padding: var(--s6) var(--s4); overflow: auto;
  backdrop-filter: blur(2px);
}
.modal.on { display: flex; align-items: flex-start; justify-content: center; }
.sheet {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--s5); width: min(920px, 100%);
  box-shadow: var(--shadow-lg);
}

/* ── Хлебные крошки и «Назад» ──────────────────────────────────────────────
   Владелец 22.08.2026: «нет функции назад, т.е. если зашел в рекламную
   компанию как попасть обратно». Крошки были в разметке, но выглядели как
   обычный серый текст и не читались как путь. Поэтому: своя полоса, крупная
   кликабельная ссылка, подчёркивание при наведении и кнопка «Назад» слева. */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2);
  padding: 0 0 var(--s3); margin-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}
.crumbs a.crumb {
  color: var(--ice); text-decoration: none; font-size: var(--t-sm);
  border-bottom: 1px dashed color-mix(in srgb, var(--ice) 45%, transparent);
}
.crumbs a.crumb:hover { text-decoration: none; border-bottom-style: solid; }
.crumbs .crumb-here { font-size: var(--t-sm); color: var(--faint); }
.crumbs .crumb-sep { color: var(--faint); font-size: var(--t-sm); }

/* Крестик окна: единственный выход, который виден всегда. */
.sheet { position: relative; }
.sheet-x {
  position: absolute; top: var(--s3); right: var(--s3);
  background: none; border: 0; color: var(--faint);
  font-size: 22px; line-height: 1; cursor: pointer; padding: var(--s1) var(--s2);
  border-radius: var(--r-sm);
}
.sheet-x:hover { color: var(--text); background: var(--panel-2); }

/* ── Выбиратель ────────────────────────────────────────────────────────────
   Строка списка должна читаться как карточка товара, а не как строка кода:
   фотография, название крупно, артикул и числа рядом. */
.pick-q { width: 100%; margin-bottom: var(--s3); font-size: var(--t-md); }
.pick-list { max-height: 52vh; overflow-y: auto; }
.pick-row {
  display: flex; align-items: center; gap: var(--s3); cursor: pointer;
  padding: var(--s2) var(--s3); border-radius: var(--r-sm);
  border: 1px solid transparent;
}
.pick-row:hover { background: var(--panel-2); border-color: var(--line); }
.pick-row.on { background: var(--accent-soft); border-color: var(--accent); }
.pick-row img, .pick-row .pick-nophoto {
  width: 38px; height: 50px; object-fit: cover; border-radius: var(--r-sm);
  background: var(--panel-2); flex: 0 0 auto;
}
.pick-row .pick-main { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.pick-row .pick-main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-row .pick-cells { display: flex; gap: var(--s4); font-size: var(--t-sm); white-space: nowrap; }
.pick-field { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.pick-field input { flex: 1; min-width: 160px; }
.pick-label { flex-basis: 100%; font-size: var(--t-sm); color: var(--dim); }
.pick-label.bad { color: var(--amber); }
/* Выбранный объект показывается объектом, а не номером: фотография, название,
   артикул и крестик «сменить». */
.pick-chosen { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; flex: 1; }
.pick-card {
  display: flex; align-items: center; gap: var(--s2);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--s1) var(--s2); max-width: 100%;
}
.pick-card img { width: 30px; height: 40px; object-fit: cover; border-radius: 4px; }
.pick-card-main { display: flex; flex-direction: column; min-width: 0; }
.pick-card-main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-x {
  background: none; border: 0; color: var(--faint); cursor: pointer;
  font-size: 18px; line-height: 1; padding: 0 2px;
}
.pick-x:hover { color: var(--text); }
.pick-why { flex-basis: 100%; font-size: var(--t-sm); color: var(--faint); }

/* ── Подвал кабинета, кликабельный логотип и строка согласия ────────────── */
.app-foot {
  border-top: 1px solid var(--line); margin-top: var(--s6);
  padding: var(--s4) var(--s5) var(--s6); color: var(--faint);
  font-size: var(--t-sm); text-align: center;
}
.app-foot a { color: var(--dim); text-decoration: none; }
.app-foot a:hover { color: var(--text); }
a.brand { text-decoration: none; color: inherit; }
.agree-row {
  display: flex; gap: var(--s3); align-items: flex-start; margin-top: var(--s3);
  font-size: var(--t-md); line-height: 1.45; cursor: pointer;
}
.agree-row input {
  margin-top: 2px; width: 17px; height: 17px; flex: none;
  accent-color: var(--accent); padding: 0;
}

/* ── Левое меню ────────────────────────────────────────────────────────────
   Было: два десятка вкладок в один ряд, который переносился на три строки и
   съедал верх экрана. Стало: семь разделов слева, раздел раскрывается в
   список своих страниц. Открытый раздел и выбранная страница видны сразу —
   иначе продавец не понимает, где он.

   На узком экране (ноутбук в дороге, планшет) меню уезжает за левый край и
   вызывается кнопкой. Оно НЕ сжимает содержимое и не ломает таблицы: полоса
   в 244 пикселя на экране в 1024 отняла бы у таблицы рекламы четверть. */
.shell { display: flex; align-items: flex-start; }
/* Содержимое занимает всё, что осталось. `min-width: 0` обязателен:
   без него широкая таблица рекламы распирает колонку флексбокса и
   выталкивает меню за край экрана. */
.shell > main { flex: 1 1 auto; min-width: 0; }

.side {
  flex: 0 0 var(--side-w); width: var(--side-w);
  position: sticky; top: var(--top-h, 54px);
  height: calc(100vh - var(--top-h, 54px));
  overflow-y: auto; overflow-x: hidden;
  background: var(--rail); border-right: 1px solid var(--line);
  padding: var(--s3) var(--s2) var(--s5);
}
.side-sec { margin-bottom: var(--s1); }
.sec-head {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  background: none; border: 0; color: var(--faint); font: inherit;
  font-size: var(--t-xs); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer; text-align: left;
  padding: var(--s2) var(--s3); border-radius: var(--r-sm);
}
.sec-head:hover { color: var(--text); background: var(--panel-2); }
.sec-head .caret {
  margin-left: auto; color: var(--faint); font-size: 10px;
  transition: transform .15s ease; letter-spacing: 0;
}
.side-sec.open > .sec-head .caret { transform: rotate(90deg); }
.side-sec.here > .sec-head { color: var(--amber); }
.side-pages { display: none; margin: 2px 0 var(--s2); }
.side-sec.open > .side-pages { display: block; }
.side-page {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  background: none; border: 1px solid transparent;
  color: var(--dim); font: inherit; cursor: pointer; text-align: left;
  padding: 7px var(--s3); border-radius: var(--r-sm);
}
.side-page:hover { background: var(--panel-2); color: var(--text); }
/* Значок пункта. Рисуется цветом строки, поэтому у выбранной страницы
   становится янтарным сам, без отдельного правила на каждый значок. */
.side-page svg { width: 17px; height: 17px; flex: none; opacity: .8; }
.side-page:hover svg { opacity: 1; }
.side-page.on svg { color: var(--amber); opacity: 1; }
/* Название пункта занимает всё, что осталось, — метка непрочитанного
   прижимается к правому краю и не пляшет по длине названия. */
.side-page .chip { margin-left: auto; }
/* Открытая страница — карточка, а не залитая синим полоса: заливка во всю
   ширину меню перетягивала внимание с содержимого экрана на само меню. */
.side-page.on {
  background: var(--panel); border-color: var(--line);
  color: var(--text); font-weight: 600; box-shadow: var(--shadow);
}
.side-foot { margin-top: var(--s4); padding: var(--s3); border-top: 1px solid var(--line); }
.side-foot .lbl { color: var(--faint); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: .09em; font-weight: 600; margin-bottom: var(--s2); }

/* Переключатель темы: три положения, выбранное видно. */
.theme-pick { display: flex; gap: 2px; flex-wrap: wrap; }
.theme-pick button {
  background: none; border: 1px solid var(--line-strong); color: var(--dim);
  font: inherit; font-size: var(--t-sm); cursor: pointer;
  padding: var(--s1) var(--s2); border-radius: var(--r-sm);
}
.theme-pick button:hover { color: var(--text); }
.theme-pick button.on {
  background: var(--panel); border-color: var(--accent); color: var(--text); font-weight: 600;
}

/* Кнопка вызова меню. На широком экране её нет: меню и так на месте. */
.burger {
  display: none; background: none; border: 1px solid var(--line-strong);
  color: var(--text); border-radius: var(--r-sm); cursor: pointer;
  padding: var(--s2) var(--s3); font: inherit; line-height: 1;
}
.side-veil { display: none; }

@media (max-width: 1080px) {
  .burger { display: inline-block; }
  .side {
    position: fixed; left: 0; top: 0; height: 100vh; z-index: 45;
    transform: translateX(-100%); transition: transform .18s ease;
    box-shadow: var(--shadow-lg); padding-top: var(--s4);
  }
  body.side-on .side { transform: none; }
  body.side-on .side-veil {
    display: block; position: fixed; inset: 0; z-index: 44;
    background: var(--veil);
  }
  main { padding: var(--s4); }
}

/* ── Разделительная полоса страницы ────────────────────────────────────── */
.page-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s4); flex-wrap: wrap; }
.page-head h1 { margin: 0; font-size: var(--t-xl); font-weight: 600; letter-spacing: -.02em; }
.page-head .where { color: var(--faint); font-size: var(--t-sm); }

/* ── Телефон ───────────────────────────────────────────────────────────────
   Продавец проверяет ставки с телефона — это обычный день, а не исключение.
   Цель нажатия не меньше 40 пикселей, поля не мельче 16 — иначе iOS
   приближает страницу при вводе и человек теряет из виду форму. */
@media (max-width: 760px) {
  /* Шапка — один ряд, а не столбик. На 390 она разваливалась на три строки и
     съедала 130 пикселей до первого слова по делу, а самой заметной кнопкой
     первого экрана оказывалась «Выйти». */
  .bar { gap: var(--s2); padding: var(--s2) var(--s3); flex-wrap: nowrap; }
  .bar .brand { font-size: var(--t-md); min-width: 0; }
  /* Кто вошёл — не для шапки телефона: ответ на «кто я» нужен раз в жизни и
     лежит в «Профиле», а ряд отнимает у каждого экрана. */
  .bar #who { display: none; }
  .bar button.act { padding: var(--s2) var(--s3); font-size: var(--t-sm); }

  main { padding: var(--s3); }
  th, td { padding: var(--s2); }
  .modal { padding: var(--s2); }
  .sheet { padding: var(--s4); }
  .panel { padding: var(--s3); }
  .tiles { grid-template-columns: 1fr 1fr; }
  .tile .val { font-size: var(--t-xl); }
  button.act, .act { min-height: 40px; }
  input, select, textarea { font-size: var(--t-lg); }
  .win-foot button.act { flex: 1; justify-content: center; }
}
/* ── Таблица на телефоне: строка становится карточкой ──────────────────────
   Срабатывает только там, где у клеток есть `data-col` с названием колонки:
   подпись берётся из него. Без атрибута таблица остаётся таблицей и
   прокручивается вбок — стопка чисел без имён была бы хуже прокрутки.

   Разметку менять больше нечем: всё остальное — вид. */
@media (max-width: 760px) {
  /* Карточкам окно прокрутки ни к чему: они и так идут в высоту страницы. */
  .scroll:has(td[data-col]) { max-height: none; overflow: visible; }

  table:has(td[data-col]),
  table:has(td[data-col]) tbody,
  table:has(td[data-col]) tfoot,
  table:has(td[data-col]) tr,
  table:has(td[data-col]) td { display: block; width: auto; }

  /* Шапка не нужна: её слова переехали к своим числам. */
  table:has(td[data-col]) thead { display: none; }

  table:has(td[data-col]) tr {
    background: var(--panel); border: 1px solid var(--line);
    border-radius: var(--r-md); padding: var(--s3) var(--s4);
    margin-bottom: var(--s3);
    /* Колонка, а не блок: в блочном потоке нет `order`, и клетку-заголовок
       нечем поднять наверх. На «Товарах» она шестая по счёту, и карточка
       открывалась отметкой и тремя видами кампаний вместо имени товара. */
    display: flex; flex-direction: column;
  }
  /* Имя объекта — первым, каким бы по счёту оно ни было в строке. Порядок
     колонок выбран для широкого экрана, где глаз идёт слева направо; в
     карточке порядок вертикальный, и первым обязано читаться имя. */
  table:has(td[data-col]) td[data-title] { order: -1; }
  table:has(td[data-col]) tbody tr:hover td { background: none; }

  table:has(td[data-col]) td {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--s2) var(--s4); padding: var(--s1) 0; border: 0;
    white-space: normal;                 /* длинному тексту дать перенос */
    /* Клетка из нескольких кусков — метка, число и пояснение — обязана
       переноситься. Без этого она уезжает за правый край карточки, и
       обходить это приходилось обёрткой на каждом экране отдельно. */
    flex-wrap: wrap;
  }
  /* Подпись слева — из названия колонки. Число справа: так их можно
     сравнивать глазом между карточками, не читая подписи заново. */
  table:has(td[data-col]) td[data-col]::before {
    content: attr(data-col);
    flex: none; color: var(--faint); font-size: var(--t-sm); font-weight: 500;
  }
  /* Заголовок карточки — имя объекта. Берётся из клетки с `data-title`:
     угадать её из CSS нельзя, на «Товарах» это пятая колонка, на «Ценах» и
     «Рекламе» первая. Разметка ставит атрибут на ту колонку, которая
     называет объект.

     Пока атрибута нет, заголовком служит первая клетка — но только если она
     не отметка для массового действия. Иначе заголовком становился пустой
     квадратик, а название товара уходило пятой строкой наравне с числами;
     на широком экране это не мешает (глаз идёт по строке), в карточке
     порядок вертикальный, и первым обязано читаться имя. */
  table:has(td[data-col]) td[data-title],
  table:has(td[data-col]) tr:not(:has(td:first-child input)) > td:first-child {
    display: block; font-size: var(--t-lg); font-weight: 600;
    padding-bottom: var(--s2); margin-bottom: var(--s2);
    border-bottom: 1px solid var(--line-2);
  }
  table:has(td[data-col]) td[data-title]::before,
  table:has(td[data-col]) tr:not(:has(td:first-child input)) > td:first-child::before {
    content: none;
  }
  /* Если заголовок назначен атрибутом, первая клетка ведёт себя как обычная
     строка — со своей подписью и своим местом по порядку. */
  table:has(td[data-col]) tr:has(td[data-title]) > td:first-child {
    display: flex; font-size: var(--t-md); font-weight: 400;
    padding: var(--s1) 0; margin-bottom: 0; border-bottom: 0;
  }
  table:has(td[data-col]) tr:has(td[data-title]) > td:first-child::before {
    content: attr(data-col);
  }
  /* Клетка отметки: квадратик прижимается вправо, как остальные значения. */
  table:has(td[data-col]) td:has(input[type="checkbox"]) { justify-content: space-between; }
  /* Пустая клетка в карточке — это строка «Что-то: » ни о чём. Убираем.
     Клетку с прочерком НЕ трогаем: «нет данных» — это ответ. */
  table:has(td[data-col]) td:empty { display: none; }

  /* Итог — такая же карточка, но видно, что итог. */
  table:has(td[data-col]) tfoot tr {
    background: var(--panel-2); border-color: var(--line);
  }
  table:has(td[data-col]) tfoot td { font-weight: 600; }

  /* Полоса доли занимала правый край строки; в карточке она встаёт рядом
     со своим числом и растягивается на свободное место. */
  table:has(td[data-col]) .meter { justify-content: flex-end; }
  table:has(td[data-col]) .meter .track { width: 72px; }
  /* Спарклайну в карточке тесно незачем: пусть занимает ширину клетки. */
  table:has(td[data-col]) .spark { width: 120px; }
}

/* Один столбец — только на совсем узких. На 390 плитки идут по две: в один
   столбец восемь плиток дают 708 пикселей, и до первой строки данных
   продавец листает два экрана. Замер на живом кабинете, не на глаз. */
@media (max-width: 360px) {
  .tiles { grid-template-columns: 1fr; }
}

/* Знак «Одного кабинета»: столбики берут цвет текста, единица всегда коралловая
   (brand/mark.svg). На тёмном фоне сплошной столбик светит белым и спорит с
   единицей — в brand/icon-dark.svg он приглушён до 75 %, повторяем это здесь.
   На светлой теме столбик остаётся сплошным, как в самом mark.svg. */
.mark rect:nth-of-type(2) { opacity: .75; }
html[data-theme="light"] .mark rect:nth-of-type(2) { opacity: 1; }
