/* ui-primitives.css — слой примитивов ui-*, подключается ПОСЛЕДНИМ в base.html,
   web_base.html и web_base_client.html; классы ui-* параллельны легаси и побеждают
   порядком. Темы: :root — светлая, html.dark-theme и :root[data-theme="dark"] — тёмная.
   Правила, состав и разметка: miniapp/frontend/UI_PRIMITIVES.md (обязателен к прочтению). */

/* ═══════════════════════════ ТОКЕНЫ ═══════════════════════════ */

/* Одна модель коробки на весь продукт: кабинет не подключает components.css и
   считал по content-box — заданная высота 40px выходила 56px вместе с padding. */
*,
*::before,
*::after { box-sizing: border-box; }

/* Кабинет не снимал браузерные 8px у body: на телефоне он был сдвинут внутрь,
   липкая шапка не доходила до краёв экрана. */
body { margin: 0; }

/* Заголовок первым в блоке не должен отодвигать содержимое браузерным margin-top.
   :where() намеренно — нулевой вес, любое осмысленное правило экрана перекроет. */
:where(h1, h2, h3, h4, h5, h6, p, ul, ol):first-child { margin-top: 0; }

:root {
    /* ── Шрифт продукта: Manrope (self-hosted, подключён в трёх базовых шаблонах).
       Кабинет пока на системном стеке — мигрирует экранами вместе со слоем. ── */
    --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

    /* ── Цвет: один акцент на продукт ── */
    --ui-accent: #2563eb;
    --ui-accent-2: #3B82F6;        /* светлый верх градиента главной кнопки */
    --ui-accent-hover: #1D4ED8;    /* плоский ховер для легаси-моста */
    --ui-on-accent: #ffffff;
    /* Ховер парой токенов, а не сплошным цветом: сплошной сбрасывает
       background-image, и кнопка на кадр перехода «моргает». */
    --ui-btn-p-bg: linear-gradient(180deg, var(--ui-accent-2), var(--ui-accent));
    --ui-btn-p-bg-hover: linear-gradient(180deg, #2F6FE8, #1D4ED8);

    --ui-danger: #DC2626;
    --ui-danger-2: #EF4444;
    --ui-danger-hover: #B91C1C;    /* плоский ховер, пара к --ui-accent-hover */
    --ui-on-danger: #ffffff;
    --ui-btn-d-bg: linear-gradient(180deg, var(--ui-danger-2), var(--ui-danger));
    --ui-btn-d-bg-hover: linear-gradient(180deg, #DC3535, #B91C1C);
    --ui-success: #15803D;
    --ui-on-success: #ffffff;
    --ui-warning: #B45309;
    --ui-on-warning: #ffffff;
    /* Пятый оттенок значков: им помечено то, что пришло извне — заявки, лиды.
       Был россыпью #7c3aed по шести файлам, теперь один токен на обе темы. */
    --ui-info: #7C3AED;
    --ui-on-info: #ffffff;
    /* Заливки цветных плиток-значков — пара к --ui-btn-p-bg для ролей, у
       которых своей кнопки нет. Светлый стоп одинаков в обеих темах: это
       декоративная заливка значка, а не роль интерфейса (ср. --ui-fire).
       Значения взяты из прежних восьми копий один в один, чтобы переезд на
       примитив ничего не перекрасил. */
    --ui-tile-success: linear-gradient(135deg, #34D399, var(--ui-success));
    --ui-tile-warning: linear-gradient(135deg, #FBBF24, var(--ui-warning));
    --ui-tile-info: linear-gradient(135deg, #A78BFA, var(--ui-info));

    /* ── Цвет в графиках: две шкалы, и каждая значит ровно одно ──
       Статусная (--ui-chart-*) — как день соотносится с нормой; ею красятся
       столбики дневных показателей. Какая это метрика, говорят значок и
       заголовок карточки, цвету это повторять не нужно. Пока цвет нёс и то и
       другое разом, они спорили: собственный цвет калорий совпадал с цветом
       перебора, и «уложился» выглядело как «переел».
       Опознавательная (--ui-series-*) — для графиков, где линий несколько и
       цвет только отличает их друг от друга: замеры, динамика упражнения.
       Одинакова в обеих темах намеренно: это метки рядов, а не роли
       интерфейса (ср. --ui-fire). */
    --ui-chart-ok: var(--ui-success);
    --ui-chart-low: var(--ui-warning);
    --ui-chart-high: var(--ui-danger);
    --ui-chart-none: var(--ui-line);
    --ui-series-1: #4DA3FF;
    --ui-series-2: #F59E0B;
    --ui-series-3: #22C55E;
    --ui-series-4: #EC4899;
    --ui-series-5: #8B5CF6;
    --ui-series-6: #06B6D4;

    /* ── Поверхности и текст ── */
    --ui-bg: #EFF2F6;              /* фон страницы под поднятыми поверхностями */
    --ui-surface: #ffffff;         /* карточки, окна, кнопки-secondary */
    --ui-surface-2: #F3F5F8;       /* тонированные плашки, фон ghost-ховера */
    /* Поднятая ИНТЕРАКТИВНАЯ поверхность (карточка тарифа, secondary-кнопка).
       В тёмной теме СВЕТЛЕЕ окружения: на тёмном глубину даёт яркость, а не тень. */
    --ui-surface-raised: #ffffff;
    --ui-line: #E3E5E9;            /* границы форм */
    --ui-knob: #ffffff;            /* кружок тумблера — светлый в обеих темах */
    --ui-ctrl-line: #CBD5E1;       /* обводка невыбранных радио/чекбоксов:
                                      контрастнее --ui-line, иначе контрол сливается */
    /* Волосок вокруг поднятой поверхности. В тёмной теме ОБЯЗАТЕЛЕН: тени на тёмном
       гаснут (на части HDR-экранов не видны вовсе), карточка перестаёт быть кликабельной. */
    --ui-ring: 0 0 0 1px rgba(16, 24, 40, 0.06);
    --ui-text: #15181C;
    --ui-text-2: #5A6169;          /* вторичный текст, подписи — держит AA */
    /* Третья ступень. На неё переехали подписи полей и мета-строки кабинета — то есть
       читаемый текст, — поэтому значение поднято до нормы AA на всех трёх фонах. */
    --ui-text-3: #686E78;

    /* ── Свет: ядро «мягкой глубины» ── */
    /* Блик по верхней кромке поднятой поверхности. По отдельности не виден,
       вместе со слоями теней даёт «дорого». */
    --ui-edge: inset 0 1px 0 rgba(255, 255, 255, 0.90);
    /* Слои: узкая чёткая тень + широкая мягкая. Одна грубая тень запрещена. */
    --ui-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05), 0 4px 10px rgba(16, 24, 40, 0.07);
    --ui-shadow-md: 0 2px 4px rgba(16, 24, 40, 0.05), 0 12px 28px rgba(16, 24, 40, 0.10);
    --ui-shadow-lg: 0 6px 16px rgba(16, 24, 40, 0.08), 0 24px 56px rgba(16, 24, 40, 0.16);
    /* Цветные тени под главными кнопками — кнопка «весит» в свой цвет */
    --ui-glow-accent: 0 6px 16px rgba(37, 99, 235, 0.30);
    --ui-glow-danger: 0 6px 16px rgba(220, 38, 38, 0.25);
    --ui-glow-success: 0 6px 16px rgba(22, 163, 74, 0.28);
    --ui-glow-ai: 0 6px 16px rgba(107, 92, 255, 0.30);
    /* Невидимый градиент поверхности: 2% сверху вниз */
    --ui-grad-surface: linear-gradient(180deg, #ffffff 0%, #FAFBFC 100%);
    --ui-dim: rgba(9, 13, 20, 0.55);   /* затемнение под окнами и шторками */

    /* ── Прозрачности от цвета текста: приглушённые подписи, подложки, разделители
       клиентского приложения. Считаем от --ui-text, поэтому обе темы выходят сами. ── */
    --ui-a-90: color-mix(in srgb, var(--ui-text) 90%, transparent);
    --ui-a-85: color-mix(in srgb, var(--ui-text) 85%, transparent);
    --ui-a-80: color-mix(in srgb, var(--ui-text) 80%, transparent);
    --ui-a-75: color-mix(in srgb, var(--ui-text) 75%, transparent);
    --ui-a-70: color-mix(in srgb, var(--ui-text) 70%, transparent);
    --ui-a-65: color-mix(in srgb, var(--ui-text) 65%, transparent);
    --ui-a-60: color-mix(in srgb, var(--ui-text) 60%, transparent);
    --ui-a-55: color-mix(in srgb, var(--ui-text) 55%, transparent);
    --ui-a-50: color-mix(in srgb, var(--ui-text) 50%, transparent);
    --ui-a-45: color-mix(in srgb, var(--ui-text) 45%, transparent);
    --ui-a-40: color-mix(in srgb, var(--ui-text) 40%, transparent);
    --ui-a-35: color-mix(in srgb, var(--ui-text) 35%, transparent);
    --ui-a-30: color-mix(in srgb, var(--ui-text) 30%, transparent);
    --ui-a-25: color-mix(in srgb, var(--ui-text) 25%, transparent);
    --ui-a-22: color-mix(in srgb, var(--ui-text) 22%, transparent);
    --ui-a-20: color-mix(in srgb, var(--ui-text) 20%, transparent);
    --ui-a-18: color-mix(in srgb, var(--ui-text) 18%, transparent);
    --ui-a-15: color-mix(in srgb, var(--ui-text) 15%, transparent);
    --ui-a-12: color-mix(in srgb, var(--ui-text) 12%, transparent);
    --ui-a-10: color-mix(in srgb, var(--ui-text) 10%, transparent);
    --ui-a-08: color-mix(in srgb, var(--ui-text) 8%, transparent);
    --ui-a-06: color-mix(in srgb, var(--ui-text) 6%, transparent);
    --ui-a-05: color-mix(in srgb, var(--ui-text) 5%, transparent);
    --ui-a-04: color-mix(in srgb, var(--ui-text) 4%, transparent);
    --ui-a-03: color-mix(in srgb, var(--ui-text) 3%, transparent);
    --ui-a-02: color-mix(in srgb, var(--ui-text) 2%, transparent);

    /* «Вдавленная» подложка: строка КБЖУ, поля ввода в тёмных карточках. В светлой
       теме плёнка лёгкая, иначе блок становится грязно-серым пятном на белой карточке. */
    --ui-d-18: rgba(0, 0, 0, 0.04);
    --ui-d-20: rgba(0, 0, 0, 0.05);
    --ui-d-28: rgba(0, 0, 0, 0.07);

    /* Фон с прозрачностью — липкие шапки, под которые уезжает содержимое */
    --ui-bg-a-96: color-mix(in srgb, var(--ui-bg) 96%, transparent);
    --ui-bg-a-88: color-mix(in srgb, var(--ui-bg) 88%, transparent);
    --ui-bg-a-70: color-mix(in srgb, var(--ui-bg) 70%, transparent);

    /* Мягкие подложки статусов: плашки, строки таблиц, выделение выбранного */
    --ui-accent-soft: color-mix(in srgb, var(--ui-accent) 12%, transparent);
    --ui-success-soft: color-mix(in srgb, var(--ui-success) 12%, transparent);
    --ui-warning-soft: color-mix(in srgb, var(--ui-warning) 14%, transparent);
    --ui-danger-soft: color-mix(in srgb, var(--ui-danger) 12%, transparent);

    /* Фирменный оранжевый: питание, серии, «огонь». Вне палитры темы намеренно — это знак
       продукта. --ui-fire-2 — золотой второй стоп; через --ui-warning красить нельзя. */
    --ui-fire: #FF6B35;
    --ui-fire-2: #FFB938;

    /* ── Кегль: только эта шкала, только целые px ── */
    --ui-t-2xs: 11px;  /* плашки, таб-бар */
    --ui-t-xs: 12px;   /* подписи полей, мета */
    --ui-t-sm: 13px;   /* вторичный текст */
    --ui-t-md: 14px;   /* основной текст, кнопки md */
    --ui-t-lg: 16px;   /* заголовки карточек, кнопки lg */
    --ui-t-xl: 20px;   /* заголовки экранов и шторок */
    --ui-t-2xl: 26px;  /* крупные заголовки (пейвол) */
    --ui-t-3xl: 32px;  /* герой-цифры и заголовки лендинговых экранов */
    --ui-lh: 1.45;
    --ui-lh-tight: 1.2;

    /* ── Высоты контролов: единственный источник выравнивания рядов ── */
    --ui-h-sm: 32px;
    --ui-h-md: 38px;
    --ui-h-lg: 46px;

    /* Многострочное поле: высота своя, с рядом контролов не выравнивается, но
       ступеней столько же, сколько у остального. */
    --ui-area: 64px;      /* по умолчанию — три строки */
    --ui-area-lg: 100px;  /* длинный текст — пять строк */

    /* ── Отступы: шаг 4px ── */
    --ui-s-1: 4px;
    --ui-s-2: 8px;
    --ui-s-3: 12px;
    --ui-s-4: 16px;
    --ui-s-5: 24px;
    --ui-s-6: 32px;
    --ui-s-7: 48px;    /* разрежённые блоки: пустые состояния, экраны-заглушки */

    /* ── Радиусы ── */
    --ui-r-2xs: 4px;   /* мелкие метки и полосы прогресса */
    --ui-r-xs: 6px;    /* бейджи, чекбоксы */
    --ui-r-sm: 8px;    /* чипы, мелкие плашки */
    --ui-r-md: 12px;   /* контролы, внутренние карточки */
    --ui-r-lg: 16px;   /* окна, шторки, крупные карточки */
    --ui-r-pill: 999px;

    /* ── Слои: одна шкала на продукт. Роли: ── */
    --ui-z-sticky: 100;      /* залипающие шапки, нижняя навигация */
    --ui-z-dropdown: 500;    /* выпадающие списки, комбобоксы, перетаскивание */
    --ui-z-overlay: 2000;    /* затемнение под окном/шторкой */
    --ui-z-modal: 2001;      /* само окно */
    --ui-z-nested: 2021;     /* то, что открывается ПОВЕРХ окна: выбор из формы, подтверждение */
    --ui-z-nested-2: 2041;   /* третье звено цепочки: окно поверх вложенного окна */
    --ui-z-popover: 2061;    /* календарь и список, что на время открытия переезжают в body — поверх любого звена цепочки */
    --ui-z-toast: 2100;      /* тосты — поверх окон, их видно всегда */
    --ui-z-tour: 10000;      /* продукт-тур ведёт поверх всего */
    --ui-z-paywall: 10100;   /* оплата — единственное, что выше тура */

    /* ── Движение: словарь. Анимации — только этими токенами. ── */
    --ui-ease: cubic-bezier(0.2, 0.9, 0.3, 1);  /* резкий старт, мягкий доезд */
    /* Та же кривая с лёгким перелётом — для того, что ПОЯВЛЯЕТСЯ (меню, поповер, тост).
       Тому, что просто едет (шторка, панель), перелёт не нужен. */
    --ui-ease-pop: cubic-bezier(0.2, 0.9, 0.25, 1.08);
    --ui-dur-1: 120ms;   /* микроотклик: нажатие, чекбокс */
    --ui-dur-2: 200ms;   /* появление меню, поповера, окна */
    --ui-dur-3: 320ms;   /* структурное: шторка, панель */
}

html.dark-theme,
:root[data-theme="dark"] {
    /* Тёмный акцент светлее светлого: насыщенный синий на чёрном «гаснет».
       Текст на нём тёмный. */
    --ui-accent: #4C9BFF;
    --ui-accent-2: #6FB2FF;
    --ui-accent-hover: #3B8BF5;
    --ui-on-accent: #08101D;
    --ui-btn-p-bg: linear-gradient(180deg, var(--ui-accent-2), var(--ui-accent));
    --ui-btn-p-bg-hover: linear-gradient(180deg, #5FA8FF, #3B8BF5);

    --ui-danger: #F16A6A;
    --ui-danger-2: #F87171;
    --ui-danger-hover: #E85555;
    --ui-on-danger: #250808;
    --ui-btn-d-bg: linear-gradient(180deg, var(--ui-danger-2), var(--ui-danger));
    --ui-btn-d-bg-hover: linear-gradient(180deg, #F98080, #E85555);
    --ui-success: #2EB584;
    --ui-on-success: #071812;
    --ui-warning: #D9A23F;
    --ui-on-warning: #241703;
    --ui-info: #A78BFA;
    --ui-on-info: #150B2B;

    /* Поверхности слегка тёплые к синему — общая пара для кабинета и клиента. Фоны
       страниц пока легаси; --ui-bg применяется поэкранно по мере миграции. */
    --ui-bg: #10151D;
    --ui-surface: #1B222C;
    --ui-surface-2: #242C38;
    --ui-surface-raised: #242C38;
    --ui-line: #2C3541;
    --ui-knob: #F2F4F7;
    --ui-ctrl-line: #4A5563;
    --ui-ring: 0 0 0 1px var(--ui-line);
    --ui-text: #F2F4F7;
    --ui-text-2: #9BA5B4;
    --ui-text-3: #8B94A1;          /* AA в тёмной теме на всех трёх фонах */
    /* Плёнка вдавленных блоков: на тёмном фоне она должна быть плотной */
    --ui-d-18: rgba(0, 0, 0, 0.18);
    --ui-d-20: rgba(0, 0, 0, 0.20);
    --ui-d-28: rgba(0, 0, 0, 0.28);

    --ui-edge: inset 0 1px 0 rgba(255, 255, 255, 0.06);
    --ui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.50), 0 6px 16px rgba(0, 0, 0, 0.40);
    --ui-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.50), 0 14px 32px rgba(0, 0, 0, 0.45);
    --ui-shadow-lg: 0 8px 20px rgba(0, 0, 0, 0.50), 0 28px 64px rgba(0, 0, 0, 0.60);
    --ui-glow-accent: 0 6px 18px rgba(76, 155, 255, 0.35);
    --ui-glow-danger: 0 6px 18px rgba(241, 106, 106, 0.30);
    --ui-glow-success: 0 6px 18px rgba(74, 200, 130, 0.32);
    --ui-glow-ai: 0 6px 18px rgba(139, 109, 255, 0.35);
    --ui-grad-surface: linear-gradient(180deg, #1E2632 0%, #1A212B 100%);
    --ui-dim: rgba(0, 0, 0, 0.60);
}

/* ═══════════════════════════ БАЗА ═══════════════════════════ */

/* Ряд контролов. stretch, не center: center маскирует разную высоту, а не
   чинит её — все контролы и так одной высоты через --ui-h-*. */
.ui-row {
    display: flex;
    gap: var(--ui-s-2);
    align-items: stretch;
    flex-wrap: wrap;
}
/* Пара главных действий делит ширину поровну (мобильный паттерн) */
.ui-row--end { justify-content: flex-end; }

.ui-row--between { justify-content: space-between; }
.ui-row--center { justify-content: center; }
.ui-row--middle { align-items: center; }   /* законно для НЕ-контролов: иконка + текст */
.ui-row--nowrap { flex-wrap: nowrap; }
.ui-row--tight { gap: var(--ui-s-1); }
.ui-row--loose { gap: var(--ui-s-4); }
/* Ряд, который на телефоне встаёт в столбик: на опорных 360 px «переключатель +
   список» не делится — вкладке достаётся 45 px при нужных 55, подпись обрезается. */
@media (max-width: 559px) {
    .ui-row--phone-stack > [class] { flex: 1 1 100%; min-width: 0; }
}

/* Ряд главных действий: кнопки по двое в ряд, ширина колонок одинаковая. Сетка,
   а не флекс: у флекса ширина считается от подписи, и «Открыть» рядом с
   «Назначить» выходили разными — ряд читался как случайный набор. */
.ui-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ui-s-2);
}
/* Половина ряда как основа + рост: две кнопки делят ширину поровну, одна
   растягивается на всю. Считает флекс, поэтому спрятанные кнопки в счёт не
   идут — на нумерации (nth-child) это ломалось: скрытая «Папка» оставляла
   «Создать» половинкой у левого края. `min-width: 0` обязателен: с
   `max-content` ширина возвращается к длине подписи и кнопки снова разные. */
.ui-actions > [class][class] {
    flex: 1 1 calc(50% - var(--ui-s-1));
    min-width: 0;
}
/* Кнопка-значок в ряду ширину не делит: она знак, а не подпись, и растянутая
   на полряда читается как «главное действие». */
.ui-actions > [class][class].ui-btn--icon { flex: 0 0 auto; width: var(--ui-h-md); }
/* Третий вид ряда — по кнопке на строку. Ставится разметкой, а не считается по
   длине подписи: «Назначить всем текущим» в половине ряда ломается на три
   строки, и ряд из двух кнопок выходит разной высоты. */
@media (max-width: 559px) {
    .ui-actions--stack > [class][class] { flex: 1 1 100%; }
}
/* На широком экране ряд стоит у правого края, а колонки остаются равными:
   сетка по содержимому даёт всем ширину самой длинной подписи. */
@media (min-width: 560px) {
    .ui-actions {
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: minmax(0, 1fr);
        width: max-content;
        max-width: 100%;
        margin-left: auto;
    }
    .ui-actions > [class][class] { width: auto; }
}

/* Вертикальный стек: расстояние задаёт контейнер, а не «отступ снизу у каждого
   соседа», — поэтому не бывает двойных и схлопнувшихся полей. */
.ui-stack {
    display: flex;
    flex-direction: column;
    gap: var(--ui-s-3);
    min-width: 0;
}
.ui-stack--tight { gap: var(--ui-s-2); }
.ui-stack--loose { gap: var(--ui-s-5); }

/* Занять свободное место в ряду. min-width:0 обязателен: без него flex:1 растягивает
   элемент по содержимому и ломает усечение текста. */
/* Базис 0, а не auto: при auto базисом становится собственная ширина элемента, а у
   полей и списков она объявлена как 100% — соседняя кнопка не влезала и переносилась. */
.ui-grow { flex: 1 1 0; min-width: 0; }
/* Колонка вдвое шире соседней — раскладка «главное слева, сводка справа» */
.ui-grow-2 { flex: 2 1 0; min-width: 0; }
/* Полоса прогресса-акцент: заполнение задаётся переменной --ui-progress */
.ui-bar { height: 4px; border-radius: var(--ui-r-pill); background: var(--ui-btn-p-bg); }
.ui-bar-fill { height: 100%; width: var(--ui-progress, 0%); border-radius: var(--ui-r-pill); background: var(--ui-btn-p-bg); transition: width var(--ui-dur-3) var(--ui-ease); }
/* Слои над окном: подтверждение поверх окна, просмотр фото поверх подтверждения.
   Вес [class] обязателен: эти классы всегда висят вместе с .ui-overlay, а её
   z-index объявлен ниже по файлу — при равном весе побеждал он, и подъём
   не срабатывал ни разу. */
[class].ui-z-above { z-index: calc(var(--ui-z-modal) + 10); }
[class].ui-z-top { z-index: var(--ui-z-nested); }

/* ═══════════════════════════ КНОПКИ ═══════════════════════════ */

.ui-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ui-s-2);
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-4);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    line-height: 1; /* высоту держит min-height, а не наследуемый интерлиньяж */
    color: var(--ui-text);
    border: none;
    border-radius: var(--ui-r-md);
    background: none;
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
    transition: transform var(--ui-dur-1) var(--ui-ease),
                filter var(--ui-dur-1) var(--ui-ease),
                background-color var(--ui-dur-1) var(--ui-ease),
                box-shadow var(--ui-dur-1) var(--ui-ease);
}
/* Микроотклик нажатия — у всех вариантов, на любом устройстве. Сжатие плюс лёгкое
   затемнение: одно сжатие на заливке почти не читается. */
.ui-btn:active:not(:disabled) { transform: scale(0.96); filter: brightness(0.92); }
.ui-btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; }
.ui-btn:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }
/* Опасная кнопка обводится своим цветом: синее кольцо вокруг красной заливки
   читается как две разные кнопки, наложенные друг на друга. */
.ui-btn--danger:focus-visible,
.ui-btn--ghost-danger:focus-visible { outline-color: var(--ui-danger); }

.ui-btn--primary {
    background: var(--ui-btn-p-bg);
    color: var(--ui-on-accent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.ui-btn--danger {
    background: var(--ui-btn-d-bg);
    color: var(--ui-on-danger);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
/* Цветное свечение — МОДИФИКАТОР для крупных одиночных CTA (пейвол, шторки), где нет
   обрезающих контейнеров: у рядовых кнопок glow режется overflow'ом в «полосы». */
.ui-btn--glow.ui-btn--primary {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), var(--ui-glow-accent);
}
.ui-btn--glow.ui-btn--danger {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), var(--ui-glow-danger);
}
.ui-btn--glow.ui-btn--success {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), var(--ui-glow-success);
}
.ui-btn--glow.ui-btn--ai {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), var(--ui-glow-ai);
}
/* Вторичная поверх тонированной подложки: непрозрачная --secondary читается
   на ней наклейкой, поэтому здесь заливка от текста, а не своя поверхность. */
.ui-btn--tint {
    background: var(--ui-a-05);
    box-shadow: inset 0 0 0 1px var(--ui-a-10);
    color: inherit;
}
/* Вторичная в «мягкой глубине» — без border, контур даёт волосок в тени */
.ui-btn--secondary {
    background: var(--ui-surface-raised);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-sm);
}
.ui-btn--success {
    background: var(--ui-success);
    color: var(--ui-on-success);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
/* Кнопка ИИ-действия. Фиолетовый градиент — сквозной знак «сделано ИИ» во всём
   продукте, поэтому цвет намеренно вне палитры системы и живёт своей парой. */
.ui-btn--ai {
    background: linear-gradient(135deg, #9B5DE5, #6B5CFF);
    color: #fff;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.ui-btn--ghost { color: var(--ui-text-2); }
/* Мягкая акцентная — карандаш правки в строке данных: заметен, но не спорит
   с главной кнопкой экрана. Раньше этот скин лежал копиями в двух страницах. */
.ui-btn--soft {
    background: var(--ui-accent-soft);
    border: 1px solid color-mix(in srgb, var(--ui-accent) 22%, transparent);
    color: var(--ui-accent);
}
/* Опасное действие без заливки: «удалить» в ряду обычных кнопок не должно
   кричать красным прямоугольником, но без границы рядом с залитыми соседями
   читалось как отвалившийся текст, а не как кнопка. */
.ui-btn--ghost-danger {
    color: var(--ui-danger);
    background: none;
    border: 1px solid color-mix(in srgb, var(--ui-danger) 35%, transparent);
}
@media (hover: hover) {
    .ui-btn--ghost-danger:hover:not(:disabled) {
        background: color-mix(in srgb, var(--ui-danger) 10%, transparent);
    }
}

/* Ховеры только там, где есть мышь: на таче :hover «залипает» после тапа */
@media (hover: hover) {
    .ui-btn--primary:hover:not(:disabled) { background: var(--ui-btn-p-bg-hover); }
    .ui-btn--danger:hover:not(:disabled) { background: var(--ui-btn-d-bg-hover); }
    .ui-btn--secondary:hover:not(:disabled) { background: var(--ui-surface-2); }
    .ui-btn--ghost:hover:not(:disabled) { background: var(--ui-surface-2); color: var(--ui-text); }
    /* Градиент осветляем фильтром: подменять пару стопов ради ховера — лишний код */
    .ui-btn--ai:hover:not(:disabled) { filter: brightness(1.06); }
}

.ui-btn--sm { min-height: var(--ui-h-sm); padding: 0 var(--ui-s-3); font-size: var(--ui-t-sm); }
.ui-btn--lg { min-height: var(--ui-h-lg); padding: 0 var(--ui-s-5); font-size: var(--ui-t-lg); font-weight: 700; }
/* Квадратная кнопка без подписи: ширина равна высоте. Только этот модификатор имеет
   право отнять у кнопки поля и зафиксировать ширину. */
.ui-btn--icon { width: var(--ui-h-md); padding: 0; flex: none; }
.ui-btn--icon.ui-btn--sm { width: var(--ui-h-sm); }
.ui-btn--icon.ui-btn--lg { width: var(--ui-h-lg); }

/* Кнопку с одной иконкой флекс-ряд растягивает в широкую плашку — гасим только это:
   `:only-child` не отменяется текстовым узлом, поэтому квадрат просит `.ui-btn--icon`. */
[class].ui-btn:has(> i:only-child):not(.ui-btn--block),
[class].ui-btn:has(> svg:only-child):not(.ui-btn--block) {
    flex: 0 0 auto;
    min-width: var(--ui-h-md);
}
[class].ui-btn--sm:has(> i:only-child):not(.ui-btn--block),
[class].ui-btn--sm:has(> svg:only-child):not(.ui-btn--block) { min-width: var(--ui-h-sm); }
[class].ui-btn--lg:has(> i:only-child):not(.ui-btn--block),
[class].ui-btn--lg:has(> svg:only-child):not(.ui-btn--block) { min-width: var(--ui-h-lg); }
/* Кнопка во всю ширину — всегда главное действие экрана или шторки, поэтому
   и высота у неё главная (46). Иначе «Сохранить» в шторке на 8px ниже, чем
   «Создать» в панели, и на соседних экранах это видно. */
.ui-btn--block { width: 100%; }
.ui-btn--block:not(.ui-btn--sm) { min-height: var(--ui-h-lg); }

/* «Добавить ещё» — пунктирная рамка вместо заливки: это не второстепенное действие,
   а приглашение, пунктир читается как пустое место, которое можно заполнить. */
.ui-btn--dashed {
    background: color-mix(in srgb, var(--ui-accent) 6%, transparent);
    border: 1.5px dashed color-mix(in srgb, var(--ui-accent) 45%, transparent);
    color: var(--ui-accent);
    font-weight: 600;
}
@media (hover: hover) {
    .ui-btn--dashed:hover:not(:disabled) {
        background: color-mix(in srgb, var(--ui-accent) 12%, transparent);
        border-color: var(--ui-accent);
    }
}

/* ═══════════════════════════ ФОРМЫ ═══════════════════════════ */

/* Поле = подпись + контрол + подсказка/ошибка. Расстояние внутри задаёт gap, между
   полями — .ui-stack: margin-bottom у поля давал двойные отступы в сетках. */
.ui-field { display: flex; flex-direction: column; gap: var(--ui-s-1); min-width: 0; }
.ui-field-label {
    font-size: var(--ui-t-xs);
    font-weight: 600;
    color: var(--ui-text-2);
}
.ui-hint { font-size: var(--ui-t-xs); color: var(--ui-text-3); line-height: var(--ui-lh); }

/* Микро-подпись капсом — «КОММЕНТАРИЙ НЕДЕЛИ», «ТИП ОТЧЁТА», шапка таблицы.
   Один источник: в страничных CSS этот паттерн разошёлся на 178 правил и 124
   разных сочетания (только у letter-spacing 24 значения). Сюда же подключены
   .ui-meta-label, .ui-table th и .ui-appbar-sub — раньше каждый нёс свою копию. */
.ui-caption,
.ui-meta-label,
.ui-table th,
.ui-appbar-sub {
    font-size: var(--ui-t-2xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* text-3, а не text-2: по продукту так красили 104 подписи из 172, и именно
       этот токен выверен по контрасту, в отличие от альфа-ступеней 45/50/55. */
    color: var(--ui-text-3);
}

/* ── Шим легаси-подписей ─────────────────────────────────────────────────────
   Те же 172 правила капсом, что жили по страницам со 124 разными сочетаниями
   (только у letter-spacing было 24 значения). Имена оставлены легаси: разметку
   на .ui-caption переведём отдельной задачей, а значения сходятся уже сейчас.
   Новые сюда не добавлять — новым местам ставить .ui-caption в разметке. ── */
.alt-suggest-head,
.body-view-label,
.cab-bigcal-dl,
.cab-cfg-item label,
.cab-group-title,
.cab-kbju-item label,
.cab-matrix thead th,
.cab-pinfo-l,
.cab-rep-fg label,
.cab-set-h,
.cab-sheet-title,
.ci-meas-table th,
.ci-strip-cap,
.cl-caption,
.col-badge,
.cs-hd .badge,
.cta .ck,
.cta2 .ck,
.cw-dr__eyebrow,
.cw-goal__eyebrow,
.cw-hero__eyebrow,
.cw-more-title,
.cw-prog__label,
.cw-qe__eyebrow,
.cw-route__eyebrow,
.cw-trainers__title,
.cw-when,
.cw-wk__eyebrow,
.dbv6-check__all,
.dbv6-check__label,
.de-sub-label,
.dr-dist__label,
.dr-group__title,
.dr-hero__chip,
.dr-kbju__label,
.dr-ring-l,
.dr-tile-l,
.dr-tile__label,
.dynamics-best-unit,
.dynamics-stat-label,
.exercise-descriptions-list .trainer-comment-card h3, .exercise-descriptions-list .exercise-technique h3,
.exercise-view-section-title,
.exr-history__title,
.exr-rest__label,
.eyebrow,
.gd-group-title,
.gr-section-title,
.hw-start-label,
.hw-today,
.inv-card .k,
.kicker,
.meal-modal .em-ai-lbl,
.meal-sheet__label,
.ms-hero__label,
.mt-sheet__cat,
.mt-sheet__kv span,
.mt-sheet__label,
.nx-numtile__label,
.obw-proj-hero .k,
.obw-ruler__label,
.pd-hist-label,
.pd-now-label,
.pd-super,
.pe-matrix-table thead th,
.pe-stats-subtitle,
.pf-section-title,
.probs-t,
.pw-brand,
.pw-stats-caption,
.qw-note .bd small,
.qw-sep,
.qw-trophy .fct small,
.rp-cal-chip__l,
.rp-eyebrow,
.rp-mp-eyebrow,
.rp-range__eyebrow,
.rp-seclabel,
.rpe-label,
.rt-big .fact small,
.rt-dxf small,
.rt-here,
.rt-histt,
.rt-me__label,
.rt-why small,
.sec-k,
.set-input-label,
.set-title,
.shot .t,
.sob-macro__l,
.sob-proj-eyebrow,
.sob-ruler__label,
.sob-sec__title,
.sob-stat__l,
.sob-sublabel,
.sob-tl__when,
.ss-hero__label,
.ss-section-label,
.tk-badge-status,
.tk-section__title,
.tk-stat__label,
.wc-apply > label,
.wc-f > span,
.wc-note label,
.wc-plabel,
.we-stats-subtitle,
.web-more-sheet-group,
.web-sidebar-section-title,
.wk-sep,
.wl-date__today,
.wl-summary__label,
.wp-appbar-sub,
.wp-body-label,
.wp-bump-kicker,
.wp-hist-stat-label,
.wp-ios-list-header,
.wp-media-kicker,
.wp-ov-caption,
.wp-ov-day,
.wp-ov-group-done,
.wp-ov-group-tag,
.wp-ov-tile-label,
.wp-period,
.wp-pr-kicker,
.wp-rest-kicker,
.wp-rest-next-label,
.wp-section-title,
.wp-set-edit-kicker,
.wp-set-kicker,
.wp-set-wait,
.wp-ss-editor-kicker,
.wp-ss-head-kicker,
.wp-ss-hist-title,
.wp-ss-label,
.wp-ss-note-title,
.wp-ss-past-title,
.wp-stat__label,
.wp-tile-label,
.wp-wc-points,
.wp-wc-recap-title,
.wp-wc-source,
.wp-wc-stat-label,
.wr-cap,
.wr-hist-caption,
.wr-sets-head,
.wr-sets-table th,
.ws-flabel,
.wsr-habits > label,
.wsr-ph-habits > label,
.wsr-ph-row label,
.wv-stats-subtitle,
.xc__f label,
.xc__sh {
    font-size: var(--ui-t-2xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ui-text-3);
}

.ui-input[aria-invalid="true"] { border-color: var(--ui-danger); }
/* Ошибка валидации поля (input-validation.js). Раньше вид жил инлайном с
   собственным #dc3545 мимо палитры и в тёмной теме читался плохо. */
[class].has-validation-error {
    border-color: var(--ui-danger);
    background: color-mix(in srgb, var(--ui-danger) 4%, transparent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-danger) 15%, transparent);
}
.input-validation-error {
    margin-top: var(--ui-s-1);
    font-size: var(--ui-t-xs);
    font-weight: 600;
    color: var(--ui-danger);
}

/* ═══════════════ ВЕРТИКАЛЬНЫЙ РИТМ ПОЛОТНА ═══════════════ */
/* Ритм задаёт страница: 16 между родственными блоками, 32 между смысловыми группами
   (`.ui-group`). Отступами, а не gap: полотна показывают инлайновым style.display. */
.ui-flow > * + * { margin-top: var(--ui-s-4); }
.ui-flow > .ui-group + *,
.ui-flow > * + .ui-group { margin-top: var(--ui-s-6); }
.ui-flow > .ui-group:first-child { margin-top: 0; }

/* Форма: колонка полей + ряды на широком экране */
.ui-form-row { display: grid; grid-template-columns: 1fr; gap: var(--ui-s-3); }
@media (min-width: 560px) {
    .ui-form-row { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}

/* Переключатель-тумблер: состояние «включено/выключено» словами не объяснить */
.ui-switch {
    -webkit-appearance: none;
    appearance: none;
    width: 40px;
    height: 24px;
    flex: none;
    margin: 0;
    border-radius: var(--ui-r-pill);
    background: var(--ui-ctrl-line);
    position: relative;
    cursor: pointer;
    transition: background var(--ui-dur-1) var(--ui-ease);
}
.ui-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: var(--ui-r-pill);
    background: var(--ui-knob);
    box-shadow: var(--ui-shadow-sm);
    transition: transform var(--ui-dur-1) var(--ui-ease);
}
.ui-switch:checked { background: var(--ui-accent); }
.ui-switch:checked::after { transform: translateX(16px); }
.ui-switch:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }

/* Поля — единственная поверхность стиля с видимой границей: полю нужен
   явный контур зоны ввода, тень его не даёт. */
.ui-input {
    width: 100%;
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-3);
    font-family: inherit;
    font-size: var(--ui-t-md);
    line-height: 1;
    color: var(--ui-text);
    background: var(--ui-surface);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-r-md);
    box-sizing: border-box;
    transition: border-color var(--ui-dur-1) var(--ui-ease),
                box-shadow var(--ui-dur-1) var(--ui-ease);
}
.ui-input::placeholder { color: var(--ui-text-3); }

/* ═══════════════ ВЫПАДАЮЩИЙ СПИСОК ═══════════════ */
/* Раскрытый системный список нам не подчиняется (прямые углы, строки 20px, системная
   подсветка) — рисуем свой. Разметка та же: <select> остаётся и держит значение. */
.ui-select {
    position: relative;
    display: block;
    width: 100%;
    /* Обёртка забирает раскладочные классы бывшего select, иногда вместе с оформлением
       поля: гасим внешность, иначе кант в канте (вид рисует .ui-select-btn внутри). */
    background: none;
    border: 0;
    padding: 0;
    box-shadow: none;
    min-height: 0;
}
/* Родной select остаётся в потоке и получает фокус — просто невидим.
   display: none забрал бы его у клавиатуры и у форм. */
.ui-select > select {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
}
.ui-select-btn {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    width: 100%;
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-3);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-r-md);
    background: var(--ui-surface);
    color: var(--ui-text);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    line-height: 1;
    text-align: left;
    cursor: pointer;
}
.ui-select-btn-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* На опорных 360 px пара списков в ряду фильтров обрезала подписи многоточием; поля
   по 8 вместо 12 отдают недостающие пиксели. Шире 400 px поля прежние. */
@media (max-width: 400px) {
    .ui-select-btn, .ui-date-btn { padding-left: var(--ui-s-2); padding-right: var(--ui-s-2); }
}
.ui-select-btn-chev {
    flex: none;
    color: var(--ui-text-3);
    font-size: var(--ui-t-xs);
    transition: transform var(--ui-dur-1) var(--ui-ease);
}
.ui-select.open .ui-select-btn-chev { transform: rotate(180deg); }
.ui-select.open .ui-select-btn {
    border-color: var(--ui-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-accent) 16%, transparent);
}
.ui-select > select:focus-visible + .ui-select-btn {
    outline: 2px solid var(--ui-accent);
    outline-offset: 2px;
}
/* Список без рамки — значение в строке свойств: поле в канте выглядит как форма посреди
   справки. Ширина по содержимому: значение стоит справа, а не растянуто на строку. */
.ui-select--bare { width: auto; }
.ui-select--bare .ui-select-btn {
    width: auto;
    min-height: var(--ui-h-sm);
    padding: 0 var(--ui-s-1);
    border-color: transparent;
    background: none;
    color: var(--ui-accent);
    font-size: var(--ui-t-md);
}
.ui-select--bare .ui-select-btn-chev { color: var(--ui-accent); }
.ui-select--bare.open .ui-select-btn {
    border-color: transparent;
    box-shadow: none;
}
/* Список — тот же, что у меню «⋯»: одна поверхность на весь продукт. */
.ui-select-list {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    z-index: var(--ui-z-dropdown);
    max-height: 280px;
    overflow-y: auto;
    padding: var(--ui-s-1);
    border-radius: var(--ui-r-md);
    background: var(--ui-grad-surface);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-lg);
    transform-origin: 0 0;
    animation: ui-pop var(--ui-dur-1) var(--ui-ease);
}
.ui-select:not(.open) .ui-select-list { display: none; }
/* Список раскрылся выше кнопки — значит внизу не хватило места. */
.ui-select-list--up { top: auto; bottom: calc(100% + 6px); transform-origin: 0 100%; }
.ui-select-opt {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-3);
    border: none;
    border-radius: var(--ui-r-sm);
    background: transparent;
    color: var(--ui-text);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
/* Выбранное показывает цвет — галочка была бы вторым способом сказать то же. */
.ui-select-opt[aria-selected="true"] { background: var(--ui-accent-soft); color: var(--ui-accent); }
.ui-select-opt.hl { background: var(--ui-surface-2); }
.ui-select-opt[disabled] { opacity: 0.45; cursor: default; }
@media (hover: hover) {
    .ui-select-opt:hover:not([aria-selected="true"]) { background: var(--ui-surface-2); }
}
@media (prefers-reduced-motion: reduce) {
    .ui-select-list { animation: none; }
    .ui-select-btn-chev { transition: none; }
}

/* Список шторкой — режим для телефона. Выпадашка в 280px там накрывала соседние
   фильтры, а у «Мышц» полторы сотни пунктов: список нужен во весь низ экрана. */
/* Высота по содержимому до потолка: у списка из трёх строк шторка во весь экран
   выглядела ошибкой, а «Мышцы» упираются в потолок и скроллятся внутри. */
/* Шторку выбора открывают из формы, которая сама часто живёт в окне или
   шторке (--ui-z-modal): на слое подложки список оказывался ПОД ними. */
/* Двойной класс: базовое правило .ui-sheet-overlay объявлено ниже по файлу
   и при равной специфичности перебивало бы это. */
.ui-sheet-overlay.ui-select-sheet { z-index: var(--ui-z-popover); }
.ui-select-sheet > .ui-sheet { display: flex; flex-direction: column; max-height: 88dvh; overflow: hidden; }
.ui-select-sheet-head {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    padding: 0 var(--ui-s-2) var(--ui-s-3);
}
.ui-select-sheet-title {
    flex: 1;
    min-width: 0;
    font-size: var(--ui-t-lg);
    font-weight: 800;
    color: var(--ui-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ui-select-sheet-find { flex: 0 0 auto; margin: 0 var(--ui-s-2) var(--ui-s-3); width: auto; }
.ui-select-sheet-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: var(--ui-s-4);
}
/* Строка шторки крупнее строки выпадашки: её выбирают пальцем, а не курсором. */
.ui-select-sheet-list .ui-select-opt { min-height: var(--ui-h-lg); }
.ui-select-sheet-empty {
    padding: var(--ui-s-6) var(--ui-s-3);
    text-align: center;
    font-size: var(--ui-t-sm);
    color: var(--ui-text-3);
}

/* ═══════════════════════════ ВЫБОР ДАТЫ ═══════════════════════════ */

/* Пара к `ui-datepicker.js`. Порядок частей в родном поле даты решает локаль браузера
   (в русском кабинете видели 08/26/2026), внутрь календаря CSS не дотягивается. */
.ui-date { position: relative; display: block; width: 100%; }
.ui-date > input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
}
.ui-date-btn {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    width: 100%;
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-3);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-r-md);
    background: var(--ui-surface);
    color: var(--ui-text);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    line-height: 1;
    text-align: left;
    cursor: pointer;
}
.ui-date-btn-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-date-btn-label--empty { color: var(--ui-text-3); font-weight: 500; }
.ui-date-btn-ico { flex: none; color: var(--ui-text-3); font-size: var(--ui-t-sm); }
.ui-date.open .ui-date-btn {
    border-color: var(--ui-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-accent) 16%, transparent);
}
.ui-date > input:focus-visible + .ui-date-btn,
.ui-date-btn:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }

/* Позиция от окна, не от поля: поле часто стоит в прокручиваемом теле шторки,
   и absolute-попап резался её overflow. Координаты считает ui-datepicker.js. */
/* Слой — popover, а не nested: открытый попап живёт в body, и на уровне
   вложенного окна он уходил ПОД окно, из которого его же и открыли. */
.ui-date-pop {
    position: fixed;
    top: var(--ui-date-pop-y, 0);
    left: var(--ui-date-pop-x, 0);
    z-index: var(--ui-z-popover);
    width: 268px;
    max-width: calc(100vw - var(--ui-s-5) * 2);
    padding: var(--ui-s-2);
    border-radius: var(--ui-r-md);
    background: var(--ui-grad-surface);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-lg);
    transform-origin: 0 0;
    animation: ui-pop var(--ui-dur-1) var(--ui-ease);
}
/* Открытый попап живёт в body (см. ui-datepicker.js), правило от .ui-date
   до него не дотянется — видимостью управляет собственный класс */
.ui-date-pop { display: none; }
.ui-date-pop.open { display: block; }
.ui-date-pop--up { transform-origin: 0 100%; }

.ui-date-head {
    display: flex;
    align-items: center;
    gap: var(--ui-s-1);
    margin-bottom: var(--ui-s-1);
}
.ui-date-title {
    flex: 1;
    text-align: center;
    font-size: var(--ui-t-md);
    font-weight: 700;
    color: var(--ui-text);
    text-transform: capitalize;
}
.ui-date-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}
.ui-date-wd {
    padding: var(--ui-s-1) 0;
    text-align: center;
    font-size: var(--ui-t-2xs);
    font-weight: 700;
    color: var(--ui-text-3);
}
.ui-date-day {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Клетка квадратная и не ниже зоны нажатия: календарь трогают пальцем. */
    aspect-ratio: 1;
    min-height: 32px;
    border: none;
    border-radius: var(--ui-r-sm);
    background: transparent;
    color: var(--ui-text);
    font-family: inherit;
    font-size: var(--ui-t-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}
/* Сегодня — кольцо, выбранное — заливка: два разных сообщения, не один цвет. */
.ui-date-day.today { box-shadow: inset 0 0 0 1.5px var(--ui-line); }
.ui-date-day.selected { background: var(--ui-accent); color: var(--ui-on-accent); }
.ui-date-day[disabled] { opacity: 0.3; cursor: default; }
@media (hover: hover) {
    .ui-date-day:hover:not(.selected):not([disabled]) { background: var(--ui-surface-2); }
}
.ui-date-foot {
    display: flex;
    gap: var(--ui-s-1);
    margin-top: var(--ui-s-1);
    padding-top: var(--ui-s-1);
    border-top: 1px solid var(--ui-line);
}
.ui-date-foot > .ui-btn { flex: 1 1 0; }
@media (prefers-reduced-motion: reduce) {
    .ui-date-pop { animation: none; }
}
/* Синее кольцо фокуса = «твоё действие». Золото зарезервировано за туром. */
/* Кольцо считается от акцента: сырой синий не следовал за перекрашенной темой. */
.ui-input:focus-visible {
    outline: none;
    border-color: var(--ui-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-accent) 18%, transparent);
}
.ui-input--lg { min-height: var(--ui-h-lg); font-size: var(--ui-t-lg); }
/* Многострочное поле: у него своя высота и внутренние отступы сверху-снизу,
   в остальном это то же поле — кант, фон и фокус берутся у .ui-input. */
.ui-input--area {
    min-height: var(--ui-area);
    padding: var(--ui-s-2) var(--ui-s-3);
    line-height: var(--ui-lh);
    resize: vertical;
}
/* Длинный текст: заметка тренера, комментарий к отчёту. */
.ui-input--area-lg { min-height: var(--ui-area-lg); }

/* Чекбокс (автопродление в пейволе и т.п.) */
.ui-check {
    -webkit-appearance: none;
    appearance: none;
    width: 20px;
    height: 20px;
    margin: 0;
    flex: none;
    border: 2px solid var(--ui-ctrl-line);
    border-radius: var(--ui-r-xs);
    background: var(--ui-surface);
    cursor: pointer;
    position: relative;
    box-sizing: border-box;
    transition: background var(--ui-dur-1) var(--ui-ease),
                border-color var(--ui-dur-1) var(--ui-ease);
}
.ui-check:checked { background: var(--ui-accent); border-color: var(--ui-accent); }
.ui-check:checked::after {
    content: '';
    position: absolute;
    left: 5px;
    top: 1px;
    width: 5px;
    height: 10px;
    border: solid var(--ui-on-accent);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* ═══════════════════════════ КАРТОЧКА ═══════════════════════════ */

/* Поднятая поверхность: без border, волосок + слои света */
.ui-card {
    background: var(--ui-grad-surface);
    border-radius: var(--ui-r-lg);
    padding: var(--ui-s-4);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-sm);
}

/* ═══════════════════════════ МОДАЛКА ═══════════════════════════ */

.ui-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: var(--ui-dim);
    z-index: var(--ui-z-overlay);
    align-items: center;
    justify-content: center;
    padding: var(--ui-s-5) var(--ui-s-4);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.ui-overlay.open { display: flex; }
/* Плотное затемнение — просмотр фотографии во весь экран */
.ui-overlay--dense { background: rgba(0, 0, 0, 0.85); }

/* Пока открыто окно, страница под ним не ездит: две полосы прокрутки рядом сбивают.
   overscroll-behavior — чтобы прокрутка не «протекала» на страницу под окном. */
body:has(.ui-overlay.open),
body:has(.ui-sheet-overlay.open),
/* .kb-overlay — шторки кабинета, ещё не переехавшие на оболочки слоя. Без них
   страница под шторкой «План КБЖУ» ездила пальцем вместе со шторкой. */
body:has(.kb-overlay.open) { overflow: hidden; }
.ui-overlay { overscroll-behavior: contain; }

/* ── Страховка для 14 окон, ещё не переехавших на .ui-modal: потолок высоты у них
   в vh, а vh на телефоне считается по экрану без панелей браузера. Новые не добавлять. ── */
.cab-lb > *,
.dr-fs > *, .ci-logmeas-overlay > *,
.saving-overlay > *, .overlay > *,
.ws-ov > *,
.exr-rest > *,
.meal-modal > *,
.pw-overlay:not(.pw-inline) > *,
.rp-sheet > *,
.srx-wrap > *,
.wp-media-overlay > *, .wp-swap-overlay > * {
    max-height: calc(100dvh - var(--ui-s-4) * 2);
    overflow: auto;
    scrollbar-gutter: stable;
}
/* Картинку и видео во весь экран не прокручивают — их вписывают. */
.cab-lb > :is(img, video), .dr-fs > :is(img, video),
.wp-media-overlay > :is(img, video) {
    overflow: visible;
    object-fit: contain;
}

/* Положение окна на полотне. Модификатор действует только на широком экране: на
   телефоне положение решает узкая форма ниже, а не пожелание конкретного экрана. */
@media (min-width: 560px) {
    .ui-overlay--top {
        align-items: flex-start;
        padding-top: var(--ui-s-6);
    }
}
/* «Шторки из модалки» здесь нет намеренно: для неё есть `.ui-sheet-overlay` + `.ui-sheet`.
   Окно остаётся окном на любой ширине — на узком меняются отступ и потолок высоты. */
@media (max-width: 559px) {
    .ui-overlay {
        padding: var(--ui-s-4);
        padding-bottom: calc(var(--ui-s-4) + env(safe-area-inset-bottom, 0px));
    }
    .ui-overlay > .ui-modal {
        max-width: none;
        max-height: calc(100dvh - var(--ui-s-4) * 2 - env(safe-area-inset-bottom, 0px));
    }
}

.ui-modal {
    background: var(--ui-grad-surface);
    border-radius: var(--ui-r-lg);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-lg);
    width: 100%;
    max-width: 400px;
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 48px);
    max-height: calc(100dvh - 48px);
    overflow: hidden;
    color: var(--ui-text);
    z-index: var(--ui-z-modal);
    /* Окно вырастает из точки, а не возникает кадром */
    animation: ui-pop var(--ui-dur-2) var(--ui-ease);
}
.ui-modal--md { max-width: 520px; }
.ui-modal--lg { max-width: 640px; }

.ui-modal-head {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    padding: var(--ui-s-4) var(--ui-s-4) 0;
    font-size: var(--ui-t-lg);
    font-weight: 700;
}
.ui-modal-head > :first-child { flex: 1; min-width: 0; }
/* Заголовок окна исторически размечают <h3>: здесь гасим браузерный дефолт, кегль держит
   .ui-modal-head. :where() намеренно — нулевой вес, правило экрана перекроет. */
.ui-modal-head :where(h1, h2, h3, h4) {
    margin: 0;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
    color: inherit;
}
.ui-modal-close {
    /* Нижняя ступень шкалы высот — она же минимальная зона нажатия. */
    width: var(--ui-h-sm);
    height: var(--ui-h-sm);
    border: none;
    background: transparent;
    color: var(--ui-text-2);
    border-radius: var(--ui-r-sm);
    cursor: pointer;
    font-size: var(--ui-t-md);
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    transition: color var(--ui-dur-1) var(--ui-ease);
}
@media (hover: hover) {
    .ui-modal-close:hover { background: var(--ui-surface-2); color: var(--ui-text); }
}

.ui-modal-body {
    padding: var(--ui-s-2) var(--ui-s-4) var(--ui-s-4);
    font-size: var(--ui-t-md);
    line-height: var(--ui-lh);
    color: var(--ui-text-2);
    overflow-y: auto;
    /* Место под полосу прокрутки занято всегда: иначе с приходом данных полоса
       появляется и содержимое сдвигается на её ширину — это и есть «окно дёрнулось». */
    scrollbar-gutter: stable;
}
.ui-modal-body p { margin: 0 0 var(--ui-s-2); }
.ui-modal-body p:last-child { margin-bottom: 0; }
/* Без боковых полей — списки выбора кладут строки до самых краёв окна. */
.ui-modal-body--flush { padding: var(--ui-s-2) 0; }

/* Футер адаптивный: на тач-ширине пара делит ширину поровну, на десктопе кнопки по
   ширине текста прижаты вправо. Ось — ширина экрана, не платформа. */
.ui-modal-foot {
    display: flex;
    gap: var(--ui-s-2);
    padding: 0 var(--ui-s-4) var(--ui-s-4);
}
/* Конфирм без тела (короткий вопрос заголовком): вернуть воздух между
   заголовком и кнопками. Десктопное правило ниже перекроет отбивкой. */
.ui-modal-head + .ui-modal-foot { padding-top: var(--ui-s-4); }
.ui-modal-foot > .ui-btn { flex: 1 1 0; min-width: 0; min-height: var(--ui-h-lg); }
@media (min-width: 560px) {
    .ui-modal-foot { justify-content: flex-end; border-top: 1px solid var(--ui-line); padding-top: var(--ui-s-3); }
    .ui-modal-foot > .ui-btn { flex: 0 0 auto; min-height: var(--ui-h-md); }
}

/* ═══════════════════════════ ШТОРКА ═══════════════════════════ */

/* Шторка гасится прозрачностью, а не display: none, — иначе появляется с анимацией, а
   исчезает кадром. visibility снимается с задержкой, чтобы не перехватывать нажатия. */
.ui-sheet-overlay {
    display: flex;
    position: fixed;
    inset: 0;
    background: var(--ui-dim);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    z-index: var(--ui-z-overlay);
    align-items: flex-end;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--ui-dur-2) var(--ui-ease),
                visibility 0s linear var(--ui-dur-2);
}
.ui-sheet-overlay.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
}

.ui-sheet {
    background: var(--ui-grad-surface);
    border-radius: var(--ui-r-lg) var(--ui-r-lg) 0 0;
    box-shadow: var(--ui-ring), var(--ui-edge), 0 -16px 44px rgba(9, 13, 20, 0.30);
    width: 100%;
    max-width: 560px;
    max-height: 92vh;
    max-height: 92dvh;
    overflow-y: auto;
    scrollbar-gutter: stable;
    -webkit-overflow-scrolling: touch;
    color: var(--ui-text);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    transform: translateY(100%);
    transition: transform var(--ui-dur-3) var(--ui-ease);
}
.ui-sheet-overlay.open > .ui-sheet { transform: translateY(0); }
/* Шторка со списком-меню: строки не упираются в края экрана. Модификатором, а не всем
   шторкам: у списков подбора подсветка строки идёт от края до края. */
.ui-sheet--inset { padding-left: var(--ui-s-3); padding-right: var(--ui-s-3); }
/* Отбивка снизу — часть ручки: без неё содержимое шторки липнет к полоске. */
.ui-sheet-handle { display: flex; justify-content: center; padding: 10px 0 var(--ui-s-3); }
.ui-sheet-handle::after {
    content: '';
    width: 40px;
    height: 4px;
    border-radius: var(--ui-r-pill);
    background: var(--ui-line);
}

/* ═══════════════════════ ЗНАЧОК В ПЛИТКЕ ═══════════════════════ */

/* Иконка в цветной подложке: строки списков, плитки действий, шапки шторок,
   пункты шторки «+». Было восемь копий (cab-attn-ico, cab-sheet-ico,
   cab-macro-ico, cab-mini-ico, cab-prog-ico, cab-stat-ico, cab-pick-ico,
   db-q-ic) с восемью размерами от 30 до 48 и фиолетовым числом в каждой.
   Размер — из шкалы контролов, оттенок — по смыслу. */
.ui-icon-tile {
    --ui-tile: var(--ui-h-md);
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--ui-tile);
    height: var(--ui-tile);
    border-radius: var(--ui-r-md);
    font-size: var(--ui-t-md);
    background: var(--ui-accent-soft);
    color: var(--ui-accent);
}
.ui-icon-tile--xs { --ui-tile: 28px; font-size: var(--ui-t-sm); }
.ui-icon-tile--sm { --ui-tile: var(--ui-h-sm); font-size: var(--ui-t-sm); }
.ui-icon-tile--lg { --ui-tile: var(--ui-h-lg); font-size: var(--ui-t-lg); }

.ui-icon-tile--success { background: var(--ui-success-soft); color: var(--ui-success); }
.ui-icon-tile--warning { background: var(--ui-warning-soft); color: var(--ui-warning); }
.ui-icon-tile--danger { background: var(--ui-danger-soft); color: var(--ui-danger); }
.ui-icon-tile--info { background: color-mix(in srgb, var(--ui-info) 16%, transparent); color: var(--ui-info); }
/* Приглушённый — «есть, но сейчас неважно»: день без занятия, пустая графа. */
.ui-icon-tile--muted { background: var(--ui-surface-2); color: var(--ui-text-3); }

/* Заливка вместо подложки — плитка ведёт в раздел, а не помечает состояние.
   Вес [class]: оттенки объявлены выше и при равном весе выигрывали бы
   порядком. Значок берёт парный к заливке токен, а не белый числом: в тёмной
   теме заливка светлеет, и белая иконка на ней проваливается. */
[class].ui-icon-tile--solid { background: var(--ui-btn-p-bg); color: var(--ui-on-accent); }
[class].ui-icon-tile--solid.ui-icon-tile--success { background: var(--ui-tile-success); color: var(--ui-on-success); }
[class].ui-icon-tile--solid.ui-icon-tile--warning { background: var(--ui-tile-warning); color: var(--ui-on-warning); }
[class].ui-icon-tile--solid.ui-icon-tile--danger { background: var(--ui-btn-d-bg); color: var(--ui-on-danger); }
[class].ui-icon-tile--solid.ui-icon-tile--info { background: var(--ui-tile-info); color: var(--ui-on-info); }

/* ═══════════════════════ СТРОКА СПИСКА ═══════════════════════ */

/* «Значок · текст · хвост» — самая частая строка продукта: пункты шторки
   «Ещё», строки-виджеты главной, заголовки категорий, ссылки подвала.
   Ставится и на <a>, и на <button>: собственный вид обоих снят, поэтому
   строка выглядит одинаково независимо от того, чем она оказалась. */
.ui-lineitem {
    display: flex;
    align-items: center;
    gap: var(--ui-s-3);
    width: 100%;
    min-height: var(--ui-h-sm);
    padding: var(--ui-s-2) 0;
    border: 0;
    background: none;
    color: var(--ui-text);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.ui-lineitem:active { opacity: 0.7; }

/* Строка сама себе виджет, а не пункт списка: текст просторнее. Размер значка
   задаётся на нём самом (.ui-icon-tile--sm / --md), а не отсюда. */
.ui-lineitem--lg > .ui-lineitem-body > .ui-lineitem-title { font-size: var(--ui-t-lg); }

.ui-lineitem-body { flex: 1 1 auto; min-width: 0; }
.ui-lineitem-title {
    display: block;
    font-size: var(--ui-t-md);
    font-weight: 700;
    line-height: var(--ui-lh-tight);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ui-lineitem-sub {
    display: block;
    margin-top: 2px;
    font-size: var(--ui-t-xs);
    font-weight: 600;
    line-height: var(--ui-lh-tight);
    color: var(--ui-text-2);
}
.ui-lineitem-sub--warning { color: var(--ui-warning); }

/* Хвост: счётчик, сумма, шеврон — по одному или вместе. */
.ui-lineitem-tail {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
}
.ui-lineitem-count {
    min-width: 22px;
    padding: 2px var(--ui-s-2);
    border-radius: var(--ui-r-pill);
    background: var(--ui-surface-2);
    color: var(--ui-text);
    font-size: var(--ui-t-sm);
    font-weight: 800;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.ui-lineitem-chev {
    flex: 0 0 auto;
    font-size: var(--ui-t-xs);
    color: var(--ui-text-3);
    transition: transform var(--ui-dur-1) var(--ui-ease);
}
/* Раскрытая строка-гармошка: шеврон вниз — стрелка вверх. */
.ui-lineitem.open .ui-lineitem-chev { transform: rotate(180deg); }

/* Карточка-цифра: крупное число и подпись. Ими выложены сводки всех экранов. */
.ui-stat {
    --ui-stat-v: var(--ui-t-2xl);
    display: flex;
    flex-direction: column;
    gap: var(--ui-s-1);
    min-width: 0;
}
.ui-stat-value {
    font-size: var(--ui-stat-v);
    font-weight: 800;
    line-height: var(--ui-lh-tight);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-stat-value small { font-size: var(--ui-t-2xs); font-weight: 700; color: var(--ui-text-3); margin-left: 2px; }
.ui-stat-label { font-size: var(--ui-t-sm); line-height: var(--ui-lh-tight); color: var(--ui-text-2); }

/* Две ступени помельче: сводка в шапке карточки клиента (sm) и числа недели
   на главной (md). Подпись у обеих одна — мелкая и разряженная, иначе на
   четырёх колонках она перевешивает само число. */
.ui-stat--sm { --ui-stat-v: var(--ui-t-lg); }
.ui-stat--md { --ui-stat-v: var(--ui-t-xl); }
.ui-stat--sm > .ui-stat-label,
.ui-stat--md > .ui-stat-label {
    font-size: var(--ui-t-2xs);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--ui-text-3);
}
/* Подпись не режем многоточием: обрезанное «Целевой ве…» читается хуже переноса. */
.ui-stat--center { align-items: center; text-align: center; }

/* Ряд чисел. Колонок по умолчанию четыре, иначе — через --ui-stats-cols. */
.ui-stats {
    display: grid;
    grid-template-columns: repeat(var(--ui-stats-cols, 4), minmax(0, 1fr));
    gap: var(--ui-s-2);
}
/* Сплошная лента внутри карточки: числа разделены линиями, а не зазором. */
.ui-stats--divided {
    gap: 0;
    background: var(--ui-grad-surface);
    border-radius: var(--ui-r-lg);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-sm);
    overflow: hidden;
}
.ui-stats--divided > .ui-stat { padding: var(--ui-s-3) var(--ui-s-2); }
.ui-stats--divided > .ui-stat + .ui-stat { box-shadow: inset 1px 0 0 var(--ui-line); }
/* На узком экране ленте отдаём поля плиток: числа короткие и в строку влезают,
   а вот подписи под ними без этого переносятся. */
@media (max-width: 380px) {
    .ui-stats--divided > .ui-stat { padding-left: var(--ui-s-1); padding-right: var(--ui-s-1); }
}

/* Число-кнопка: ведёт в свой раздел. Такие числа уже вели, но выглядели как
   текст — что по ним можно нажать, было видно только на ощупь. Вид взят у
   соседних плиток действий: рамка, поверхность, отклик на нажатие. */
.ui-stat--action {
    padding: var(--ui-s-2) var(--ui-s-1);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-r-md);
    background: var(--ui-surface);
    color: inherit;
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--ui-dur-1) var(--ui-ease),
                border-color var(--ui-dur-1) var(--ui-ease);
}
.ui-stat--action:active { background: var(--ui-surface-2); }
@media (hover: hover) {
    .ui-stat--action:hover { border-color: var(--ui-accent); }
}

/* ═══════════════════════ СЕКЦИЯ И ЗАГОЛОВОК ═══════════════════════ */

/* Шапка блока: заголовок слева, действие справа. Заменяет cab-section-head,
   section-title, web-card-header — 175 вхождений. */
.ui-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ui-s-3);
    margin-bottom: var(--ui-s-3);
}
.ui-section-title {
    font-size: var(--ui-t-lg);
    font-weight: 700;
    letter-spacing: -0.01em;
    min-width: 0;
}

/* ═══════════════════════ МЕТА: ПОДПИСЬ И ЗНАЧЕНИЕ ═══════════════════════ */

/* Пара «что» / «сколько» — 137 вхождений (meta-label, meta-value, meta-item).
   Подпись сверху мелкая, значение крупное: так читается на скан, а не построчно. */
.ui-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* Типографика — в общем правиле .ui-caption выше. */
.ui-meta-value {
    font-size: var(--ui-t-lg);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
/* Пара в строку: подпись слева, значение справа. Для значений-слов колонка давала по
   две строки на пару и растягивала карточку, хотя их читают построчно. */
.ui-meta--inline {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--ui-s-3);
}
.ui-meta--inline .ui-meta-value {
    font-size: var(--ui-t-md);
    font-weight: 600;
    text-align: right;
}

/* ═══════════════════════════ БЕЙДЖ / СТАТУС ═══════════════════════════ */

/* 275 вхождений: status-badge, web-nav-item-badge, nx-badge. Цвет несёт смысл,
   поэтому варианты названы по смыслу, а не по цвету. */
.ui-status {
    display: inline-flex;
    align-items: center;
    gap: var(--ui-s-1);
    padding: 3px var(--ui-s-2);
    border-radius: var(--ui-r-pill);
    /* 12px, а не 11px: плашку читают вместо кнопки, которая тут стояла бы, а на 11px
       серым по серому она пропадала. 11px остаётся у таб-бара. */
    font-size: var(--ui-t-xs);
    font-weight: 700;
    line-height: 1.4;
    white-space: nowrap;
    background: var(--ui-surface-2);
    color: var(--ui-text-2);
}
.ui-status--ok { background: color-mix(in srgb, var(--ui-success) 14%, transparent); color: var(--ui-success); }
.ui-status--warn { background: color-mix(in srgb, var(--ui-warning) 16%, transparent); color: var(--ui-warning); }
.ui-status--danger { background: color-mix(in srgb, var(--ui-danger) 14%, transparent); color: var(--ui-danger); }
.ui-status--accent { background: color-mix(in srgb, var(--ui-accent) 14%, transparent); color: var(--ui-accent); }
/* Плашка-выделение («★ Ближайшая»): единственная сплошная среди приглушённых.
   Не смысл-цвет — не ставить туда, где статус означает ошибку или успех. */
.ui-status--fire {
    background: linear-gradient(135deg, var(--ui-fire), var(--ui-fire-2));
    color: var(--ui-on-accent);
}
/* Счётчик у пункта меню: круглый, всегда читаемый */
.ui-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--ui-r-pill);
    background: var(--ui-accent);
    color: var(--ui-on-accent);
    font-size: var(--ui-t-2xs);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════ ЧИП ═══════════════════════════ */

/* 184 вхождения: hw-chip, chip, exercise-tag, cab-tag, filter-chip.
   Чип бывает и меткой, и переключателем — второй режим ниже. */
.ui-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--ui-s-1);
    padding: 0 var(--ui-s-3);
    min-height: var(--ui-h-sm);
    border-radius: var(--ui-r-pill);
    background: var(--ui-surface-2);
    color: var(--ui-text-2);
    font-size: var(--ui-t-sm);
    font-weight: 600;
    white-space: nowrap;
    border: none;
    font-family: inherit;
}
/* Приглушённая фишка — второстепенный вариант подстановки рядом с основным. */
.ui-chip--dim { background: var(--ui-a-03); color: var(--ui-text-3); }
.ui-chip--action { cursor: pointer; transition: background var(--ui-dur-1) var(--ui-ease); }
.ui-chip--action.selected,
.ui-chip--action[aria-pressed="true"] {
    background: var(--ui-accent);
    color: var(--ui-on-accent);
}
/* Мягкий выбор — там, где выбрано сразу несколько чипов подряд: сплошной акцент
   тремя пятнами рядом кричит, а фильтр — не главное действие экрана. */
.ui-chip--soft.selected,
.ui-chip--soft[aria-pressed="true"] {
    background: var(--ui-accent-soft);
    color: var(--ui-accent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-accent) 30%, transparent);
}
/* Тот же мягкий выбор, но фиолетовый — внутри ИИ-поверхностей, где знак «сделано
   ИИ» держит свою пару цветов (см. .ui-btn--ai). Вне их не ставить. */
.ui-chip--ai.selected,
.ui-chip--ai[aria-pressed="true"] {
    background: color-mix(in srgb, #6B5CFF 14%, transparent);
    color: #8B6DFF;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #6B5CFF 32%, transparent);
}
@media (hover: hover) {
    .ui-chip--action:hover:not(.selected) { background: var(--ui-line); }
}
/* ═══════════════════════════ ВКЛАДКИ ═══════════════════════════ */

/* 96 вхождений. Сегмент-переключатель: вдавленный трек + поднятая активная. Трек берёт
   фон страницы — он темнее любой поверхности в обеих темах, а тени на тёмном гаснут. */
.ui-tabs {
    display: flex;
    gap: var(--ui-s-1);
    padding: var(--ui-s-1);
    background: var(--ui-bg);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-r-md);
    overflow-x: auto;
    scrollbar-width: none;
}
.ui-tabs::-webkit-scrollbar { display: none; }
.ui-tab {
    /* inline-flex, а не текст в кнопке: вкладка с иконкой должна ставить её
       вровень с подписью, а не по базовой линии шрифта. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ui-s-1);
    flex: 1 1 auto;
    min-width: 0;
    min-height: var(--ui-h-sm);
    padding: 0 var(--ui-s-3);
    border: none;
    border-radius: var(--ui-r-sm);
    background: transparent;
    color: var(--ui-text-2);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--ui-dur-1) var(--ui-ease), color var(--ui-dur-1) var(--ui-ease);
}
/* Ховер невыбранной вкладки — на мыши. :not(...) вместо расчёта на порядок правил:
   у выбранной вкладки своя заливка, и подменять её на наведении нельзя. */
@media (hover: hover) {
    .ui-tab:hover:not(.active):not([aria-selected="true"]) {
        background: var(--ui-surface-2);
        color: var(--ui-text);
    }
}

/* Число рядом с подписью вкладки: «Планы 2». Приглушено, чтобы читалось как уточнение
   к слову, а не как второе слово. */
.ui-tab-count {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    opacity: 0.55;
}

/* Переключатель, который обязан поместиться целиком: у фильтра из трёх состояний уехавшая
   вкладка читается как обрезанный текст, а не как «потяни вбок». */
.ui-tabs--fit {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    overflow-x: visible;
}
.ui-tabs--fit .ui-tab {
    padding: 0 var(--ui-s-2);
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
    line-height: var(--ui-h-sm);
}
/* На опорных 360 px три вкладки обрезали подпись на последней букве — поля по 4 отдают
   недостающее. Кегль не трогаем; многоточие остаётся страховкой для длинных подписей. */
@media (max-width: 400px) {
    .ui-tabs--fit .ui-tab { font-size: var(--ui-t-xs); padding: 0 var(--ui-s-1); }
}
/* Первая вкладка меньше остальных, но не по контенту: сброс «Все» не должен есть треть
   строки у полноценных условий, но «по контенту» превращал её в точку — палец промахивался. */
.ui-tabs--fit.ui-tabs--lead { display: flex; }
.ui-tabs--fit.ui-tabs--lead > .ui-tab { flex: 2 1 0; min-width: 0; }
.ui-tabs--fit.ui-tabs--lead > .ui-tab:first-child { flex: 1 1 0; }

/* Переключатель в несколько рядов — когда разделов больше, чем помещается в строку.
   Колонок три: при четырёх шесть вкладок дают ряд 4+2 с пустой половиной второго. */
.ui-tabs--grid {
    display: grid;
    grid-template-columns: repeat(var(--ui-tabs-cols, 3), minmax(0, 1fr));
    overflow-x: visible;
}
@media (min-width: 769px) {
    /* На широком экране всё помещается в один ряд. */
    .ui-tabs--grid { --ui-tabs-cols: 6; }
}

/* Вкладки с иконками на телефоне: иконка над подписью, иначе пара не влезает в колонку
   ~90px и ряд становится двухэтажным вразнобой. Модификатор, а не общее правило. */
@media (max-width: 480px) {
    .ui-tabs--stack .ui-tab {
        flex-direction: column;
        gap: 2px;
        min-height: var(--ui-h-lg);
        padding: 0 var(--ui-s-2);
        font-size: var(--ui-t-2xs);
    }
}
.ui-tab:disabled { opacity: 0.35; cursor: default; }
.ui-tab.active,
.ui-tab[aria-selected="true"] {
    background: var(--ui-surface);
    color: var(--ui-text);
    box-shadow: var(--ui-ring), var(--ui-shadow-sm);
}
/* Вкладки, где выбор — главное действие экрана, а не навигация по разделу:
   выбранная залита акцентом. Ставить точечно, иначе акцент перестаёт значить. */
.ui-tabs--accent > .ui-tab.active,
.ui-tabs--accent > .ui-tab[aria-selected="true"] {
    background: var(--ui-accent);
    color: var(--ui-on-accent);
    box-shadow: none;
}
.ui-tabs--accent > .ui-tab.active .ui-tab-count,
.ui-tabs--accent > .ui-tab[aria-selected="true"] .ui-tab-count { opacity: 0.8; }

/* ═══════════════════════════ ТАБ-БАР ═══════════════════════════ */

/* Нижняя навигация мини-аппа и мобильного кабинета (40 вхождений). */
.ui-tabbar {
    display: flex;
    align-items: stretch;
    background: var(--ui-surface);
    box-shadow: 0 -1px 0 var(--ui-line);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    z-index: var(--ui-z-sticky);
}
.ui-tabbar-item {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: var(--ui-s-2) var(--ui-s-1);
    color: var(--ui-text-2);
    font-size: var(--ui-t-2xs);
    font-weight: 600;
    text-decoration: none;
    background: none;
    border: none;
    font-family: inherit;
    cursor: pointer;
}
.ui-tabbar-item.active { color: var(--ui-accent); }
.ui-tabbar-item i { font-size: var(--ui-t-lg); }

/* ═══════════════════════════ ТАБЛИЦА ═══════════════════════════ */

.ui-table-wrap {
    overflow-x: auto;
    border-radius: var(--ui-r-lg);
    background: var(--ui-surface);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-sm);
}
.ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-t-md); }
.ui-table th {
    text-align: left;
    padding: var(--ui-s-3) var(--ui-s-4);
    white-space: nowrap;
    border-bottom: 1px solid var(--ui-line);
}
.ui-table td { padding: var(--ui-s-3) var(--ui-s-4); border-bottom: 1px solid var(--ui-line); }
.ui-table tr:last-child td { border-bottom: none; }
.ui-table--clickable tbody tr { cursor: pointer; }
@media (hover: hover) {
    .ui-table--clickable tbody tr:hover { background: var(--ui-surface-2); }
}

/* ═══════════════════════════ МЕДИА-КАРУСЕЛЬ ═══════════════════════════ */

/* Несколько роликов у одного упражнения. Листание обычной прокруткой со «щелчком»
   (scroll-snap); счётчик в углу, при одном файле он не появляется. */
.ui-media { position: relative; }
.ui-media-track {
    display: flex;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    border-radius: var(--ui-r-md);
}
.ui-media-track::-webkit-scrollbar { display: none; }
.ui-media-slide {
    flex: 0 0 100%;
    min-width: 0;
    scroll-snap-align: center;
    scroll-snap-stop: always;
}
.ui-media-slide > :is(img, video) {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.ui-media-count {
    position: absolute;
    top: var(--ui-s-2);
    right: var(--ui-s-2);
    padding: 3px var(--ui-s-2);
    border-radius: var(--ui-r-pill);
    /* Тёмная плашка своим цветом, а не токеном поверхности: она лежит на
       кадре, а не на фоне страницы, и должна читаться на любом кадре. */
    background: rgba(10, 14, 21, 0.72);
    color: #fff;
    font-size: var(--ui-t-2xs);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}
.ui-media[data-count="1"] .ui-media-count,
.ui-media:not([data-count]) .ui-media-count { display: none; }
/* Стрелки — только там, где листают мышью. */
.ui-media-nav { display: none; }
@media (hover: hover) and (min-width: 560px) {
    .ui-media[data-count]:not([data-count="1"]) .ui-media-nav {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        display: flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        border: none;
        border-radius: var(--ui-r-pill);
        background: rgba(10, 14, 21, 0.6);
        color: #fff;
        font-size: var(--ui-t-sm);
        cursor: pointer;
    }
    .ui-media-nav--prev { left: var(--ui-s-2); }
    .ui-media-nav--next { right: var(--ui-s-2); }
    .ui-media-nav[disabled] { opacity: 0.35; cursor: default; }
}

/* ═══════════════════════════ СТРАНИЦЫ СПИСКА ═══════════════════════════ */

/* Ряд номеров под длинным списком; разметку рисует window.uiPager (ui-runtime.js).
   Перенос разрешён: девять номеров со стрелками на телефоне в строку не помещаются. */
.ui-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--ui-s-1);
    margin-top: var(--ui-s-5);
}
.ui-pager[hidden] { display: none; }
.ui-pager-gap { padding: 0 4px; color: var(--ui-text-3); }
/* Номер — квадратный: ряд из цифр разной ширины выглядит рваным. */
.ui-pager > .ui-btn:not(.ui-btn--icon) { min-width: var(--ui-h-sm); padding: 0 var(--ui-s-2); }

/* ═══════════════════════════ ПУСТОЕ СОСТОЯНИЕ ═══════════════════════════ */

/* 68 вхождений. Пустой экран — это не «ничего нет», а следующий шаг. */
.ui-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--ui-s-2);
    padding: var(--ui-s-6) var(--ui-s-4);
    color: var(--ui-text-2);
}
.ui-empty-icon { font-size: 32px; color: var(--ui-text-3); margin-bottom: var(--ui-s-1); }
.ui-empty-title { font-size: var(--ui-t-lg); font-weight: 700; color: var(--ui-text); }
.ui-empty-text { font-size: var(--ui-t-md); max-width: 42ch; }
/* Пустой список, в который перетаскивают: пунктир — это аффорданс drop-зоны */
.ui-empty--dropzone {
    border: 2px dashed var(--ui-line);
    border-radius: var(--ui-r-lg);
}

/* ═══════════════════════════ АВАТАР ═══════════════════════════ */

.ui-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: none;
    border-radius: var(--ui-r-pill);
    background: var(--ui-surface-2);
    color: var(--ui-text-2);
    font-size: var(--ui-t-sm);
    font-weight: 700;
    overflow: hidden;
    text-transform: uppercase;
}
.ui-avatar img { width: 100%; height: 100%; object-fit: cover; }
.ui-avatar--sm { width: 28px; height: 28px; font-size: var(--ui-t-2xs); }
.ui-avatar--lg { width: 56px; height: 56px; font-size: var(--ui-t-lg); }
/* Иконка в цветном кружке — частый приём продукта: смысл читается цветом,
   а не подписью. Заливка полупрозрачная, чтобы работать на любой поверхности. */
.ui-avatar--soft-accent { background: color-mix(in srgb, var(--ui-accent) 14%, transparent); color: var(--ui-accent); }
.ui-avatar--soft-success { background: color-mix(in srgb, var(--ui-success) 14%, transparent); color: var(--ui-success); }
.ui-avatar--soft-warning { background: color-mix(in srgb, var(--ui-warning) 16%, transparent); color: var(--ui-warning); }
.ui-avatar--square { border-radius: var(--ui-r-md); }

/* ═══════════════════════════ ПРОГРЕСС ═══════════════════════════ */

/* Значение приходит переменной: style="--ui-progress: 42%" — единственный
   законный инлайн (динамика, а не стиль). */
.ui-progress {
    height: 6px;
    border-radius: var(--ui-r-pill);
    background: var(--ui-surface-2);
    overflow: hidden;
}
.ui-progress::after {
    content: '';
    display: block;
    height: 100%;
    width: var(--ui-progress, 0%);
    border-radius: inherit;
    background: var(--ui-accent);
    transition: width var(--ui-dur-3) var(--ui-ease);
}
.ui-progress--success::after { background: var(--ui-success); }
.ui-progress--warning::after { background: var(--ui-warning); }
.ui-progress--danger::after { background: var(--ui-danger); }
/* Тонкая полоса — там, где прогресс идёт строкой внутри карточки. */
.ui-progress--sm { height: 4px; }

/* ═══════════════════════════ ПАГИНАЦИЯ ═══════════════════════════ */

.ui-pagination { display: flex; align-items: center; justify-content: center; gap: var(--ui-s-2); flex-wrap: wrap; }
.ui-pagination-info { font-size: var(--ui-t-sm); color: var(--ui-text-2); font-variant-numeric: tabular-nums; }
/* Кнопка страницы: квадратная, чтобы номера в ряду стояли ровной сеткой */
.ui-pagination-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--ui-h-md);
    min-height: var(--ui-h-md);
    padding: 0 var(--ui-s-2);
    border: none;
    border-radius: var(--ui-r-md);
    background: var(--ui-surface-raised);
    box-shadow: var(--ui-ring);
    color: var(--ui-text);
    font-family: inherit;
    font-size: var(--ui-t-md);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}
.ui-pagination-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.ui-pagination-btn.active {
    background: var(--ui-accent);
    box-shadow: none;
    color: var(--ui-on-accent);
}

/* ═══════════════════════════ МЕНЮ (выпадающее) ═══════════════════════════ */

.ui-menu {
    background: var(--ui-grad-surface);
    border-radius: var(--ui-r-md);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-md);
    padding: var(--ui-s-1);
    min-width: 180px;
    max-height: 320px;
    overflow-y: auto;
    z-index: var(--ui-z-dropdown);
    animation: ui-pop var(--ui-dur-1) var(--ui-ease);
}
.ui-menu-item {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    width: 100%;
    padding: var(--ui-s-2) var(--ui-s-3);
    border: none;
    background: none;
    border-radius: var(--ui-r-sm);
    font-family: inherit;
    font-size: var(--ui-t-md);
    /* button не наследует межстрочный от UA — на пункте в две строки видно */
    line-height: var(--ui-lh);
    color: var(--ui-text);
    text-align: left;
    cursor: pointer;
    text-decoration: none;
}
.ui-menu-item.selected { background: color-mix(in srgb, var(--ui-accent) 12%, transparent); color: var(--ui-accent); }
.ui-menu-item--danger { color: var(--ui-danger); }
@media (hover: hover) {
    .ui-menu-item:hover { background: var(--ui-surface-2); }
}
.ui-menu-sep { height: 1px; background: var(--ui-line); margin: var(--ui-s-1) 0; }

/* ═══════════════════════════ ТОСТ ═══════════════════════════ */

/* 149 вхождений. Тост поверх окон: его видно всегда. */
.ui-toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--ui-s-5) + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%) translateY(8px);
    max-width: min(420px, calc(100vw - 32px));
    padding: var(--ui-s-3) var(--ui-s-4);
    border-radius: var(--ui-r-md);
    background: var(--ui-text);
    color: var(--ui-surface);
    font-size: var(--ui-t-md);
    font-weight: 600;
    text-align: center;
    box-shadow: var(--ui-shadow-lg);
    z-index: var(--ui-z-toast);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ui-dur-2) var(--ui-ease), transform var(--ui-dur-2) var(--ui-ease);
}
.ui-toast.open { opacity: 1; transform: translateX(-50%) translateY(0); }
.ui-toast--success { background: var(--ui-success); color: var(--ui-on-success); }
.ui-toast--danger { background: var(--ui-danger); color: var(--ui-on-danger); }
/* По центру экрана — когда подтверждение важно заметить сразу: внизу его легко
   пропустить, особенно поверх открытого окна. */
.ui-toast--center {
    bottom: auto;
    top: 50%;
    max-width: min(360px, calc(100vw - 32px));
    transform: translate(-50%, calc(-50% + 10px));
    padding: var(--ui-s-4) var(--ui-s-6);
    font-size: var(--ui-t-lg);
}
.ui-toast--center.open { transform: translate(-50%, -50%); }

/* ═══════════════════════════ СПИННЕР ═══════════════════════════ */

/* 238 вхождений вместе с loading-container. Крутилка — для действия
   («сохраняю»), для загрузки экрана правильнее скелетон. */
.ui-spinner {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 2px solid color-mix(in srgb, var(--ui-text) 15%, transparent);
    border-top-color: var(--ui-accent);
    border-radius: var(--ui-r-pill);
    animation: ui-spin 0.7s linear infinite;
}
.ui-spinner--sm { width: 14px; height: 14px; border-width: 2px; }
.ui-spinner--lg { width: 32px; height: 32px; border-width: 3px; }
@keyframes ui-spin { to { transform: rotate(360deg); } }

/* Ожидание внутри блока: одна вращалка по центру, без подписи. Запас высоты
   (--ui-loading-h) держит рамку неподвижной, пока данные едут. */
.ui-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--ui-s-3);
    min-height: var(--ui-loading-h, 180px);
    padding: var(--ui-s-6) var(--ui-s-4);
    color: var(--ui-text-2);
    font-size: var(--ui-t-md);
}
/* Внутри строки таблицы или узкой карточки полный запас высоты избыточен. */
.ui-loading--sm { --ui-loading-h: 88px; }
.ui-loading--lg { --ui-loading-h: 320px; }

/* Уважение к «уменьшить движение» собрано в одном месте — ниже по файлу. */

/* ═══════════════ МОЛЕКУЛЫ ОПЛАТЫ (пейвол, тарифы) ═══════════════ */

/* Тонированная плашка: соцпруф, аргументы. Плоская — тень только у
   интерактивного и поднятого, иначе экран «плывёт». */
.ui-plate {
    background: var(--ui-surface-2);
    border-radius: var(--ui-r-md);
    padding: var(--ui-s-3) var(--ui-s-4);
}

/* Таймер спеццены */
.ui-timer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ui-s-2);
    padding: var(--ui-s-3) var(--ui-s-4);
    border-radius: var(--ui-r-md);
    background: color-mix(in srgb, var(--ui-danger) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--ui-danger) 30%, transparent);
}
.ui-timer-label {
    display: flex;
    align-items: center;
    gap: var(--ui-s-2);
    font-size: var(--ui-t-md);
    font-weight: 600;
    color: var(--ui-text);
}
.ui-timer-label i { color: var(--ui-danger); }
.ui-timer-value {
    font-size: var(--ui-t-xl);
    font-weight: 800;
    font-variant-numeric: tabular-nums; /* цифры не «дышат» при отсчёте */
    color: var(--ui-danger);
    white-space: nowrap;
}

/* Карточка тарифа: поднятая поверхность (в тёмной теме — светлее окружения),
   волосок держит контур, выбор — акцентное кольцо + залитый кружок */
.ui-plan {
    display: flex;
    align-items: center;
    gap: var(--ui-s-4);
    width: 100%;
    text-align: left;
    padding: var(--ui-s-4);
    min-height: var(--ui-h-lg);
    background: var(--ui-surface-raised);
    border: none;
    border-radius: var(--ui-r-md);
    box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-sm);
    cursor: pointer;
    position: relative;
    font-family: inherit;
    color: var(--ui-text);
    box-sizing: border-box;
    transition: box-shadow var(--ui-dur-1) var(--ui-ease);
}
@media (hover: hover) {
    .ui-plan:hover:not(.selected) { box-shadow: var(--ui-ring), var(--ui-edge), var(--ui-shadow-md); }
}
.ui-plan.selected {
    box-shadow: var(--ui-edge), var(--ui-shadow-sm),
                0 0 0 1.5px var(--ui-accent), var(--ui-glow-accent);
}
.ui-plan-radio {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    border: 2px solid var(--ui-ctrl-line);
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    color: transparent;
    font-size: var(--ui-t-2xs);
    box-sizing: border-box;
    transition: background var(--ui-dur-1) var(--ui-ease),
                border-color var(--ui-dur-1) var(--ui-ease);
}
.ui-plan.selected .ui-plan-radio {
    background: var(--ui-accent);
    border-color: var(--ui-accent);
    color: var(--ui-on-accent);
}
.ui-plan-main { flex: 1; min-width: 0; }
/* Кеглей 17 и 19 в шкале нет — молекула приведена к шкале. */
.ui-plan-name { display: block; font-size: var(--ui-t-lg); font-weight: 700; letter-spacing: -0.01em; color: var(--ui-text); }
.ui-plan-note { display: block; font-size: var(--ui-t-sm); color: var(--ui-text-2); margin-top: 3px; }
.ui-plan-price { text-align: right; flex: none; }
.ui-plan-amount {
    display: block;
    font-size: var(--ui-t-xl);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--ui-text);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.ui-plan-old {
    font-size: var(--ui-t-md);
    font-weight: 400;
    color: var(--ui-text-3);
    text-decoration: line-through;
    margin-right: var(--ui-s-2);
}
.ui-plan-period { display: block; font-size: var(--ui-t-sm); color: var(--ui-text-2); margin-top: 3px; }

/* Бейдж-язычок на карточке. 11px — порог, мельче нельзя. */
.ui-badge {
    position: absolute;
    top: -12px;
    left: var(--ui-s-4);
    padding: 3px 10px;
    border-radius: var(--ui-r-xs);
    font-size: var(--ui-t-2xs);
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ui-on-success);
    background: var(--ui-success);
}
/* Зелёный язычок говорит о выгоде, акцентный — просто выделяет карточку
   («популярный тариф»). Два смысла, поэтому модификатор, а не свой класс. */
.ui-badge--accent { background: var(--ui-accent); color: var(--ui-on-accent); }

/* ═══════════════════════════ ДВИЖЕНИЕ ═══════════════════════════ */

/* Окно вырастает из точки: сдержанно, потому что предмет крупный и заметный
   перелёт на нём читается как рывок. */
@keyframes ui-pop {
    from { opacity: 0; transform: scale(0.96) translateY(6px); }
    to   { opacity: 1; transform: scale(1) translateY(0); }
}
/* Меню и поповер — то же движение, но заметнее: предмет мелкий, и без внятного роста
   он «возникает кадром». */
@keyframes ui-pop-menu {
    from { opacity: 0; transform: scale(0.85); }
    to   { opacity: 1; transform: scale(1); }
}
@keyframes ui-slide-up {
    from { transform: translateY(24px); opacity: 0.5; }
    to   { transform: translateY(0); opacity: 1; }
}
@keyframes ui-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Появление списка: карточки поднимаются по очереди, а не возникают пачкой. Каскад
   ограничен восемью элементами — дальше задержка читается как тормоза. */
@keyframes ui-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}
.ui-cascade > * {
    animation: ui-rise var(--ui-dur-2) var(--ui-ease) both;
}
.ui-cascade > *:nth-child(1) { animation-delay: 0ms; }
.ui-cascade > *:nth-child(2) { animation-delay: 40ms; }
.ui-cascade > *:nth-child(3) { animation-delay: 80ms; }
.ui-cascade > *:nth-child(4) { animation-delay: 120ms; }
.ui-cascade > *:nth-child(5) { animation-delay: 160ms; }
.ui-cascade > *:nth-child(6) { animation-delay: 200ms; }
.ui-cascade > *:nth-child(7) { animation-delay: 240ms; }
.ui-cascade > *:nth-child(n+8) { animation-delay: 280ms; }

/* Меню, поповер и список вырастают из своей кнопки: точку роста ставит ui-runtime.js,
   --ui-origin — запасное значение на случай, если скрипта нет. */
.ui-menu,
.ui-select-list,
.notification-dropdown {
    transform-origin: var(--ui-origin, 100% 0);
    animation: ui-pop-menu var(--ui-dur-2) var(--ui-ease-pop);
}

/* Элемент на время запроса: содержимое остаётся в потоке и гасится прозрачностью, иначе
   кнопка сожмётся. `.ui-btn--busy` — кнопки слоя, `.ui-busy` — любой нажимаемый элемент. */
.ui-busy,
.ui-btn--busy {
    position: relative;
    pointer-events: none;
    color: transparent;
}
.ui-busy > *,
.ui-btn--busy > * { opacity: 0; }
.ui-busy::after,
.ui-btn--busy::after {
    content: '';
    /* inset + margin: auto — спиннер по центру независимо от того, как
       разложено содержимое элемента. */
    position: absolute;
    inset: 0;
    margin: auto;
    width: 16px;
    height: 16px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: var(--ui-r-pill);
    color: var(--ui-text);
    animation: ui-spin 0.7s linear infinite;
}
/* На заливке акцентом спиннер белый, на спокойной поверхности — цветом текста. */
.ui-btn--busy.ui-btn--primary::after,
.ui-btn--busy.ui-btn--danger::after,
.ui-btn--busy.ui-btn--success::after { color: var(--ui-on-accent); }

/* Число «набегает» вместо мгновенной подстановки. Работает без JS — величина приходит
   в --ui-num; @property нужен, чтобы браузер знал, что это число, и анимировал его. */
@property --ui-num {
    syntax: '<integer>';
    initial-value: 0;
    inherits: false;
}
/* Имя ui-countup, а не ui-count: последнее уже занято круглым счётчиком у
   пункта меню, и совпадение имён красило число в акцентную пилюлю. */
.ui-countup {
    counter-reset: ui-num var(--ui-num);
    animation: ui-countup var(--ui-dur-3) var(--ui-ease) both;
    font-variant-numeric: tabular-nums;
}
.ui-countup::after { content: counter(ui-num); }
@keyframes ui-countup {
    from { --ui-num: 0; }
}

/* ═══════════════════════ ЗАГРУЗКА ═══════════════════════ */

/* Скелетон — форма будущего содержимого вместо крутилки: экран не «прыгает»
   при подстановке данных, потому что место уже занято. */
.ui-skeleton {
    position: relative;
    overflow: hidden;
    background: var(--ui-surface-2);
    border-radius: var(--ui-r-sm);
}
.ui-skeleton::after {
    content: '';
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg,
        transparent,
        color-mix(in srgb, var(--ui-text) 8%, transparent),
        transparent);
    animation: ui-shimmer 1.2s infinite;
}
@keyframes ui-shimmer {
    to { transform: translateX(100%); }
}
.ui-skeleton--text { height: var(--ui-t-md); margin-bottom: var(--ui-s-2); }
/* Полоска-заглушка на месте будущего заголовка. display задан явно: её ставят
   и внутрь строчного контейнера (<h2>, <span>), где высота у inline не берётся. */
.ui-skeleton--line { display: inline-block; height: var(--ui-t-sm); width: 80%; min-width: 120px; vertical-align: middle; }
.ui-skeleton--circle { border-radius: var(--ui-r-pill); width: 40px; height: 40px; flex: none; }
/* Строка списка целиком: кружок аватара и две полосы текста. Форма повторяет
   будущую строку, поэтому при подстановке данных ничего не прыгает. */
.ui-skeleton-row {
    display: flex;
    align-items: center;
    gap: var(--ui-s-3);
    padding: var(--ui-s-3) var(--ui-s-4);
}
.ui-skeleton-row__body { flex: 1 1 auto; min-width: 0; }
.ui-skeleton-row__body > .ui-skeleton:last-child { margin-bottom: 0; }

/* ═══════════ УМЕНЬШЕННОЕ ДВИЖЕНИЕ ═══════════ */
/* Один блок на весь слой: пока их было два, добавленный примитив попадал
   в тот, что ближе, и часть движения оставалась незаглушённой. */
@media (prefers-reduced-motion: reduce) {
    .ui-btn, .ui-input, .ui-check, .ui-plan, .ui-plan-radio,
    .ui-progress::after, .ui-toast,
    .ui-sheet, .ui-sheet-overlay { transition: none; }
    .ui-modal, .ui-cascade > *, .ui-menu { animation: none; }
    .ui-skeleton::after, .ui-countup { animation: none; }
    .ui-countup::after { content: counter(ui-num); }
    .ui-spinner { animation-duration: 2s; }
}

/* ═══════════════════════ ПРОКРУТКА ═══════════════════════ */
/* Прокручиваемые списки внутри карточек есть почти на каждом экране: системная полоса
   16px съедает край карточки — делаем тонкую и в цвете темы, одинаково везде. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--ui-a-20) transparent;
}
*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--ui-a-20);
    border-radius: var(--ui-r-pill);
}
@media (hover: hover) {
    *::-webkit-scrollbar-thumb:hover { background: var(--ui-a-30); }
}
/* ═══════════════════ ПЕРЕХОД МЕЖДУ СТРАНИЦАМИ ═══════════════════ */
/* Кросс-документный View Transitions здесь ПРОБОВАЛИ и убрали: на этом стеке он оставляет
   живой снапшот старого документа — страница двоилась, захват экрана вставал по таймауту. */
/* ═══════════ ШИМЫ СОСУЩЕСТВОВАНИЯ С ЛЕГАСИ ═══════════ */
/* Миграция идёт «двойным классом». Шимы нужны только там, где легаси дерётся !important
   или свойствами, которых у нас нет; каждый шим удаляется вместе со своим легаси-классом. */


/* min-width:120px у пары -modern ломает ui-row--split на узких экранах */
.ui-btn.btn-primary-modern { min-width: 0; min-height: var(--ui-h-lg); font-size: 15px; }

/* ═══════════ ФАЗА 2: мост легаси-акцентов ═══════════ */
/* Легаси-акценты сведены к --ui-accent через var-индирекцию; в селекторе перечислены и
   тематические блоки легаси — они специфичнее голого :root, мост только на :root проиграл бы.
   --tg-theme-* здесь нет СОЗНАТЕЛЬНО: telegram-web-app.js ставит их ИНЛАЙНОМ на <html>, а
   инлайн бьёт любой CSS без !important. У мессенджера берётся один бит — светло или темно. */
:root,
html.dark-theme,
:root[data-theme="dark"],
:root[data-theme="light"] {
    /* Градиент главных кнопок кабинета ведём на градиентную пару слоя */
}
/* ═══════════════════ ФОКУС ═══════════════════ */
/* После тапа браузер оставляет на кнопке системное кольцо фокуса — в вебвью Telegram оно
   белое и читается как дефект вёрстки. Кольцо нужно только клавиатуре: :focus-visible. */
button:focus:not(:focus-visible),
a:focus:not(:focus-visible),
[role="button"]:focus:not(:focus-visible),
summary:focus:not(:focus-visible) { outline: none; }

/* Клавиатурное кольцо своё, акцентом: системное `outline: auto` в тёмной теме белое и
   читается как рамка вокруг элемента. Компоненты слоя перекрывают это правило. */
:focus-visible {
    outline: 2px solid var(--ui-accent);
    outline-offset: 2px;
}

/* ═══════════════════ НАТИВНЫЕ КОНТРОЛЫ ═══════════════════ */
/* Без color-scheme браузер красит нерисуемые нами контролы по системной теме человека:
   на светлой теме продукта поле со ссылкой приглашения выходило чёрным. */
:root { color-scheme: light; }
:root[data-theme="dark"],
html.dark-theme { color-scheme: dark; }
/* Корню нужен свой фон: при color-scheme: dark браузер красит область за пределами body
   системным тёмным. Базовые шаблоны дублируют это инлайном — значения обязаны совпадать. */
html { background: var(--ui-bg); }

/* ═══════════════════ ПЛАШКА СООБЩЕНИЯ ═══════════════════ */
/* Блочное сообщение над формой: в отличие от тоста остаётся на месте и читается вместе
   с формой. Цвет несёт смысл, поэтому вариант обязателен — голая .ui-alert нейтральна. */
.ui-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--ui-s-2);
    padding: var(--ui-s-3) var(--ui-s-4);
    border-radius: var(--ui-r-md);
    font-size: var(--ui-t-sm);
    line-height: var(--ui-lh);
    background: var(--ui-surface-2);
    color: var(--ui-text-2);
}
.ui-alert--danger {
    background: color-mix(in srgb, var(--ui-danger) 12%, transparent);
    color: var(--ui-danger);
}
.ui-alert--success {
    background: color-mix(in srgb, var(--ui-success) 12%, transparent);
    color: var(--ui-success);
}
.ui-alert--warn {
    background: color-mix(in srgb, var(--ui-warning) 14%, transparent);
    color: var(--ui-warning);
}
.ui-alert--info {
    background: color-mix(in srgb, var(--ui-accent) 12%, transparent);
    color: var(--ui-accent);
}
/* Однострочная плашка: значок встаёт вровень с текстом, а не по верхнему краю */
.ui-alert--center { align-items: center; }
/* Компактная плашка — подсказка под полем, а не сообщение над формой */
.ui-alert--sm {
    align-items: center;
    padding: var(--ui-s-2) var(--ui-s-3);
    border-radius: var(--ui-r-xs);
}
/* Заголовок внутри плашки, когда текст длиннее одной строки */

/* ═══════════════════ ШАПКА ЭКРАНА ═══════════════════ */
/* Липкая шапка клиентского приложения; стоит на каждом экране, поэтому живёт в слое.
   Фон растворяется книзу, чтобы содержимое уезжало под неё без резкой границы. */
.ui-appbar {
    position: sticky;
    top: 0;
    z-index: var(--ui-z-sticky);
    display: flex;
    align-items: center;
    gap: var(--ui-s-3);
    padding: var(--ui-s-3) var(--ui-s-4) var(--ui-s-4);
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--ui-bg) 96%, transparent) 0%,
        color-mix(in srgb, var(--ui-bg) 88%, transparent) 80%,
        transparent 100%);
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
}
.ui-appbar-title {
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: var(--ui-t-lg);
    font-weight: 700;
    color: var(--ui-text);
    letter-spacing: -0.02em;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-appbar-sub {
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-appbar-btn {
    /* Квадрат по шкале высот: боковые кнопки шапки должны быть одинаковой
       ширины, иначе центрированный заголовок «плывёт» относительно центра. */
    width: var(--ui-h-md);
    min-height: var(--ui-h-md);
    flex-shrink: 0;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--ui-r-md);
    background: var(--ui-surface-raised);
    box-shadow: var(--ui-ring);
    border: none;
    color: var(--ui-text);
    font-size: var(--ui-t-md);
    cursor: pointer;
}
/* Акцентная кнопка шапки — там, где она сама является действием экрана,
   а не возвратом назад (маршрут клиента, передача тренировок). */
.ui-appbar-btn--accent {
    background: color-mix(in srgb, var(--ui-accent) 10%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-accent) 22%, transparent);
    color: var(--ui-accent);
}

/* Полотно клиентского экрана: свой фон и запас снизу под таб-бар */
/* Контролы шрифт не наследуют — задаём явно, иначе поля и кнопки внутри
   полотна остаются системным шрифтом браузера. */
.ui-page button,
.ui-page input,
.ui-page select,
.ui-page textarea { font-family: inherit; }

.ui-page {
    min-height: 100vh;
    min-height: 100dvh;
    box-sizing: border-box;
    padding: var(--ui-s-3) var(--ui-s-3) 80px;
    font-family: var(--ui-font);
    background: var(--ui-bg);
    color: var(--ui-text);
    -webkit-font-smoothing: antialiased;
}

/* ═══════════════════ УТИЛИТЫ (после компонентов) ═══════════════════ */
/* Утилиты в конце файла осознанно: при равной специфичности побеждает последнее правило,
   а утилита обязана перебивать компонент (иначе .ui-input бьёт .ui-w-auto-md). */

/* Вертикальный ритм там, где контейнер задать нельзя: закрывает 149 инлайновых отступов
   внутри чужих контейнеров. Для НОВОЙ вёрстки расстояние задаёт .ui-stack / .ui-row. */
.ui-mt-2 { margin-top: var(--ui-s-2); }
.ui-mt-3 { margin-top: var(--ui-s-3); }
.ui-mt-4 { margin-top: var(--ui-s-4); }
.ui-mt-5 { margin-top: var(--ui-s-5); }
.ui-mb-2 { margin-bottom: var(--ui-s-2); }
.ui-mb-3 { margin-bottom: var(--ui-s-3); }
.ui-mb-4 { margin-bottom: var(--ui-s-4); }
.ui-mb-5 { margin-bottom: var(--ui-s-5); }
.ui-m-0 { margin: 0; }
.ui-block { display: block; }
/* Внутренние поля блока — той же шкалой, что и всё остальное */
.ui-pad-0 { padding: 0; }
.ui-pad-4 { padding: var(--ui-s-4); }
.ui-pad-5 { padding: var(--ui-s-5); }
.ui-pad-6 { padding: var(--ui-s-6); }
.ui-pad-y-2 { padding-top: var(--ui-s-2); padding-bottom: var(--ui-s-2); }
.ui-clip { overflow: hidden; }
.ui-clickable { cursor: pointer; }
.ui-nowrap { white-space: nowrap; }
/* Ссылка без подчёркивания — там, где ссылка оформлена как элемент интерфейса */
.ui-plain-link { text-decoration: none; }
.ui-ml-auto { margin-left: auto; }
.ui-mr-1 { margin-right: var(--ui-s-1); }
.ui-mr-2 { margin-right: var(--ui-s-2); }
.ui-mb-0 { margin-bottom: 0; }
/* Компонент слоя со своим верхним отступом, поставленный в ряд, где отступ уже задан */
.ui-mt-0 { margin-top: 0; }
.ui-pt-3 { padding-top: var(--ui-s-3); }
.ui-w-full { width: 100%; }
.ui-underline { text-decoration: underline; }
.ui-inline-flex { display: inline-flex; }
.ui-scroll-y { max-height: 80vh;
 max-height: 80dvh; overflow-y: auto; }
.ui-dimmed { opacity: 0.75; }
/* Текст, который человек выделяет и копирует (название упражнения, код).
   Заголовок в списке по умолчанию не выделяется — список тянут пальцем. */
.ui-selectable { user-select: text; outline: none; }
.ui-pad-2 { padding: var(--ui-s-2); }
.ui-pad-3 { padding: var(--ui-s-3); }
.ui-pad-x-4 { padding-left: var(--ui-s-4); padding-right: var(--ui-s-4); padding-bottom: var(--ui-s-3); }
.ui-pb-5 { padding-bottom: var(--ui-s-5); }
.ui-w-40 { width: 40px; }
.ui-w-48 { width: 48px; }
.ui-h-180 { height: 180px; position: relative; }
/* Высота графика. 180 было мало: разница между «мало» и «много» умещалась
   в полтора десятка пикселей и на глаз читалась как ровная линия. */
.ui-h-240 { height: 240px; position: relative; }
.ui-mw-120 { max-width: 120px; }
.ui-mw-240 { max-width: 240px; }
.ui-mw-400 { max-width: 400px; }
.ui-mw-560 { max-width: 560px; }
.ui-mw-800 { max-width: 800px; }
.ui-mw-none { max-width: none; }
.ui-text--warning { color: var(--ui-warning); }
/* Плитки, которые сами раскладываются по ширине экрана */
.ui-grid-auto { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--ui-s-3); }
/* Фотография во весь экран: вписывается целиком, не обрезается */
.ui-lightbox-img { max-width: 90vw; max-height: 90vh;
 max-height: 90dvh; object-fit: contain; border-radius: var(--ui-r-sm); }
/* Полоса прогресса в нулевом состоянии: значение приходит из JS переменной */
.ui-progress-empty { width: 0%; }
.ui-borderless { border: none; }
.ui-w-auto-sm { width: auto; min-width: 120px; }
.ui-modal-w-400 { max-width: 400px; }
.ui-modal-w-460 { max-width: 460px; }
/* Затемнение внутри блока (не на весь экран): поверх карточки на время загрузки */
.ui-scrim { position: absolute; inset: 0; background: var(--ui-dim); }
.ui-inherit-color { color: inherit; }
.ui-w-auto-md { width: auto; min-width: 150px; }

/* ═══════════════════════════ РЯД ФИЛЬТРОВ ═══════════════════════════ */

/* Пара «что показать» / «как отсортировать» — одна строка, а не две: основа каждого
   списка половина ряда, третий фильтр уходит вниз. На широком встают по своей ширине. */
.ui-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ui-s-2);
    flex: 1 1 100%;
    min-width: 0;
}
.ui-filters > [class][class] {
    flex: 1 1 calc(50% - var(--ui-s-1));
    min-width: 0;
    width: auto;
}
/* Когда в ряду есть кнопка-значок, половина ряда паре списков не по карману — значки
   уезжали на вторую строку; вычитаем их долю. 56, а не 46: на 46 ряд роняет округление. */
.ui-filters:has(> .ui-btn--icon) > [class][class]:not(.ui-btn--icon) {
    flex-basis: calc(50% - 56px);
}
/* Кнопка-значок при фильтрах половину ряда не занимает: она не фильтр, а действие при
   них — иначе третий элемент уезжал на свою строку и ряд выглядел незаконченным. */
.ui-filters > [class][class].ui-btn--icon { flex: 0 0 auto; width: var(--ui-h-md); }
@media (min-width: 560px) {
    .ui-filters { flex: 0 1 auto; }
    .ui-filters > [class][class] { flex: 0 1 auto; min-width: 150px; }
}

/* ═══════════════════════ ТИПОГРАФИКА (утилиты) ═══════════════════════ */
/* Закрывают инлайновые color, font-size, text-align, font-weight. Атомарного набора
   вроде .mt-8 сознательно нет — расстояния задаёт .ui-stack / .ui-row. */

.ui-text--2xs { font-size: var(--ui-t-2xs); }
.ui-text--xs { font-size: var(--ui-t-xs); }
.ui-text--sm { font-size: var(--ui-t-sm); }
.ui-text--md { font-size: var(--ui-t-md); }
.ui-text--lg { font-size: var(--ui-t-lg); }
.ui-text--xl { font-size: var(--ui-t-xl); line-height: var(--ui-lh-tight); }
.ui-text--2xl { font-size: var(--ui-t-2xl); line-height: var(--ui-lh-tight); letter-spacing: -0.02em; }
.ui-text--3xl { font-size: var(--ui-t-3xl); line-height: var(--ui-lh-tight); letter-spacing: -0.02em; }

.ui-text--muted { color: var(--ui-text-2); }
.ui-text--faint { color: var(--ui-text-3); }   /* только декор, читаемым текстом не набирать */
.ui-text--accent { color: var(--ui-accent); }
.ui-text--danger { color: var(--ui-danger); }
.ui-text--success { color: var(--ui-success); }

.ui-text--strong { font-weight: 700; }
.ui-text--semi { font-weight: 600; }
.ui-text--center { text-align: center; }
.ui-text--right { text-align: right; }
/* Цифры в колонках не «дышат» при обновлении */
.ui-nums { font-variant-numeric: tabular-nums; }
.ui-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Обрезка в две строки — для описаний в карточках */
.ui-clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}

/* ═══════════════════════════ СКРЫТИЕ ═══════════════════════════ */
/* Стоит В КОНЦЕ файла осознанно: скрытие обязано перебивать display компонентов слоя.
   БЕЗ !important — половина продукта показывает элементы через el.style.display. */
/* Второй селектор — не украшение: файл экрана подключается ПОСЛЕ слоя, а `[class].ui-hidden`
   весит как два класса и перебивает его правила, оставаясь слабее инлайна. */
.ui-hidden,
[class].ui-hidden,
/* d-none приехал из bootstrap и остался в разметке 277 раз. Держим по тем же правилам:
   без !important и с парным селектором против правил экранов. */
.d-none,
[class].d-none { display: none; }
.ui-hidden--force { display: none !important; }

/* Скрытие по ширине из файлов кабинета: они подключаются ДО слоя, и display компонента
   (.ui-btn — inline-flex) выигрывал у них порядком. Повторяем правила здесь, последними. */
@media (max-width: 768px) {
    .cab-hide-sm { display: none; }
    .ui-btn.cab-assign {
        flex: 1 1 auto;
        width: auto;
        padding: 0 var(--ui-s-3);
    }
}

/* Кнопка, которая на телефоне сворачивается в иконку. Оба намерения названы явно:
   `--collapse` на кнопке, `ui-btn-label` на подписи — из разметки ничего не угадывается. */
/* [class] намеренно: панели кабинета растягивают детей правилами в два класса, и без
   добавочного веса свёрнутая кнопка оставалась широкой плашкой со спрятанной подписью. */
@media (max-width: 768px) {
    /* Вместе с подписью прячется и счётчик при ней: в свёрнутой кнопке он становится
       вторым ребёнком и растягивает квадрат по высоте (49 против 38 у соседа). */
    .ui-btn--collapse > .ui-btn-label,
    .ui-btn--collapse > .ui-status { display: none; }
    /* Ширина считается от фактической высоты, а не берётся ступенью шкалы: высоту кнопке
       нередко задаёт панель, и свёрнутая выходила прямоугольником 38×46 вместо квадрата. */
    [class].ui-btn--collapse {
        flex: 0 0 auto;
        width: auto;
        aspect-ratio: 1;
        min-width: var(--ui-h-sm);
        padding: 0;
    }
}
@media (min-width: 769px) {
    .cab-mobile-only { display: none; }
    /* На широком экране подпись видна — значит значка при ней быть не должно (правило 25).
       Какая из двух покажется, решает ширина. */
    .ui-btn--collapse > i { display: none; }
}
