/* ============================================================================
   SmartCatalog UI-Kit · единый источник стилей
   Подключается ГЛОБАЛЬНО один раз (base.html) до остальных стилей страниц.
   Все экраны верстаются ТОЛЬКО этими классами. Правило: нет своего класса —
   добавь его сюда, не пиши инлайн-стиль в шаблоне.
   Значения — канон Command Center (см. DESIGN_SYSTEM.md в корне проекта).
   Версия 2.0 · единственный источник токенов и компонентов.
   ============================================================================ */

/* ---- 1. ТОКЕНЫ ------------------------------------------------------------ */
/* Inter — рабочий шрифт админки (решение дизайн-директора 14.10.2026,
   направление Premium Compact). До этого админка и витрина делили Nunito
   (решение владельца 06.09.2026) — у витрины он и остался: там дружелюбная
   округлость к месту, а в плотной рабочей таблице она мешает числам.
   Файлы свои, как и у Nunito: сборка витрины кладёт шрифты под хэш-именами,
   ссылаться на них из админки нельзя. Диапазоны — те же четыре подмножества,
   браузер качает только нужные. Файлы Nunito в каталоге оставлены: вернуть
   прежний шрифт — одна строка в токене --font. */
@font-face{font-family:"Inter";font-style:normal;font-weight:400 700;font-display:swap;src:url("/static/fonts/inter-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:"Inter";font-style:normal;font-weight:400 700;font-display:swap;src:url("/static/fonts/inter-cyrillic.woff2") format("woff2");unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:"Inter";font-style:normal;font-weight:400 700;font-display:swap;src:url("/static/fonts/inter-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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:"Inter";font-style:normal;font-weight:400 700;font-display:swap;src:url("/static/fonts/inter-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}
:root{
  /* Браузер должен знать, какая тема на странице: от этого зависят СВОИ
     элементы — галочки, переключатели, календарь в поле даты, полосы
     прокрутки. Без этого в тёмной теме галочки оставались белыми коробками
     и читались как уже отмеченные (независимое ревью, фаза F). */
  color-scheme:dark;
  /* поверхности · канон Command Center: мягкий тёмный #1A1D24, карточки —
     светлый overlay поверх фона, а не отдельный чёрный. Ступени сжатые:
     разница между уровнями читается краем глаза, но не режет. */
  --bg:#1a1d24;
  --surface:#21242b;
  --surface-2:#262a33;
  --surface-3:#2c313b;
  --border:#2e323b;
  --border-strong:#3e434f;

  /* Текст: три ступени, и каждая обязана читаться НА ПОВЕРХНОСТИ, а не на
     фоне страницы. Прошлая правка считала контраст на --bg и пропустила, что
     подписи и заголовки колонок лежат на карточках и полях, где фон светлее:
     --faint давал там 3.07:1. Ступени разведены сильнее обычного — нижняя
     ровно на пороге 4.5, средняя заметно выше, — иначе при подъёме нижней до
     нормы иерархия схлопывается в две. */
  --text:#e0e2e7;   /* 11.1:1 */
  --muted:#a8acb4;  /*  6.3:1 */
  --faint:#8d929f;  /*  4.6:1 */

  /* акцент. ВАЖНО: #a78bfa — цвет ТЕКСТА и подсветки, на нём белый нечитаем
     (2.0:1). Разделение обязательное: покрасить кнопку в --accent значит
     сломать контраст, заливка primary — отдельный токен --accent-fill.
     UI 2.0 (владелец, 19.09.2026: «современная светлая SaaS-админка, без
     градиентов»): заливка СПЛОШНАЯ. Градиент CC давал кнопку двух оттенков
     и 3.66:1 на светлом краю; сплошной #5b47d6 — 6.9:1 по всей ширине. */
  --accent:#a78bfa;
  --accent-weak:rgba(167,139,250,.13);
  --accent-line:rgba(167,139,250,.28);
  --accent-fill:#5b47d6;
  --accent-fill-solid:#6d5ad4;
  --accent-glow:0 4px 20px rgba(102,126,234,.35);
  --on-accent:#ffffff;
  /* Текст на СПЛОШНОЙ семантической заливке (тост). Тёмный, потому что
     заливка светлая в обеих темах — иначе белое на зелёном даёт 1.8:1. */
  --on-solid:#0b1a12;

  /* семантика статусов — ОТДЕЛЬНО от акцента · канон Command Center.
     Пара weak+line: тональная заливка плюс тональная граница, как у чипов CC. */
  --green:#34d399;  --green-weak:rgba(52,211,153,.10);  --green-line:rgba(52,211,153,.24);
  --amber:#fbbf24;  --amber-weak:rgba(251,191,36,.10);  --amber-line:rgba(251,191,36,.24);
  --red:#f87171;    --red-weak:rgba(248,113,113,.10);   --red-line:rgba(248,113,113,.24);
  --blue:#60a5fa;   --blue-weak:rgba(96,165,250,.10);   --blue-line:rgba(96,165,250,.24);

  /* Боковое меню — тёмная панель В ОБЕИХ темах (Premium Compact, этап 13.3,
     концепция D утверждена владельцем). Поэтому у него свои токены, а не
     общий --surface — иначе в светлой теме меню белое на белом и каркас
     экрана теряется. В тёмной теме панель на ступень глубже страницы — тот же приём,
     что у карточек, только в другую сторону. */
  --nav:#15161d;
  --nav-2:#21232d;
  --nav-text:#f3f4f8;
  --nav-muted:#a7abb8;
  --nav-line:#2b2d37;

  /* Радиусы · UI 2.0: 8 контейнеры, 6 контролы. Было 14/10/7 от Command
     Center — для плотного рабочего экрана мягко: карточки выглядели
     надутыми, а три близких ступени глаз не различал (разбор OpenAI и
     Gemini, 19.09.2026). */
  --r:8px;         /* карточки, модалки          */
  --r-md:8px;      /* плитки, вложенные блоки    */
  --r-sm:6px;      /* контролы, полоски, метки   */
  --r-pill:999px;  /* пилюли: бейдж, статус, роль */

  /* шкала отступов (используем ТОЛЬКО эти значения) */
  --s1:4px; --s2:6px; --s3:8px; --s4:10px; --s5:12px; --s6:14px;
  --s7:16px; --s8:20px; --s9:24px; --s10:32px;

  /* ширина рабочей области (этап 13.4, владелец: «на большом мониторе
     пусто слева и справа»).

     Потолок поднят с 1600 до 2200: на 2560 рабочая область занимала две
     трети экрана, треть уходила в поля. Ноутбук 14 дюймов правки не замечает
     вовсе — там доступной ширины меньше прежнего потолка, раскладка ровно та,
     что принял дизайн-директор.

     Почему потолок всё-таки есть. Сначала он был снят совсем, и на 2560 вышло
     хуже прежнего: таблица заказов растянулась на весь экран и стала
     разреженной — число, плашка и значок разъехались по углам. 2200 — ширина,
     на которой таблица ещё плотная; дальше пустота переезжает внутрь строки,
     а это не выигрыш, а другой способ проиграть.

     Поля остались прежними (--s9), и это сознательно: растущие поля я
     сначала сделал и тут же откатил — на 1280 и 1440 они отнимали у работы
     16-24px, то есть портили ровно те ширины, которые меняться не должны.
     При потолке запас и так уходит в наружные отступы, растить нечего.
     Верхняя полоса берёт тот же потолок — иначе бренд слева и «Выйти»
     справа разъедутся с краями карточек.

     Расползается не всё: формы держит .field (520px), текст — ch-потолки,
     модалки — свои ширины. Шире становятся таблицы и аналитика, то есть
     ровно то, ради чего правка. */
  --wrap-max:min(100%, 2200px);

  /* типографика */
  /* Inter — рабочий шрифт админки (дизайн-директор, 14.10.2026). Витрина
     осталась на Nunito: у покупателя другой разговор, чем у менеджера.
     Вернуть прежний шрифт — поменять первое имя в этой строке. */
  --font:"Inter",-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --fs-label:11.5px;  /* заголовок колонки, подпись капсом */
  --fs-xs:12px;       /* мета, сноска                      */
  --fs-sm:13px;       /* вторичный текст, контролы         */
  --fs-body:14px;     /* основной текст                    */
  --fs-md:16px;       /* акцентная строка, h3              */
  --fs-h2:18px;       /* заголовок блока                   */
  --fs-h1:20px;       /* заголовок страницы                */
  --fs-num:24px;      /* крупный показатель                */
  /* ОДНА высота контролов (UI 2.0). Поле было ~42px, кнопка ~34px, селект
     фильтра ~40px — в одном ряду тулбара края не сходились. Кнопка, поле
     и селект одного ряда — одна высота; компактная — для строк таблицы.
     На телефоне — выше (блок порога в конце файла). */
  --ctl-h:36px;
  --ctl-h-sm:30px;
  /* Фон поля ввода отдельным токеном: в светлой теме поле белое на белой
     карточке и держится рамкой, в тёмной — на ступень светлее карточки. */
  --field-bg:#262a33;
  --shadow-modal:0 26px 70px rgba(0,0,0,.55);

  /* Слои по ролям, а не по случайно подобранным числам. Было 1 · 2 · 20 · 40 ·
     41 · 100 · 200, где 40 и 41 — соседи по совпадению. Пока слоёв мало, это
     безобидно; проблема появляется в момент, когда одно перекрыло другое, и
     подобрать число можно только опытом. */
  --z-raise:2;      /* подъём внутри блока: крестик над содержимым */
  --z-sticky:20;    /* залипающая шапка, нижняя панель */
  --z-overlay:40;   /* затемнение под шторкой */
  --z-modal:50;     /* модалка, шторка */
  --z-popover:100;  /* меню, подсказка, выпадающий список */
  --z-toast:200;    /* уведомление поверх всего */
  /* Цвет внутри data-URI не берёт var(), поэтому стрелка селекта — сама
     переменная: у каждой темы свой url. Иначе она застывает в одном цвете. */
  --select-arrow:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a8acb4' stroke-width='2.5'><path d='M6 9l6 6 6-6'/></svg>");
  /* Затемнение поверх картинки (маска обрезки аватара). Токеном, а не
     сырой rgba: в тёмной теме нужна своя плотность. */
  --scrim:rgba(0,0,0,.72);
  --edge-shade:rgba(0,0,0,.55);   /* тень у обрезанного края таблицы */
  /* Подложка под фото товара: снимки поставщика сняты на белом, и в тёмной
     теме тоже должны лежать на белом — иначе у половины товаров рваный фон. */
  --photo-bg:#ffffff;
}
:root[data-theme="light"]{
  color-scheme:light;
  /* UI 2.0: нейтральные серые с лёгким холодным оттенком вместо
     фиолетово-серых CC — акцент один, фон его не повторяет. */
  --bg:#f5f6f8;
  --surface:#ffffff;
  --surface-2:#f6f7f9;
  --surface-3:#eceef2;
  --border:#e3e5ea;
  --border-strong:#cdd1d9;
  --field-bg:#ffffff;
  --text:#1b1d22;
  /* Отклонение от CC осознанное: у CC textSub #8e8e93 даёт 3.5:1 на белом.
     Ступени те же, что в тёмной: нижняя на пороге, средняя выше. */
  --muted:#575a62;  /* 6.3:1 */
  --faint:#6b6f78;  /* 4.6:1 */
  /* То же с акцентом: #a78bfa на белом — 2.2:1. Для светлой темы берём
     тёмный конец того же градиента. Заливка кнопки остаётся общей. */
  --accent:#5b47d6;
  --accent-weak:rgba(91,71,214,.08);
  --accent-line:rgba(91,71,214,.24);
  --accent-glow:0 4px 20px rgba(102,126,234,.22);
  --green:#117f39;  --green-weak:rgba(17,127,57,.09);  --green-line:rgba(17,127,57,.22);
  --amber:#906505;  --amber-weak:rgba(144,101,5,.10);  --amber-line:rgba(144,101,5,.22);
  --red:#d22222;    --red-weak:rgba(210,34,34,.08);    --red-line:rgba(210,34,34,.20);
  --blue:#2563eb;   --blue-weak:rgba(37,99,235,.08);   --blue-line:rgba(37,99,235,.20);
  --on-solid:#0b1a12;
  --shadow-modal:0 16px 48px rgba(20,30,50,.18);
  --select-arrow:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23575a62' stroke-width='2.5'><path d='M6 9l6 6 6-6'/></svg>");
  --scrim:rgba(0,0,0,.6);
  --edge-shade:rgba(15,23,42,.24);
  /* Подложка под фото товара: снимки поставщика сняты на белом, и в тёмной
     теме тоже должны лежать на белом — иначе у половины товаров рваный фон. */
  --photo-bg:#ffffff;
}

/* ---- 2. БАЗА + ЗАЩИТНАЯ СЕТКА --------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0}
body{background:var(--bg);color:var(--text);font-family:var(--font);font-size:var(--fs-body);line-height:1.45;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
a{color:inherit;text-decoration:none}
/* Иконка в потоке строки садится на базовую линию и добавляет 3-4px снизу —
   в кнопках это выглядит как съехавший на пиксель текст. */
svg{display:block}
button{cursor:pointer}
/* КРИТИЧНО: любой контрол наследует шрифт — иначе textarea/ввод падают в моноширинный */
input,textarea,select,button{font-family:inherit;font-size:inherit;color:inherit}
/* КРИТИЧНО: hidden обязан побеждать display компонента. Иначе .btn с
   display:inline-flex перебивает браузерное [hidden]{display:none}, и кнопка,
   помеченная скрытой, всё равно видна — поймано на «Выбрать все по фильтру». */
[hidden]{display:none!important}
/* Кольцо только для клавиатуры: :focus подсвечивал бы и клик мышью.
   Гало нужно, чтобы кольцо читалось и на поверхности поля, и на фоне. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
  box-shadow:0 0 0 4px var(--accent-weak)}
img{max-width:100%}

/* ---- 3. ТИПОГРАФИКА ------------------------------------------------------- */
h1,h2,h3{margin:0}
h1,.h1{font-size:var(--fs-h1);font-weight:700;text-wrap:balance}
h2,.h2{font-size:var(--fs-h2);font-weight:700;text-wrap:balance}
h3,.h3{font-size:var(--fs-md);font-weight:600}
/* Заголовок в потоке страницы отбивается сам; внутри шапок и карточек
   отбивку задаёт контейнер через gap, поэтому у классов её нет. */
h2{margin:var(--s9) 0 var(--s5)}
h3{margin:0 0 var(--s1)}
.num{font-variant-numeric:tabular-nums}
.num--big{font-size:var(--fs-num);font-weight:700;line-height:1.1}
.sub{color:var(--muted);font-size:var(--fs-sm)}
.label{font-size:var(--fs-xs);color:var(--muted);font-weight:600}

/* ---- 4. КНОПКИ ------------------------------------------------------------ */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  background:var(--accent-fill);border:1px solid transparent;color:var(--on-accent);
  min-height:var(--ctl-h);padding:var(--s2) var(--s6);border-radius:var(--r-sm);font-weight:600;font-size:var(--fs-sm);
  cursor:pointer;transition:filter .12s,border-color .12s,background .12s;white-space:nowrap;
  background-size:200% 100%;background-position:0 0}
.btn:hover{filter:brightness(1.07)}
.btn:active{filter:brightness(.95)}
.btn--secondary{background:var(--field-bg);border-color:var(--border);color:var(--text)}
.btn--secondary:hover{border-color:var(--border-strong);filter:none}
.btn--ghost{background:transparent;border-color:transparent;color:var(--muted)}
.btn--ghost:hover{color:var(--text);background:var(--surface-2);filter:none}
.btn--danger{background:var(--red-weak);border-color:var(--red-line);color:var(--red)}
.btn--danger:hover{border-color:var(--red);filter:none}
/* Вторичное действие, которому нужно чуть больше веса, чем у обычного
   secondary, но не заливка primary — «Загрузить прайс», «Фильтры» (каталог,
   макет владельца 06.09.2026). Тональная, как .btn--danger: заметная
   обводка акцентом, не крик. */
.btn--accent{background:var(--accent-weak);border-color:var(--accent-line);color:var(--accent)}
.btn--accent:hover{border-color:var(--accent);filter:none}
.btn--accent.is-loading::after{color:var(--accent)}
.btn--sm{min-height:var(--ctl-h-sm);padding:var(--s1) var(--s5);font-size:var(--fs-xs)}
/* Шеврон кнопки, у которой раскрывается меню («Ещё ▾»). */
.btn__chev{font-size:var(--fs-xs);opacity:.6}
.btn[disabled],.btn:disabled{background:var(--surface-3);background-image:none;
  border-color:var(--border);color:var(--faint);cursor:not-allowed;filter:none}
/* иконочная кнопка (квадрат) */
.icon-btn{display:grid;place-items:center;width:var(--ctl-h);height:var(--ctl-h);flex-shrink:0;
  background:var(--field-bg);border:1px solid var(--border);color:var(--muted);
  border-radius:var(--r-sm);cursor:pointer;transition:.12s}
.icon-btn:hover{border-color:var(--border-strong);color:var(--text)}
.icon-btn--active{background:var(--accent-weak);border-color:var(--accent);color:var(--accent)}
/* Тональные состояния значка: «включено опасное» и «предупреждение».
   Раньше жили своими классами .micon.on / .micon.hid в styles.css — то же
   самое под другими именами (находка UX-056). */
.icon-btn--danger{background:var(--red-weak);border-color:var(--red-line);color:var(--red)}
.icon-btn--warn{background:var(--amber-weak);border-color:var(--amber-line);color:var(--amber)}
/* Недоступно (статус с заказами удалить нельзя): видно, но не зовёт нажать. */
.icon-btn:disabled{opacity:.35;cursor:not-allowed}

/* Кнопка в работе. Разбор прайса идёт около 30 секунд, и всё это время
   человек, нажавший кнопку, не должен гадать, услышали его или нет.
   Ширина не меняется: подпись остаётся на месте под прозрачностью,
   иначе ряд кнопок дёргается в момент нажатия. */
.btn.is-loading{position:relative;color:transparent;pointer-events:none}
.btn.is-loading::after{content:"";position:absolute;width:14px;height:14px;
  border:2px solid currentColor;border-top-color:transparent;border-radius:50%;
  color:var(--on-accent);animation:spin .6s linear infinite}
.btn--secondary.is-loading::after,.btn--ghost.is-loading::after{color:var(--text)}
.btn--danger.is-loading::after{color:var(--red)}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---- 5. ФОРМЫ ------------------------------------------------------------- */
.field{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s6);max-width:520px}
.field:last-child{margin-bottom:0}
/* Поля в ряду: ряд центрирует детей по вертикали, а нижний отступ есть у
   всех полей, кроме последнего, — и последнее уезжало вниз ровно на половину
   отступа (карточка клиента, точки: «Адрес» и «Контактное лицо»). В ряду
   отступ снизу не нужен — расстояние между рядами задаёт сам ряд. */
.row > .field{margin-bottom:0}
.field label,.field__label{font-size:var(--fs-sm);font-weight:600;color:var(--text)}
.field-row{display:flex;gap:var(--s4)}
.field-row > .field{flex:1;margin-bottom:0}
.field-row + *{margin-top:var(--s6)}
/* ЕДИНЫЙ стиль ВСЕХ вводов — input, textarea, select одинаковы */
.input,.textarea,.select,
.field input,.field textarea,.field select{
  width:100%;background:var(--field-bg);border:1px solid var(--border-strong);color:var(--text);
  min-height:var(--ctl-h);padding:var(--s2) var(--s5);border-radius:var(--r-sm);font-size:var(--fs-body);
  font-family:inherit;line-height:1.4;transition:border-color .12s}
.input:focus,.textarea:focus,.select:focus,
.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}
.input::placeholder,.textarea::placeholder{color:var(--faint)}
.textarea,.field textarea{min-height:80px;resize:vertical}
.select,.field select{appearance:none;-webkit-appearance:none;padding-right:32px;cursor:pointer;
  background-image:var(--select-arrow);background-repeat:no-repeat;background-position:right 12px center}
.field__hint{color:var(--faint);font-size:var(--fs-xs);line-height:1.5;margin-top:calc(-1 * var(--s1))}
/* code — выделенная часть подсказки, и лежит она на более светлой
   поверхности: наследовать --faint нельзя, там 4.2:1. */
.field__hint code{background:var(--surface-3);color:var(--muted);
  padding:1px 6px;border-radius:var(--r-sm);font-size:var(--fs-label)}
/* метка «изменено вручную» у поля */
.field__badge{font-size:var(--fs-xs);font-weight:600;color:var(--amber)}
/* Ошибка поля. Красная рамка без текста говорит «что-то не так» и не
   говорит что именно — поэтому рамка и объяснение всегда вместе. */
.field--error .input,.field--error .select,.field--error .textarea,
.field--error input,.field--error select,.field--error textarea{border-color:var(--red)}
.field__error{display:flex;align-items:flex-start;gap:var(--s2);
  color:var(--red);font-size:var(--fs-xs);line-height:1.5}
.field__error::before{content:"!";flex-shrink:0;display:grid;place-items:center;
  width:14px;height:14px;border-radius:50%;background:var(--red);color:var(--on-solid);
  font-size:var(--fs-label);font-weight:700;line-height:1}

/* переключатель (toggle) */
.switch{width:42px;height:24px;border-radius:var(--r-pill);background:var(--surface-3);
  border:1px solid var(--border);position:relative;flex-shrink:0;cursor:pointer;transition:.15s}
.switch::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;
  background:var(--muted);transition:.15s}
.switch[aria-checked="true"],.switch.is-on,
.switch-wrap input:checked + .switch{background:var(--accent-fill-solid);border-color:var(--accent-fill-solid)}
.switch[aria-checked="true"]::after,.switch.is-on::after,
.switch-wrap input:checked + .switch::after{left:20px;background:#fff}
/* Обёртка для настоящего чекбокса: форма отправляет значение, а видимый
   тумблер рисуется через :checked. Кнопкой с aria-checked тумблер тоже
   работает — оба способа живут в одном компоненте, а не в двух. */
.switch-wrap{display:inline-flex;align-items:center;gap:var(--s3);cursor:pointer;flex-shrink:0;position:relative}
.switch-wrap input{position:absolute;opacity:0;width:0;height:0}
/* Подпись рядом с тумблером, в той же строке — «Только с фото» (каталог).
   Отдельный элемент, не .toggle-row__title: тот несёт свой блок с описанием
   в две строки, здесь подписи ровно одна и она компактная. */
.switch-wrap__label{font-size:var(--fs-sm);color:var(--text)}
/* строка тумблера */
.toggle-row{display:flex;align-items:center;justify-content:space-between;gap:var(--s5);
  padding:var(--s5) var(--s6);background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--r-sm);margin-bottom:var(--s3)}
.toggle-row__title{font-weight:600;font-size:var(--fs-sm)}
.toggle-row__desc{color:var(--faint);font-size:var(--fs-xs);margin-top:2px}

/* сегментированный контрол */
.seg{display:inline-flex;background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--r-sm);overflow:hidden}
.seg button{padding:var(--s3) var(--s5);background:0;border:0;color:var(--muted);
  font-weight:600;font-size:var(--fs-sm);cursor:pointer}
.seg button.is-on{background:var(--accent-fill);color:var(--on-accent)}
.seg--wrap{flex-wrap:wrap}

/* ---- 6. КАРТОЧКИ / КОНТЕЙНЕРЫ -------------------------------------------- */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden}
.card--pad{padding:var(--s8)}
/* Карточка с выпадающим списком внутри: overflow:hidden резал список до двух
   строк, а список обязан лечь поверх того, что ниже. */
.card--open{overflow:visible}
.card + .card{margin-top:var(--s7)}

/* Заглушка изображения. Форму и размер задаёт компонент-хозяин (.bava, .th,
   .pimg-now), здесь — только «фото нет». Отдельное имя, а не .empty: тот
   класс — пустое состояние страницы с padding 44px, и на боксе 56px он
   раздувал аватар в овал, а подпись выносил наружу. */
.imgph{display:grid;place-items:center;padding:var(--s1);
  background:var(--surface-2);border:1px dashed var(--border-strong);
  color:var(--faint);font-size:var(--fs-label);line-height:1.2;text-align:center;
  overflow:hidden;overflow-wrap:anywhere}

/* ---- 7. ТАБЛИЦА ----------------------------------------------------------- */
.table-wrap{width:100%;overflow-x:auto;
  /* Таблица шире окна скроллится внутри — признак этого (UX-062): тень у
     правого края, пока справа есть что листать. Заплатка цвета карточки
     приклеена к концу содержимого (local) и накрывает тень, когда долистали
     до конца; сама тень приклеена к окну (scroll). Без JS и без наблюдателей. */
  background:linear-gradient(var(--surface),var(--surface)) right center/28px 100% no-repeat local,
    radial-gradient(farthest-side at 100% 50%,var(--edge-shade),transparent) right center/14px 100% no-repeat scroll}
.table{width:100%;border-collapse:collapse}
.table thead th{text-align:left;font-size:var(--fs-xs);background:var(--surface-2);
  color:var(--muted);font-weight:600;padding:var(--s3) var(--s5);border-bottom:1px solid var(--border);white-space:nowrap}
/* Плотность строки (этап 13.3, Premium Compact): по вертикали 6px вместо 8.
   Строка с одной подписью под значением держится в 48px, с двумя — в 56px;
   зона нажатия от этого не страдает — нажимается вся строка. */
.table tbody td{padding:var(--s2) var(--s5);border-bottom:1px solid var(--border);vertical-align:middle}
.table tbody tr:last-child td{border-bottom:0}
.table tbody tr.is-clickable{cursor:pointer}
/* Сортируемый заголовок (UX-042): ссылка тем же шрифтом, что заголовок;
   стрелка приглушена, у активной колонки — акцентом. */
/* Зона нажатия у заголовка — не высота строчки текста: 17px по вертикали
   мышью ловится с трудом, а пальцем не ловится вовсе (сквозная проверка,
   фаза F). Отступы съедают высоту ячейки, а не добавляют её: у th уже есть
   свои 6px, поэтому строка таблицы от этого не растёт. */
.th-sort{display:inline-flex;align-items:center;gap:var(--s1);color:inherit;text-decoration:none;
  white-space:nowrap;padding:var(--s2) 0;min-height:24px}
.th-sort:hover{color:var(--text)}
.th-sort__ic{opacity:.45;font-size:var(--fs-xs)}
.th-sort.is-active{color:var(--text)}
.th-sort.is-active .th-sort__ic{opacity:1;color:var(--accent)}
.table tbody tr.is-clickable:hover{background:var(--surface-2)}
/* Строка-ссылка (UX-043): имя в строке — настоящая ссылка или кнопка, её
   видит клавиатура; строка лишь расширяет зону клика (base.html, data-row-link).
   Выглядит как текст строки, при наведении на строку — акцентом. */
.row-link{appearance:none;background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;
  text-align:inherit;text-decoration:none;cursor:pointer}
.table tbody tr.is-clickable:hover .row-link,.row-link:hover{color:var(--accent)}
.table tbody tr.is-selected{background:var(--accent-weak)}
/* Служебные колонки — фиксированный минимум, а не подгонка браузером под
   содержимое ТЕКУЩЕЙ строки. Без порога смена фильтра меняла длину значения
   в одной колонке («12,50» ↔ «—» при переключении типа цены в каталоге,
   разная длина чисел при смене периода в «Топ товарах») — и вся таблица
   уезжала вбок вместе с ней, хотя по смыслу изменилась одна колонка
   (владелец, 05.09.2026). min-width — колонка не сжимается ниже порога, но
   растёт, если реальному значению тесно; фиксированная ширина служебной
   колонки — разрешённое исключение из шкалы отступов, как 44px у кнопок. */
/* Куда уходит лишняя ширина (этап 13.4). С потолком 1600px вопрос не стоял:
   лишней ширины не бывало. Когда рабочая область дотянулась до 2200px, таблица
   начала раздавать запас всем колонкам поровну — между «Сумма» и «Статус»
   получалась пустота в пол-экрана, и глаз шёл через неё от числа к плашке.
   width:1% — это «по содержимому»: браузер отдаёт такой колонке минимум, а
   min-width ниже него не пускает. Весь запас достаётся .col-wide — колонке с
   именем, адресом или названием, которой длина действительно нужна. */
.table .col-check{min-width:44px;width:1%}
.table .col-thumb{min-width:66px;width:1%}
.table .col-eye{min-width:62px;width:1%}
.table .col-num{min-width:84px;width:1%;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
/* Цена и сумма несут ещё и валюту — «12 345,67 BYN» не помещается в порог
   обычной числовой колонки; «—» вместо цены не должен её сужать. */
/* Колонка статуса витрины: плашка «Скрыт · нет цены 11-П» длиннее «На витрине»,
   и без минимума соседние колонки сдвигались при смене типа цены. */
.table .col-state{min-width:200px}
/* Колонка с именем или адресом (UI 2.0): на планшете таблица заказов ужимала
   клиента до слова в строке. С порогом она прокручивается вбок внутри
   .table-wrap, а имя остаётся читаемым. */
.table .col-wide{min-width:200px;width:100%}
.table .col-num.col-price{min-width:132px}
/* приглушённая (скрытая) строка */
.table tbody tr.is-dim .thumb{opacity:.4;filter:grayscale(1)}
.table tbody tr.is-dim td:not(.col-keep){opacity:.62}
/* Полоса состояния слева — тот же принцип, что у виджета: появляется только
   когда строка требует внимания (выключенная учётка, скрытый товар), а не
   на каждой строке подряд — иначе она перестаёт что-либо значить. */
.table tbody tr.is-flagged{box-shadow:inset 3px 0 0 var(--red)}

/* ---- 8. БЕЙДЖИ / СТАТУСЫ -------------------------------------------------- */
.badge{display:inline-flex;align-items:center;gap:var(--s2);padding:2px 9px;border-radius:var(--r-pill);
  white-space:nowrap;font-size:var(--fs-xs);font-weight:600;background:var(--surface-3);color:var(--muted);
  border:1px solid transparent}
.badge--accent{background:var(--accent-weak);color:var(--accent);border-color:var(--accent-line)}
.badge--green{background:var(--green-weak);color:var(--green);border-color:var(--green-line)}
.badge--amber{background:var(--amber-weak);color:var(--amber);border-color:var(--amber-line)}
.badge--red{background:var(--red-weak);color:var(--red);border-color:var(--red-line)}
.badge--blue{background:var(--blue-weak);color:var(--blue);border-color:var(--blue-line)}
.badge--muted{background:var(--surface-3);color:var(--muted)}
.badge--tiny{font-size:var(--fs-label);padding:1px var(--s3)}
/* Значок вместо слова (пометка «правлен»): SVG набора вместо глифа ✎. */
.badge--icon svg{width:12px;height:12px}
/* Точку рисует сам бейдж. Раньше её вставляли отдельным пустым <span class="d">
   в каждой разметке — тридцать штук, которые нечем было держать вместе. */
.badge--dot::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}
/* Без заливки: статус строкой, а не плашкой. Это второй облик того же
   компонента, а не третий компонент — раньше здесь жил отдельный .status,
   а в styles.css — ещё и .pill, полный двойник .badge (находка UX-053). */
.badge--plain{background:none;border-color:transparent;padding:0;font-size:var(--fs-sm);white-space:nowrap;color:inherit}
/* Точка берёт цвет из данных (--dot: цвет статуса заказа, который владелец
   задаёт сам), а без него — тон модификатора. */
.badge--plain::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--dot, currentColor);flex-shrink:0}
.badge--plain.badge--green{color:var(--green)}
.badge--plain.badge--red{color:var(--red)}
.badge--plain.badge--amber{color:var(--amber)}
.badge--plain.badge--blue{color:var(--blue)}
.badge--plain.badge--muted{color:var(--muted)}
.badge--plain.badge--accent{color:var(--accent)}

/* фильтр-чип */
.chip{display:inline-flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s5);
  background:var(--surface-2);border:1px solid var(--border);color:var(--text);
  border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:500;cursor:pointer}
.chip.is-on,.chip:has(input:checked){background:var(--accent-weak);border-color:var(--accent);color:var(--accent)}
/* Чип-переключатель прячет свой input: кликают по всей площади. */
.chip input{position:absolute;opacity:0;width:0;height:0}
.chip--off{opacity:.55;cursor:not-allowed}
/* Чип применённого фильтра: сам — ссылка «снять этот фильтр», крестик
   внутри — только значок. Кликабельна вся площадь, а не крошечный ✕. */
.chip--removable{background:var(--accent-weak);border-color:var(--accent-line);color:var(--accent);text-decoration:none}
.chip--removable:hover{border-color:var(--accent)}
.chip__x{font-size:var(--fs-xs);opacity:.7}
/* Ряд применённых фильтров — один на все списки (partials/active_filters.html). */
.active-filters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);margin:var(--s4) 0}
.active-filters__reset{margin-left:auto}
.active-filters__reset svg{width:14px;height:14px}
.chips{display:flex;flex-wrap:wrap;gap:var(--s3)}

/* Плитка-показатель. Раньше называлась .chip и делила имя с фильтр-чипом:
   последнее объявление побеждало, и статистика получала форму пилюли и
   курсор-руку, хотя она не кликабельна. */
.stats{display:flex;flex-wrap:wrap;gap:var(--s3)}
.stat{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-sm);
  padding:var(--s4) var(--s6);min-width:96px}
.stat__n{font-size:19px;font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}
.stat__l{font-size:var(--fs-label);color:var(--muted);margin-top:2px}
.stat--green .stat__n{color:var(--green)}
.stat--amber .stat__n{color:var(--amber)}
.stat--accent .stat__n{color:var(--accent)}

/* миниатюра товара */
.thumb{width:38px;height:38px;border-radius:var(--r-sm);background:#fff;display:grid;place-items:center;
  overflow:hidden;flex-shrink:0}
.thumb img{width:100%;height:100%;object-fit:cover}

/* Аватар — один компонент, размер переменной.
   Их было четыре и все в styles.css: .ava 38, .mava 40, .cava 46, .avabig 64.
   Одинаковые круги, отличавшиеся только величиной и тем, кто их завёл
   (находка UX-057). Новый размер теперь не заводит пятый класс. */
.avatar{
  --avatar-size:38px;
  width:var(--avatar-size);height:var(--avatar-size);border-radius:50%;
  display:grid;place-items:center;flex-shrink:0;object-fit:cover;
  font-weight:700;font-size:var(--fs-sm);color:var(--on-accent)}
.avatar--sm{--avatar-size:30px;font-size:var(--fs-xs)}
.avatar--md{--avatar-size:40px}
.avatar--lg{--avatar-size:46px}
.avatar--xl{--avatar-size:64px;font-size:var(--fs-h1)}
/* Заливка — отдельной осью от размера: роль и величина независимы. */
.avatar--admin{background:var(--blue)}
.avatar--mgr{background:var(--accent)}
.avatar--client{background:var(--accent-fill);background-size:200% 100%}

/* ---- 8а. ВИДЖЕТЫ ---------------------------------------------------------- */
/* Плитка сводки. Правила сняты с Command Center (src/components/widgets/
   Widget.jsx), значения — токенами каталога. Одна форма на все сводные
   блоки: иконка в тональном квадрате, мелкий заголовок капсом, содержимое,
   подвал со сноской через тонкую линию.

   Сетка задаёт ОДИНАКОВУЮ высоту ряда: разнобой высот читается как
   несобранность быстрее, чем любая мелочь внутри плитки. Отсюда следствие:
   виджет обрезает содержимое (overflow: hidden), поэтому список внутри
   показывает фиксированное число строк, а остаток называет в подвале.
   Молча обрезанный список — дефект, а не экономия места. */
/* 260px, а не 280: контейнер админки ~1130px, и при 280 на десктопе
   получалось три колонки вместо четырёх — широкий виджет не влезал
   в остаток ряда и ряды рвались с дырами. */
.widget-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));
  gap:var(--s5);grid-auto-rows:180px}
/* Ряд показателей в шапке страницы: высота по содержимому, не 180px. */
/* Компактная сводка — ряд показателей над списком (клиенты, заказы, топ,
   бэкапы, карточка клиента). Их обычно четыре, а колонка шириной 260px на
   рабочей области ~780px вмещала только три: четвёртая плитка уезжала одна на
   вторую строку (находка UX-060). Показатель — число и подпись, ему хватает
   190px; ряд из четырёх теперь помещается целиком, а на узком экране сетка
   по-прежнему сама переходит на две колонки и одну. */
.widget-grid--compact{grid-template-columns:repeat(auto-fit,minmax(min(190px,100%),1fr));grid-auto-rows:auto}
.widget-grid--compact .widget{min-height:0}
/* Сводка по содержимому ничего не режет: высота ряда не фиксирована, а
   overflow:hidden виджета срезал 2px у числа на телефоне (замер 19.09.2026). */
.widget-grid--compact .widget__body{overflow:visible}

.widget{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
  padding:var(--s6) var(--s7);display:flex;flex-direction:column;gap:var(--s3);
  /* Явно по верху: подписей у плиток разное число, выравнивание по низу
     или центру сдвигало цифры соседних плиток друг относительно друга. */
  justify-content:flex-start;height:100%;min-height:110px;overflow:hidden;
  transition:transform .15s ease,border-color .15s ease}
.widget--wide{grid-column:span 2}
/* Полоса слева — только когда у виджета есть состояние, требующее внимания. */
.widget--amber{border-left:3px solid var(--amber)}
.widget--red{border-left:3px solid var(--red)}
.widget--green{border-left:3px solid var(--green)}

a.widget{text-decoration:none;color:inherit}
/* Только настоящая ссылка поднимается под курсором: плитка без адреса (число,
   которое список не повторит, docs/adr/0024) не должна выглядеть кликабельной. */
a.widget[href]{cursor:pointer}
a.widget[href]:hover{transform:translateY(-1px);border-color:var(--accent-line)}

.widget__head{display:flex;align-items:center;gap:var(--s4)}
.widget__icon{width:28px;height:28px;border-radius:var(--r-sm);flex-shrink:0;
  background:var(--accent-weak);color:var(--accent);display:grid;place-items:center}
.widget__icon--amber{background:var(--amber-weak);color:var(--amber)}
.widget__icon--green{background:var(--green-weak);color:var(--green)}
.widget__icon--blue{background:var(--blue-weak);color:var(--blue)}
/* Заголовок плитки — одной строкой с многоточием (UI 2.0): перенос на
   второй строке съедал высоту фиксированного ряда, и плитка молча резала
   последнюю строку содержимого. min-width:0 + многоточие не дают хвосту
   заголовка лечь поверх шеврона (settings.html, ux-logic 3 круг). */
.widget__title{flex:1;min-width:0;margin:0;font-size:var(--fs-sm);font-weight:600;
  color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.widget__chev{flex-shrink:0;color:var(--faint)}
.widget__body{flex:1;min-height:0;overflow:hidden;
  display:flex;flex-direction:column;gap:var(--s3)}
/* Подвал — ровно одна строка. Перенос съедает высоту у содержимого, и
   виджет начинает резать последнюю строку списка на узкой колонке. */
.widget__foot{font-size:var(--fs-xs);color:var(--faint);
  padding-top:var(--s1);border-top:1px solid var(--border);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Крупный показатель внутри виджета. */
.wstat{display:flex;flex-direction:column;gap:2px}
.wstat__n{font-size:var(--fs-num);font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}
.wstat__n .cur{font-size:var(--fs-sm);font-weight:600;color:var(--faint)}
/* Значение-текст (дата, слово) — меньше числа: в --fs-num оно не влезает. */
.wstat__n--text{font-size:var(--fs-h1)}
.wstat__l{font-size:var(--fs-xs);font-weight:500;color:var(--muted)}
.wdelta{font-size:var(--fs-xs);font-weight:600}
.wdelta--up{color:var(--green)} .wdelta--down{color:var(--red)}

/* ---- Строка показателей (этап 13.3, концепция D) --------------------------
   Сводка над списком одной строкой вместо трёх плиток: до первой строки
   заказов было полэкрана отступа. Ячейка — ВХОД в отбор: число, которое
   сообщает о беде и не даёт её открыть, бесполезно. Итоговое значение
   (оборот) кнопкой не притворяется — у него .metric--flat.
   Плитки .widget остаются дашборду: там число и есть содержимое экрана. */
.metrics{display:flex;flex-wrap:wrap;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r);overflow:hidden}
.metric{flex:1 1 180px;min-width:0;display:flex;flex-direction:column;gap:2px;
  padding:var(--s5) var(--s7);border-right:1px solid var(--border);color:inherit}
.metric:last-child{border-right:0}
a.metric:hover{background:var(--surface-2)}
.metric.is-on{background:var(--accent-weak)}
.metric--flat{cursor:default}
.metric__n{font-size:var(--fs-num);font-weight:700;line-height:1.1;font-variant-numeric:tabular-nums}
.metric__n .cur{font-size:var(--fs-sm);font-weight:600;color:var(--faint)}
.metric__l{font-size:var(--fs-sm);font-weight:500;color:var(--muted)}
.metric__d{font-size:var(--fs-xs);color:var(--faint)}

/* ---- Быстрые фильтры (этап 13.3) -----------------------------------------
   Пилюли-пресеты над панелью фильтров. Они не заводят своей логики отбора:
   каждая — те же параметры адреса, что и у обычных фильтров. */
.quick{display:flex;flex-wrap:wrap;gap:var(--s1);margin:var(--s7) 0 var(--s5)}
.quick__i{display:inline-flex;align-items:center;gap:var(--s2);height:var(--ctl-h-sm);
  padding:0 var(--s5);border-radius:var(--r-pill);border:1px solid transparent;
  color:var(--muted);font-size:var(--fs-sm);font-weight:500;white-space:nowrap}
.quick__i:hover{background:var(--surface-2);color:var(--text)}
.quick__i.is-on{background:var(--accent-weak);border-color:var(--accent-line);color:var(--accent);font-weight:600}
.quick__n{font-size:var(--fs-xs);font-weight:600;color:var(--faint);font-variant-numeric:tabular-nums}

/* Сворачиваемая справка под списком: на рабочем экране длинное пояснение
   читается как документация, но убрать его насовсем нельзя — оно отвечает на
   вопросы, которые владелец задавал. */
.note-fold>summary{display:inline-flex;align-items:center;gap:var(--s2);cursor:pointer;
  color:var(--muted);font-size:var(--fs-sm);font-weight:600;list-style:none}
.note-fold>summary::-webkit-details-marker{display:none}
.note-fold>summary::before{content:"?";display:grid;place-items:center;width:18px;height:18px;
  border-radius:var(--r-pill);background:var(--surface-3);color:var(--muted);font-size:var(--fs-xs)}
.note-fold>summary:hover{color:var(--text)}
.note-fold>.sub{margin-top:var(--s4);max-width:78ch}
.quick__i.is-on .quick__n{color:var(--accent)}

/* Строка «подпись — число» внутри виджета. */
.wrow{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3)}
/* Строки «подпись — число» стоят плотнее списка: их до четырёх в плитке. */
.widget__body:has(> .wrow){gap:var(--s2)}
/* Значение-слово («назначен», «выключены настройкой») мельче числа: крупным
   оно выдавливало подпись строки в многоточие («Тревог…»). */
/* Словесное значение переносится и ужимается само, число — нет. Иначе
   длинное «выключены настройкой» съедало подпись, и строка читалась как
   «Тре… выключены настройкой» (находка на снимке дашборда 14.10.2026). */
.wrow__v--text{font-size:var(--fs-sm);font-weight:600;white-space:normal;text-align:right;
  flex-shrink:1;min-width:0}
.wrow__l{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:var(--fs-sm);font-weight:500;color:var(--muted)}
.wrow__v{flex-shrink:0;font-size:var(--fs-md);line-height:1.25;font-weight:700;white-space:nowrap;
  font-variant-numeric:tabular-nums}

/* Список внутри виджета: имя слева жмётся, значение справа не переносится. */
.wlist{display:flex;flex-direction:column;gap:var(--s3);min-height:0}
.wlist__i{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s4);
  font-size:var(--fs-sm)}
.wlist__n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wlist__v{flex-shrink:0;font-weight:600;font-variant-numeric:tabular-nums}
/* Точка статуса: цвет приходит из настроек, поэтому задаётся инлайном. */
.wdot{display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:var(--s2);vertical-align:middle;flex-shrink:0}
/* Точка без цвета из базы — «Без статуса» в разбивке заказов по статусам. */
.wdot--muted{background:var(--faint)}

/* Столбики динамики. Высота приходит из данных — единственный инлайн здесь. */
/* min-height, а не только flex:1: на телефоне высота виджета определяется
   содержимым, и растягиваться столбикам не от чего — график исчезал. */
.wbars{flex:1;min-height:18px;display:flex;align-items:flex-end;gap:3px}
.wbars i{flex:1;display:block;background:var(--accent-line);border-radius:2px 2px 0 0}
.wbars i.is-last{background:var(--accent)}

/* Полоса долей: сегменты одной шкалы. */
.wseg{display:flex;gap:2px;height:8px;border-radius:var(--r-pill);overflow:hidden}
.wseg span{display:block}

/* ---- 8б. СТРАНИЧНЫЕ КОМПОНЕНТЫ -------------------------------------------- */
/* Всё, что раньше жило в styles.css и потому не было частью системы:
   шапка, крошки, вкладки, панель фильтров, пагинация, пустое состояние.
   Правка эталона до экрана не доходила — ровно та причина, по которой
   дизайн-система однажды разошлась сама с собой. */

/* шапка страницы: [крошки] / заголовок + подпись / действия справа.
   margin-bottom — ОБЩЕЕ правило отступа до первого блока страницы: часть
   экранов отбивали его вручную классом mt-s7 на первом блоке, часть — нет
   («Клиенты», «Топ товары»: фильтр прилипал к шапке). Смежные вертикальные
   отступы у соседних блочных боксов схлопываются (max, не сумма), поэтому
   оставшиеся mt-s7 на первых блоках других экранов не удваивают отступ. */
.phead{display:flex;align-items:center;gap:var(--s5);flex-wrap:wrap;
  margin-bottom:var(--s8)}
/* Плитки-значка в шапке больше нет (UI 2.0): она повторяла значок пункта
   меню в двух сантиметрах слева и занимала 40px высоты на каждой странице. */
.phead__txt{flex:1;min-width:180px}
.phead__txt h1{margin:0}
.phead__sub{margin-top:2px;font-size:var(--fs-sm);color:var(--muted)}
.phead__acts{display:flex;gap:var(--s3);flex-wrap:wrap}

/* хлебные крошки */
.crumbs{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  margin-bottom:var(--s3);font-size:var(--fs-sm);color:var(--faint)}
.crumbs a{color:var(--muted);text-decoration:none}
.crumbs a:hover{color:var(--accent)}
.crumbs .sep{opacity:.5}
.crumbs .cur{color:var(--muted)}

/* Вкладки. ОДИН компонент: раньше их было два — подчёркнутые в шапке витрины
   и пилюли в карточке клиента, одна и та же работа в двух видах.
   Активная — тональная, а не залитая акцентом: белый на #a78bfa даёт 2.0:1,
   и подпись активной вкладки читалась хуже неактивных. */
.tabs{display:flex;gap:var(--s1);margin:var(--s7) 0 var(--s7);border-bottom:1px solid var(--border);
  overflow-x:auto;scrollbar-width:none}
/* UI 2.0: одна вкладка на весь продукт — подчёркнутая. Их было три вида:
   рамочные кнопки (клиент), сегменты во всю ширину (настройки витрины) и
   пилюли (страница витрины). Активная — акцентная черта и акцентный текст
   на тональном фоне, а не заливка: белый на #a78bfa — 2.0:1. */
.tabs a,.tabs button{display:inline-flex;align-items:center;gap:var(--s2);flex-shrink:0;
  padding:var(--s3) var(--s5);margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;
  background:none;border:0;border-bottom:2px solid transparent;color:var(--muted);
  font-family:inherit;font-weight:600;font-size:var(--fs-sm);
  text-decoration:none;white-space:nowrap;cursor:pointer;transition:.12s}
.tabs a:hover,.tabs button:hover{color:var(--text);background:var(--surface-2)}
.tabs a.is-active,.tabs button.is-active,.tabs a.active{
  background:var(--accent-weak);border-bottom-color:var(--accent);color:var(--accent)}
.tabs .count{font-size:var(--fs-xs);font-weight:600;color:var(--faint)}
.tabs a.is-active .count,.tabs a.active .count{color:inherit;opacity:.75}

/* панель фильтров. ЗАДАЁТ ТОЛЬКО РАСКЛАДКУ: вид контролов приходит из
   форм набора. Раньше тулбар перекрашивал select и input заново — второй
   комплект правил на те же элементы, и высота в ряду разъезжалась. */
.toolbar{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;margin-bottom:var(--s6)}
/* Поиск забирает свободное место, фильтры стоят по содержимому. Иначе
   селект из набора (width:100%) занимает всю строку, и каждый фильтр
   получает свой ряд — панель растёт вниз на пол-экрана. */
.toolbar .search{position:relative;flex:1 1 180px;min-width:0}
.toolbar .search .ic{position:absolute;left:var(--s4);top:50%;transform:translateY(-50%);
  display:flex;color:var(--faint);pointer-events:none}
.toolbar .search .input,.toolbar .search input{width:100%;padding-left:34px}
.toolbar .select,.toolbar select{width:auto;min-width:150px}
/* Кнопка «Фильтры · N» свёрнутой панели — только на телефоне (ниже). */
/* Панель отбора свёрнута на любой ширине (этап 13.3): быстрые пресеты и
   шесть полей одновременно — это старая перегруженная модель. Видимым
   остаётся поиск и кнопка «Фильтры · N»; раскрытые поля переносятся
   следующей строкой (row-gap у .toolbar — те же 8px). */
.toolbar__fold{display:inline-flex}
.toolbar[data-fold]:not(.is-open)>:not(.search):not(.toolbar__fold){display:none}
.toolbar .input,.toolbar input{width:auto}
.toolbar__end{margin-left:auto;display:flex;align-items:center;gap:var(--s3)}

/* строка над списком: сколько показано и чем список живёт */
.list-meta{display:flex;align-items:center;justify-content:space-between;gap:var(--s5);
  flex-wrap:wrap;margin-bottom:var(--s4);font-size:var(--fs-xs);color:var(--faint)}

/* пагинация */
.pager{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  flex-wrap:wrap;padding:var(--s5) var(--s7);border-top:1px solid var(--border);
  font-size:var(--fs-sm);color:var(--muted)}
.pages{display:flex;gap:var(--s1)}
.pages a,.pages span{display:grid;place-items:center;min-width:32px;height:32px;
  padding:0 var(--s3);border-radius:var(--r-sm);border:1px solid transparent;
  color:var(--muted);text-decoration:none;font-size:var(--fs-sm);font-variant-numeric:tabular-nums}
.pages a:hover{background:var(--surface-2);color:var(--text)}
.pages .is-current{background:var(--accent-weak);border-color:var(--accent-line);color:var(--accent);font-weight:700}
.pages .gap{color:var(--faint)}

/* пустое состояние. Не «нет данных», а что делать дальше — поэтому у него
   есть место под объяснение, а не только под заголовок. */
.empty-state{padding:48px var(--s9);border-radius:var(--r);text-align:center;
  background:var(--surface);border:1px dashed var(--border-strong)}
.empty-state__icon{width:64px;height:64px;border-radius:var(--r);margin:0 auto var(--s6);
  background:var(--accent-weak);color:var(--accent);display:grid;place-items:center}
.empty-state__t{font-size:var(--fs-md);font-weight:600;margin-bottom:var(--s2)}
.empty-state__d{margin:0 auto;max-width:44ch;font-size:var(--fs-sm);color:var(--muted);line-height:1.6}
.empty-state .btn{margin-top:var(--s7)}

/* панель выделения: липнет к низу окна, пока в списке что-то отмечено */
.selbar{position:sticky;bottom:var(--s5);z-index:var(--z-sticky);
  display:flex;align-items:center;gap:var(--s5);flex-wrap:wrap;
  margin-top:var(--s5);padding:var(--s5) var(--s7);
  background:var(--surface);border:1px solid var(--accent-line);
  border-radius:var(--r);box-shadow:var(--shadow-modal)}
.selbar[hidden]{display:none}
/* «Все по фильтру» — другое состояние, и выглядит по-другому: действие
   уйдёт и на товары, которых на экране не видно. */
.selbar--all{border-color:var(--amber)}
.selbar__n{font-weight:700;white-space:nowrap}
.selbar__hint{font-size:var(--fs-xs);color:var(--muted)}
.selbar__end{margin-left:auto;display:flex;align-items:center;gap:var(--s3);position:relative}

/* всплывающее меню: безобидное сверху, опасное внизу за чертой и красным */
.menu{min-width:280px;padding:var(--s2);border-radius:var(--r-md);
  background:var(--surface);border:1px solid var(--border);box-shadow:var(--shadow-modal)}
.menu[hidden]{display:none}
/* обёртка кнопки, у которой всплывает меню */
.menu-anchor{position:relative;display:inline-flex}
/* Раскрытое у кнопки: вверх, если кнопка в панели у нижнего края. */
.menu--pop{position:absolute;right:0;z-index:var(--z-popover)}
.menu--pop-up{bottom:calc(100% + var(--s3))}
.menu--pop-down{top:calc(100% + var(--s3))}
.menu__k{padding:var(--s4) var(--s5) var(--s2);font-size:var(--fs-xs);font-weight:600;
  color:var(--muted)}
.menu__i{display:flex;align-items:center;gap:var(--s4);width:100%;
  padding:var(--s3) var(--s5);border:0;border-radius:var(--r-sm);background:0;
  color:var(--text);font:inherit;font-size:var(--fs-sm);text-align:left;cursor:pointer;
  text-decoration:none}
.menu__i:hover{background:var(--surface-2)}
.menu__i .chev{margin-left:auto;color:var(--faint)}
.menu__sep{height:1px;margin:var(--s2) var(--s3);background:var(--border)}
.menu__i--danger{color:var(--red)}
.menu__i--danger:hover{background:var(--red-weak)}

/* поле с поиском по списку: разделов сотни, обычный список не работает.
   Один компонент для фильтра в шапке и для второго шага действия. */
.combo{position:relative}
.combo__list{position:absolute;left:0;right:0;top:calc(100% + var(--s2));
  z-index:var(--z-popover);max-height:260px;overflow:auto}
.combo__list--inline{position:static;margin-top:var(--s2);box-shadow:none}
.combo__hit{color:var(--accent);font-weight:700}
.combo__empty{padding:var(--s5);text-align:center;color:var(--muted);font-size:var(--fs-sm)}
/* Комбо, читающееся как select: тот же шеврон, что у .select, и с теми же
   отступами — иначе стрелка стоит не там, где на соседних полях фильтра
   (каталог, «Раздел», макет владельца 06.09.2026). Значения повторяют .select
   один в один, а не берут свои — это одна и та же геометрия стрелки. */
.combo--select .input{background-image:var(--select-arrow);background-repeat:no-repeat;
  background-position:right 12px center;padding-right:32px;cursor:pointer}

/* выбранный файл в зоне загрузки */
.files{display:flex;flex-direction:column;gap:var(--s3);margin-top:var(--s5)}
.files:empty{display:none}
.filerow{display:flex;align-items:center;gap:var(--s4);
  padding:var(--s3) var(--s5);border-radius:var(--r-sm);
  background:var(--surface-2);border:1px solid var(--border);font-size:var(--fs-sm)}
.filerow__n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.filerow__s{margin-left:auto;color:var(--faint);font-size:var(--fs-xs);white-space:nowrap}

/* ---- 8в. ОБРАТНАЯ СВЯЗЬ И ЗАГРУЗКА ---------------------------------------- */

/* флеш-сообщение страницы */
.flash{padding:var(--s4) var(--s6);margin-bottom:var(--s7);border-radius:var(--r-md);
  font-size:var(--fs-body);border:1px solid transparent}
.flash--ok{background:var(--green-weak);border-color:var(--green-line);color:var(--green)}
.flash--err{background:var(--red-weak);border-color:var(--red-line);color:var(--red)}
.flash--warn{background:var(--amber-weak);border-color:var(--amber-line);color:var(--amber)}

/* тост: подтверждение без ухода со страницы */
.toast{position:fixed;left:50%;bottom:var(--s9);z-index:var(--z-toast);
  transform:translateX(-50%) translateY(20px);opacity:0;pointer-events:none;
  padding:var(--s5) var(--s8);border-radius:var(--r-md);
  background:var(--green);color:var(--on-solid);font-weight:700;
  box-shadow:var(--shadow-modal);transition:.25s}
.toast.is-shown{opacity:1;transform:translateX(-50%) translateY(0)}
.toast--err{background:var(--red)}

/* полоса выполнения. Разбор прайса идёт около 30 секунд: без неё человек
   видит нажатую кнопку и больше ничего. */
.progress{height:6px;border-radius:var(--r-pill);background:var(--surface-3);overflow:hidden}
.progress__bar{display:block;height:100%;border-radius:inherit;background:var(--accent-fill);
  transition:width .3s ease}
/* Когда доля неизвестна — бегунок, а не застывший ноль. */
.progress--unknown .progress__bar{width:40%;animation:progress-slide 1.1s ease-in-out infinite}
@keyframes progress-slide{0%{transform:translateX(-100%)}100%{transform:translateX(250%)}}

/* скелет: форма содержимого до того, как оно приехало. Пустой экран на
   секунду читается как «ничего нет», скелет — как «сейчас будет». */
.skeleton{border-radius:var(--r-sm);background:var(--surface-3);animation:skeleton 1.4s ease-in-out infinite}
.skeleton--text{height:10px}
.skeleton--title{height:14px;width:40%}
.skeleton--line{height:10px;width:100%}
.skeleton--short{height:10px;width:60%}
.skeleton--tile{height:88px}
.skeleton-stack{display:flex;flex-direction:column;gap:var(--s4)}
@keyframes skeleton{0%,100%{opacity:1}50%{opacity:.45}}

/* ---- 9. МОДАЛКА ----------------------------------------------------------- */
/* Продукт использует нативный <dialog>: фокус-ловушка, Esc и инертный фон
   достаются бесплатно. База элемента живёт здесь — в styles.css она была
   третьим определением модалки после .overlay и .modal. */
dialog{border:1px solid var(--border);border-radius:var(--r);background:var(--surface);
  color:var(--text);padding:var(--s9);width:min(560px,calc(100vw - 32px));
  box-shadow:var(--shadow-modal);
  /* Без этих трёх строк содержимое уезжает за пределы окна: длинный список
     вниз, широкая строка вбок. Диалог обязан скроллиться сам — иначе на
     невысоком ноутбуке кнопки подтверждения просто недостижимы. */
  max-height:calc(100dvh - 64px);overflow:auto;overscroll-behavior:contain}
dialog::backdrop{background:rgba(0,0,0,.55);backdrop-filter:blur(8px)}
/* Фон под открытым окном не прокручивается. Нативный <dialog> сам этого не
   делает: колесо над затемнением уезжало по списку заказов (владелец,
   14.10.2026). Класс ставит base.html один раз на все окна — своей блокировки
   у каждого окна быть не должно. scrollbar-gutter держит место полосы
   прокрутки всегда, иначе в момент открытия страница дёргалась бы вбок на
   ширину полосы. */
html{scrollbar-gutter:stable}
html.is-modal-open{overflow:hidden}
/* .overlay удалён: самодельный оверлей не использовался ни на одном экране, а
   нативный <dialog> делает то же самое правильнее. Держать мёртвый компонент в
   наборе — значит однажды собрать по нему новый экран. */
/* Без position: нативный модальный <dialog> лежит в верхнем слое и центрирован
   браузером; position:relative вынимал его из этого слоя, и на прокрученной
   странице окно открывалось где-то вверху, за краем экрана. Абсолютный ✕
   внутри держится за fixed-диалог и без relative. */
.modal{width:min(620px,calc(100vw - 32px));padding:var(--s8)}
.modal--wide{width:min(720px,calc(100vw - 32px))}
/* Шире .modal--wide: окну нового клиента (six карточек-групп, 06.09.2026)
   тесно в 720 — пары полей внутри карточки схлопываются в столбик раньше,
   чем нужно. Подходящего модификатора не было — этот один на замену
   третьему самодельному .modal--product/.modal--order размеру заводить
   не значит: те двое частные (продукт/заказ), этот — общего пользования. */
.modal--xl{width:min(1100px,calc(100vw - 32px))}
.modal--confirm{width:min(420px,calc(100vw - 32px))}
.modal--image{width:min(720px,calc(100vw - 32px));padding:var(--s8)}
.modal--image img{display:block;max-width:100%;max-height:80vh;margin:var(--s6) auto 0;border-radius:var(--r-md);background:#fff}
.modal--confirm .confirm-text{margin:0}
/* Шторка слева — мобильная навигация (владелец, 06.09.2026): тот же приём,
   что у .sheet ниже, только с другого края и не только на телефоне — бургер
   живёт до 1023px. Вместо position (тот же довод, что у голого dialog выше —
   относительный слой вынимает окно из top layer, и на прокрученной странице
   оно откроется не там) двигаем margin: правый и нижний авто — прижимает к
   левому и верхнему краю без центрирования, высота во весь экран следует за
   нулевыми верх/низ. */
.modal--drawer{margin:0 auto 0 0;width:min(280px,84vw);height:100dvh;max-height:100dvh;
  padding:var(--s8) var(--s6);border-radius:0 var(--r) var(--r) 0;overflow-y:auto}
/* правый отступ 52px резервирует место под кнопку закрытия — длинный заголовок не налезает на ✕ */
/* Правый отступ 52px резервирует место под абсолютный ✕: без него длинный
   заголовок уезжает прямо под кнопку. Считается в пикселях сознательно —
   это ширина кнопки плюс её отступ, а не шаг шкалы. */
.modal__head{padding-right:52px;margin-bottom:var(--s6)}
.modal__title{font-size:var(--fs-h2);font-weight:700;line-height:1.3;text-wrap:balance}
.modal__desc{color:var(--muted);font-size:var(--fs-sm);margin-top:2px}
/* абзац в теле окна: вопрос подтверждения, пояснение */
.modal__text{margin:0;color:var(--muted);font-size:var(--fs-md);line-height:1.5}
/* Крестик в покое выглядит как в фокусе (кольцо :focus-visible акцентом) —
   иначе диалог, куда фокус приходит сам (первый фокусируемый элемент), и
   диалог, куда партиал грузится htmx уже открытым (фокус мимо кнопки),
   показывают один компонент двумя цветами. Тональная триада — та же, что у
   .badge--accent. */
.modal__close{position:absolute;top:var(--s8);right:var(--s8);width:32px;height:32px;
  display:grid;place-items:center;background:none;border:1px solid transparent;
  color:var(--muted);border-radius:var(--r-sm);font-size:16px;cursor:pointer}
.modal__close:hover{background:var(--surface-2);border-color:var(--border);color:var(--text)}
.modal__body{margin-top:var(--s6)}
.modal__footer{display:flex;gap:var(--s3);margin-top:var(--s7)}
.modal__footer--end{justify-content:flex-end}

/* Шторка снизу — тот же нативный <dialog>, только на телефоне приезжает от
   нижнего края. Отдельный механизм заводить не за чем: фокус-ловушка, Esc и
   инертный фон уже здесь. По умолчанию это обычная модалка: шторка во весь
   экран ноутбука — жест из телефона, попавший не туда. Телефонный вид —
   в общем блоке порога, третьего порога не появляется. */
dialog.sheet{width:min(560px,calc(100vw - 32px))}
.sheet__grip{display:none;width:36px;height:4px;border-radius:var(--r-pill);
  background:var(--border-strong);margin:0 auto var(--s6)}

/* цвет текста по смыслу — вместо инлайнового color: var(--green) в шаблоне */
.text--green{color:var(--green)} .text--amber{color:var(--amber)}
.text--red{color:var(--red)} .text--blue{color:var(--blue)}
.text--accent{color:var(--accent)} .text--muted{color:var(--muted)}
.text--faint{color:var(--faint)}

/* ---- 10. УТИЛИТЫ (минимум; не заменяют компоненты) ----------------------- */
.row{display:flex;align-items:center;gap:var(--s4);flex-wrap:wrap}
.row--between{justify-content:space-between}
.row--tight{gap:var(--s3)}
.stack{display:flex;flex-direction:column;gap:var(--s6)}
.stack--tight{gap:var(--s5)}
.spacer{flex:1}
.grow{flex:1;min-width:0}
.inline{display:inline}
.center{text-align:center}
.pad-lg{padding:var(--s9)}
.text--md{font-size:var(--fs-md)}
.text--medium{font-weight:500}
.text--pre{white-space:pre-line}

/* Отбивки. Список ЗАКРЫТ и привязан к шкале: утилита существует, чтобы у
   разметки не было повода писать style="margin-top:13px". Если нужного шага
   нет — это повод собрать блок через .stack с gap, а не добавить утилиту. */
.mt-s3{margin-top:var(--s3)}   .mt-s4{margin-top:var(--s4)}
.mt-s5{margin-top:var(--s5)}   .mt-s6{margin-top:var(--s6)}
.mt-s7{margin-top:var(--s7)}   .mt-s9{margin-top:var(--s9)}
.mb-s1{margin-bottom:var(--s1)} .mb-s2{margin-bottom:var(--s2)}
.mb-s5{margin-bottom:var(--s5)} .mb-s6{margin-bottom:var(--s6)}
.mb-s2{margin-bottom:var(--s2)} .mb-s3{margin-bottom:var(--s3)}
.mb-s7{margin-bottom:var(--s7)}
.mt-s2{margin-top:var(--s2)}
.text--xs{font-size:var(--fs-xs)}
.text--semibold{font-weight:600}
.row--loose{gap:var(--s9)}
/* Узкие поля фиксированной ширины: код, телефон, количество. Ширина здесь —
   подсказка о длине значения, а не подгонка под соседа. */
.field--xs{max-width:130px} .field--sm{max-width:180px}
/* Готовый адрес — поле по длине значения, а не на всю ширину карточки:
   ссылка короче строки формы, и растянутое поле читалось как незаполненное. */
.field--url{max-width:520px}
/* Значение и кнопка «скопировать» — одной строкой: при переносе кнопка
   оказывалась под полем и читалась как отдельное действие ни над чем. */
.field--url .row{flex-wrap:nowrap}
.field--url .input{flex:1 1 auto;min-width:0}
/* Экран без шапки: вход, ошибка. */
.page-narrow{max-width:640px;margin:var(--s10) auto}
.list-inset{margin:var(--s3) 0 0;padding-left:var(--s8)}
/* Карточка, требующая внимания: рамка цветом состояния. Это состояние
   компонента, а не украшение — поэтому вариант, а не инлайн border-color. */
.card--warn{border-color:var(--amber)}
.card--alert{border-color:var(--red)}

/* Палец не попадает в 34px. Правило CC: на телефоне кликабельное — 44×44. */
@media (max-width:767px){
  :root{--ctl-h:40px;--ctl-h-sm:36px}
  .icon-btn{width:44px;height:44px}
  .switch{width:48px;height:28px}
  .switch::after{width:22px;height:22px}
  .switch[aria-checked="true"]::after,.switch.is-on::after{left:22px}
  /* Виджеты: две колонки, высота по содержимому — иначе половина плиток
     стоит полупустой, а сетка растёт на несколько экранов. */
  .widget-grid{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:auto;align-items:start}
  .widget{height:auto}
  .widget--wide{grid-column:1 / -1}
  /* Шторка: во всю ширину, от нижнего края, с «язычком» сверху. */
  dialog.sheet{width:100%;max-width:none;margin:auto auto 0;padding:var(--s8);
    border-radius:var(--r) var(--r) 0 0;border-bottom:0;max-height:86dvh}
  .sheet__grip{display:block}
  /* Виджет со списком — на всю ширину: в половине экрана от названия
     клиента остаётся «ООО «…», и список перестаёт что-либо сообщать. */
  .widget:has(.wlist){grid-column:1 / -1}
  /* Высота ряда здесь по содержимому: столбикам не от чего растягиваться,
     а процентную высоту нельзя посчитать от неопределённой — нужна явная. */
  .wbars{flex:none;height:44px}
  /* Блок действий шапки страницы (.phead__acts) сам умеет переноситься
     (flex-wrap), но как ЭЛЕМЕНТ родительского .phead у него нет базовой
     ширины — перенесясь на свою строку, он занимает столько, сколько нужно
     ВСЕМ кнопкам в один ряд, а не ширину строки. На каталоге с четырьмя
     кнопками («Правила каталога» / «История загрузок» / «Разделы» / «…»)
     это шире экрана. flex:1 1 100% отдаёт ему всю строку, и уже его
     собственный flex-wrap переносит кнопки внутри неё. */
  .phead__acts{flex:1 1 100%}
  /* Прилипающая ПЕРВАЯ колонка таблицы (заказы, клиенты, витрины,
     пользователи, торговые точки): широкая таблица скроллится внутри
     .table-wrap по дизайну (DESIGN_SYSTEM.md, §7), но на телефоне после пары
     свайпов не видно, о какой строке речь. Класс ставится на конкретную
     th/td в шаблоне, а не на все таблицы разом — раскладка у первой колонки
     разная. На таблице заказов содержательных колонок в начале две — чекбокс
     и номер: без чекбокса нельзя отметить заказ, не прокрутив обратно,
     поэтому прилипают обе (.col-sticky + .col-sticky2 — второй столбик со
     сдвигом на ширину первого), а не только «Клиент» посередине таблицы, как
     было раньше. Фон — свой на все состояния строки, иначе сквозь прилипшую
     ячейку просвечивал бы контент, проехавший под ней при скролле.
     Ширина обязана быть ЯВНОЙ: position:sticky у ячейки таблицы сам по себе
     сбивает автоматический расчёт ширины колонки браузером — колонка вставала
     ýже, чем без sticky, и телефон/id без пробелов (их не на чем переносить)
     рисовался поверх соседней прилипшей колонки. min-width — гарантированный
     пол (колонка может стать и шире, если содержимому тесно, как у обычных
     .col-check/.col-num), а не потолок: потолок только сузил бы то, что и так
     ужалось. overflow-wrap заставляет длинную строку перенестись внутри
     полученной ширины, overflow — последняя страховка на случай, если и
     переносить нечем. */
  .table .col-sticky{position:sticky;left:0;z-index:var(--z-raise);background:var(--surface);
    min-width:170px;overflow:hidden;overflow-wrap:anywhere}
  /* Чекбокс — не «текстовая» колонка: 170px ей не нужны, а ширина обязана
     быть известна заранее — от неё считает сдвиг второй прилипшей колонки. */
  .table .col-check.col-sticky{width:44px;min-width:44px;overflow:visible}
  /* Вторая прилипшая колонка (номер заказа) сдвинута ровно на ширину первой
     (чекбокс) — иначе они лягут друг на друга в одной точке left:0. Ширина —
     своя, а не общие 170px .col-sticky (те для имени/названия, «№13» столько
     не нужно, только займёт место у соседней «Дата»); на th и td класс один
     и тот же (col-sticky2), поэтому одно правило держит оба. */
  .table .col-sticky2{left:44px;min-width:70px}
  .table tbody tr.is-clickable:hover .col-sticky{background:var(--surface-2)}
  .table tbody tr.is-selected .col-sticky{background:var(--accent-weak)}
  /* Список-таблица на телефоне — стопкой строк (UI 2.0). Прокрутка
     широкой таблицы вбок с прилипшими колонками оставляла на 390px
     клиенту одно слово в строке, а сумму и статус — за краем экрана.
     .table--stack превращает строку в карточку из трёх зон: слева
     галочка (.col-check), сверху главное (.st-lead) и число справа
     (.st-end), дальше остальные ячейки строками, действия (.st-acts) —
     справа внизу, второстепенное (.st-hide) прячется, подпись ячейки
     даёт data-label. Разметка таблицы одна на оба экрана. */
  .table.table--stack{min-width:0}
  /* Шапка не прячется целиком (ux-logic, UI 2.0): в ней живут «выбрать все
     на странице» и сортировка. На телефоне она — строка над стопкой:
     галочка и сортируемые заголовки чипами, остальные колонки скрыты. */
  .table--stack thead{display:block;border-bottom:1px solid var(--border);background:var(--surface-2)}
  .table--stack thead tr{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s3) var(--s5)}
  .table.table--stack thead th{display:none;padding:0;border:0;background:none;position:static;min-width:0;width:auto}
  .table.table--stack thead th.col-check,.table.table--stack thead th:has(.th-sort){display:block}
  .table--stack thead .th-sort{padding:var(--s2) var(--s5);border:1px solid var(--border);
    border-radius:var(--r-pill);background:var(--surface);min-height:36px}
  /* Сегмент — тот же палец, что и у остальных контролов на телефоне. */
  .seg button{min-height:var(--ctl-h)}
  /* Бренд в верхней полосе: подпись скрыта, и кликабельной оставалась точка
     в девять пикселей. Теперь зона — как у соседних значков. */
  .brand{min-height:44px;min-width:44px;justify-content:center}
  .table--stack thead .th-sort.is-active{border-color:var(--accent-line);background:var(--accent-weak)}
  /* Галочка крупнее: палец промахивался и попадал в фото под ней. */
  .table--stack .col-check input{width:24px;height:24px}
  /* Номера страниц — тот же палец, что у остальных контролов. */
  .pages a,.pages span{min-width:var(--ctl-h);height:var(--ctl-h)}
  .table--stack tbody{display:block}
  /* Запись заказа на телефоне держится в 168px (разбор дизайн-директора):
     поля 12px, строки значений без собственных отступов — иначе шесть ячеек
     добавляли записи полтора десятка пикселей ни за что. */
  .table--stack tbody tr{display:grid;grid-template-columns:auto minmax(0,1fr) auto;
    column-gap:var(--s4);padding:var(--s5);border-bottom:1px solid var(--border)}
  .table--stack tbody tr:last-child{border-bottom:0}
  .table.table--stack tbody td{grid-column:2;display:block;min-width:0;width:auto;
    padding:0;border:0;text-align:left;position:static;background:none;overflow:visible}
  .table.table--stack tbody td .pmeta{margin-top:0}
  .table.table--stack tbody td.col-check{grid-column:1;grid-row:1;width:auto;min-width:0;padding-top:2px}
  /* Фото — в той же левой колонке первой строки, под галочкой: во второй
     строке оно съезжало к разделу, ниже названия товара. */
  .table.table--stack tbody td.st-media{grid-column:1;grid-row:1;align-self:end}
  .table--stack tr:has(.st-media) td.st-lead{min-height:72px}
  .table.table--stack tbody td.st-lead{grid-row:1;font-weight:600}
  .table.table--stack tbody td.st-end{grid-column:3;grid-row:1;text-align:right;white-space:nowrap}
  .table.table--stack tbody td.st-acts{grid-column:3;grid-row:2 / span 5;align-self:end;justify-self:end;text-align:right}
  .table.table--stack tbody td.st-hide{display:none}
  .table--stack td[data-label]::before{content:attr(data-label) ": ";color:var(--faint)}
  /* Вторая строка ячейки (время под датой) в стопке — через пробел, а не ниже. */
  .table--stack td.st-inline>div{display:inline;margin-left:var(--s2)}
  /* Окно «Правила каталога»: три колонки (Каталог 21/11/11-П) по ~103px не
     читаются — каждая уже сама несёт свой заголовок (label) над двумя
     полями, поэтому перенос в столбец ничего не теряет: подпись остаётся
     прямо над своими полями, просто без соседей сбоку (ux-logic, 3 круг). */
  .field-row{flex-wrap:wrap}
  .field-row>.field{flex:1 1 100%;min-width:0}
  /* Крестик модалки — тоже цель нажатия, тот же порог 44×44. Сдвинут ближе
     к краю (right: var(--s3) вместо var(--s8)), чтобы уложиться в те же
     52px, которые .modal__head резервирует под него (52 = 8 + 44) — второе
     объявление padding у .modal__head тест не пропустит (test_modal_head_
     reserves_room_for_the_close_button), поэтому решаем с другой стороны. */
  .modal__close{width:44px;height:44px;right:var(--s3)}
  /* Телефон: до первой строки списка было 3 плитки и 6 рядов фильтров
     (UX-061). Панель с data-fold свёрнута до поиска и кнопки «Фильтры · N»
     (N — применённые фильтры, кроме поиска); плитки сводки — одной строкой
     с прокруткой вбок, а не столбиком на полэкрана. */
  .toolbar__fold{flex:1 1 100%;justify-content:center}
  /* Число в плитке не переносится: «1 146.90» рвалось на «1» и «146.90» и
     читалось как два значения (разбор OpenAI). Плитка берёт ширину по нему. */
  /* UI 2.0: сводка — две колонки, а не лента вбок: вторую плитку
     обрезал край экрана, и «Сумма за период» читалась как «Сумма за пер»
     (разбор OpenAI, 19.09.2026). */
  /* Строка показателей на телефоне — две колонки, а не четыре ряда во всю
     ширину: иначе до первой строки списка нужно проскроллить полэкрана.
     Пояснение под числом прячется — оно объясняет, а не считает. */
  .metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
  .metric{padding:var(--s5)}
  .metric:nth-child(odd){border-right:1px solid var(--border)}
  .metric:nth-child(even){border-right:0}
  .metric:nth-child(-n+2){border-bottom:1px solid var(--border)}
  .metric__n{font-size:var(--fs-h1)}
  .metric__d{display:none}
  /* Быстрые фильтры — одна строка с прокруткой вбок: перенос в три ряда
     съедал экран. Край растушёван, иначе обрезанная пилюля читается как
     поломка вёрстки, а не как «есть ещё». */
  .quick{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;gap:var(--s3);
    -webkit-mask-image:linear-gradient(90deg,#000 94%,transparent);
    mask-image:linear-gradient(90deg,#000 94%,transparent)}
  .quick::-webkit-scrollbar{display:none}
  .quick__i{flex:none}
  .widget-grid--compact{grid-template-columns:repeat(2,minmax(0,1fr))}
  .widget-grid--compact .wstat__n{white-space:nowrap;font-size:var(--fs-h1)}
  /* Нечётная последняя плитка — во всю ширину, а не сиротой в пол-ряда. */
  .widget-grid--compact>.widget:last-child:nth-child(odd){grid-column:1 / -1}
}

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