/* ==========================================================================
   COSA — стекло

   Подключается ПОСЛЕ theme-dark.css. Меняет только материал поверхностей:
   заливку, грань и тень. Радиусы, отступы, кегли и раскладка не трогаются —
   форма интерфейса остаётся ровно той же, меняется вещество.

   Про размытие — честно, потому что это решает и вид, и скорость.
   backdrop-filter имеет смысл там, где под панель ЗАЕЗЖАЕТ содержимое:
   под шапкой прокручивается страница, под модальным окном лежит весь
   интерфейс. Там оно и стоит, с большим радиусом.

   Под карточками лежит только фон страницы — плавный градиент. Размывать
   плавный градиент нечего: результат попиксельно тот же. Поэтому у карточек
   радиус маленький: он ничего не портит, но и кадры не ест. Раньше именно
   два с половиной десятка полноценных фильтров на карточках и давали
   микрофризы, о которых шла речь.
   ========================================================================== */

:root {
  /* Тело стекла и его грань.

     Раньше это была почти прозрачная белая вуаль. Пока фон был ровным,
     разницы не было, но как только за карточками появился свет, он стал
     проступать прямо сквозь таблицу — пятном поперёк строк. У поверхности
     должно быть собственное тело: тёмное и плотное на три четверти. Свет
     за ней тогда читается как глубина, а не как грязь на стекле. */
  --glass-bg:            rgba(23, 26, 33, 0.74);
  --glass-bg-raised:     rgba(31, 35, 43, 0.78);
  --glass-edge:          rgba(255, 255, 255, 0.065);
  --glass-edge-top:      rgba(255, 255, 255, 0.10);
  --glass-shade-bottom:  rgba(0, 0, 0, 0.22);

  /* Единый материал крупных панелей: шапка, сайдбар, область содержимого.
     Одно значение на всех — чтобы стыки между ними не выдавали разницу. */
  --panel-bg:            rgba(16, 18, 23, 0.72);
  --panel-blur:          blur(20px) saturate(1.28);
}

/* --------------------------------------------------------------------------
   Фон страницы

   Еле различимый вертикальный градиент от тёмно-серого к почти чёрному
   плюс два очень слабых световых пятна по углам. Ровная заливка не даёт
   стеклу ничего: панель на ней читается плоским прямоугольником, какой бы
   хорошей ни была грань.

   fixed — чтобы фон не ехал при прокрутке и не перерисовывался.
   -------------------------------------------------------------------------- */
body {
  background:
    /* Вертикальная линия под правым краем сайдбара. Она не разделитель —
       граница у сайдбара своя; это след в глубине, из-за которого фон
       перестаёт быть пустым полем и у панели появляется «за чем» стоять.
       Ширина 1px, поэтому на любом экране она остаётся линией, а не полосой. */
    linear-gradient(180deg, transparent, rgba(120, 150, 200, 0.16) 18%,
                    rgba(120, 150, 200, 0.16) 74%, transparent) no-repeat,
    radial-gradient(60rem 40rem at 12% -10%, rgba(90, 120, 170, 0.055), transparent 60%),
    radial-gradient(50rem 36rem at 92% 8%,  rgba(120, 140, 180, 0.035), transparent 58%),
    linear-gradient(180deg, #14161B 0%, #0D0E12 45%, #0A0B0E 100%);
  background-size: 1px 100%, auto, auto, auto;
  background-position: var(--sidebar-width) 0, 0 0, 0 0, 0 0;
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* Сайдбар может быть свёрнут — линия едет вместе с его краем */
body:has(.sidebar.collapsed) { background-position: var(--sidebar-collapsed) 0, 0 0, 0 0, 0 0; }

/* На узком экране сайдбар уезжает за левый край, а линия оставалась на месте
   и шла прямо через карточки. Следу не за чем стоять — убираем слой. */
@media (max-width: 768px) {
  body, body:has(.sidebar.collapsed) { background-size: 0 0, auto, auto, auto; }
}

/* --------------------------------------------------------------------------
   Вертикальный ритм страницы

   Отступы между верхними блоками расставлены по вьюхам вручную, и местами
   их просто нет: на списке QR-кодов полоса вкладок стояла к таблице
   вплотную, зазор ноль. Задаём ритм в одном месте для всех страниц.

   margin-top у следующего, а не margin-bottom у предыдущего: соседние
   вертикальные отступы схлопываются, поэтому там, где во вьюхе отступ уже
   задан, останется больший из двух, а не сумма. Ничего не разъезжается.
   -------------------------------------------------------------------------- */
.page-content > * + *:not(.modal-overlay):not(script):not(style) {
  margin-top: var(--space-lg);
}

/* Кнопки в ячейке таблицы лежат во флекс-контейнере с gap: 3px. В узкой
   ячейке они переносятся на вторую строку, и эти же 3px между строками
   читаются как одна слипшаяся деталь.

   Раздвигаем именно строки: row-gap не трогает расстояние по горизонтали,
   поэтому кнопки, стоящие в один ряд, остаются как были. Верхний отступ на
   самой кнопке для этого не годится — при align-items: center он смещает её
   вниз на половину, и в ряду кнопки вставали на разной высоте. */
.table td .table-actions { row-gap: 6px; }

/* Часть таблиц задаёт контейнеру gap прямо в разметке, сокращённым
   свойством. Инлайновый стиль бьётся только !important — иначе строки так
   и остаются в 3px. */
.table td div[style*="flex"]:has(> .btn + .btn) { row-gap: 6px !important; }

/* Два бейджа в узкой ячейке переносятся на вторую строку и встают в 1px друг
   к другу: межстрочное расстояние ячейки (20.8px) почти равно высоте бейджа
   (20px). Раздвигаем строки только в тех ячейках, где бейдж не один, —
   вертикальный margin на самом бейдже поднял бы высоту строк и там, где
   бейджи стоят в один ряд. */
.table td:has(.badge + .badge) { line-height: 2.1; }

/* --------------------------------------------------------------------------
   Поверхности без живого размытия

   Карточки, плитки, обёртки таблиц. Заливка, грань сверху, притенение снизу
   и тень — этого достаточно, чтобы поверхность читалась стеклянной над
   градиентом.
   -------------------------------------------------------------------------- */
.card,
.widget,
.stat-card,
.table-wrapper,
.dashboard-hero,
.pagination-wrapper {
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  box-shadow:
    inset 0 1px 0 var(--glass-edge-top),
    inset 0 -1px 0 var(--glass-shade-bottom),
    var(--shadow-md);
  /* backdrop-filter здесь намеренно НЕТ.
     Сравнил два снимка страницы, с фильтром и без: они попиксельно
     одинаковые — под карточкой лежит плавный градиент, а размывать
     плавный градиент нечего. Зато карточкам меняет прозрачность выезд
     по прокрутке, и каждый фильтр пересчитывался бы на каждом кадре
     этой анимации. Два с половиной десятка таких проходов — ровно тот
     случай, из-за которого раньше были микрофризы. */
}

/* Карточка под курсором подсвечивает грань и углубляет тень.
   Сам подъём делает GSAP (assets/js/motion.js): он пишет transform инлайном,
   и правило из CSS всё равно бы не сработало. */
.widget,
.card {
  transition:
    box-shadow 260ms ease,
    border-color 260ms ease;
}

.widget:hover,
.card:hover {
  border-color: rgba(255, 255, 255, 0.11);
  box-shadow:
    inset 0 1px 0 var(--glass-edge-top),
    inset 0 -1px 0 var(--glass-shade-bottom),
    var(--shadow-lg);
}

/* Плитка внутри стеклянной панели — на ступень светлее, иначе тонет */
.dashboard-hero .stat-card {
  background: var(--glass-bg-raised);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.card-header,
.card-footer,
.modal-header,
.modal-footer { border-color: var(--glass-edge); }

.table td { border-color: var(--glass-edge); }
.table tbody tr:hover { background: rgba(255, 255, 255, 0.035); }

/* --------------------------------------------------------------------------
   Поверхности с живым размытием

   Под шапкой действительно проезжает содержимое, модальное окно обязано
   закрывать интерфейс под собой. Сайдбар размывает только фон, но он один
   на странице — пусть будет заодно, ради единства материала.
   -------------------------------------------------------------------------- */
/* Шапка и сайдбар — ОДИН материал.

   Раньше у них были разные значения прозрачности и насыщения (0.72/1.3
   против 0.78/1.2), и на стыке это читалось как «размытие у шапки другое».
   Держим их одной переменной: разойтись уже не смогут.

   Область содержимого сюда НЕ входит, хотя попытка была. Три панели одного
   цвета, стоящие вплотную — шапка сверху, сайдбар слева, содержимое между
   ними, — дают ровно тот эффект «блок вплотную к блоку»: границы панелей
   превращаются в швы посреди экрана. Содержимое должно лежать НА фоне,
   а не быть ещё одной плашкой; карточки на нём и создают глубину. */
.header,
.sidebar {
  background: var(--panel-bg);
  backdrop-filter: var(--panel-blur);
  -webkit-backdrop-filter: var(--panel-blur);
}

.header {
  border-bottom: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-edge-top), var(--shadow-sm);
}

.sidebar {
  border-right: 1px solid var(--glass-edge);
  /* сайдбар лежит поверх прокручиваемого содержимого — не перерисовывать
     его вместе с ним */
  contain: paint;
}

.sidebar-logo   { border-bottom: 1px solid var(--glass-edge); }
.sidebar-footer { border-top: 1px solid var(--glass-edge); }

.modal {
  background: rgba(22, 25, 31, 0.86);
  border: 1px solid rgba(255, 255, 255, 0.085);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.11),
    var(--shadow-lg);
  backdrop-filter: blur(24px) saturate(1.25);
  -webkit-backdrop-filter: blur(24px) saturate(1.25);
}

.dropdown-menu {
  background: rgba(22, 25, 31, 0.9);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-edge-top), var(--shadow-lg);
  backdrop-filter: blur(18px) saturate(1.2);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
}

.toast {
  background: rgba(24, 27, 33, 0.92);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-edge-top), var(--shadow-lg);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
}

/* --------------------------------------------------------------------------
   Поля ввода — вдавленные, а не выпуклые
   -------------------------------------------------------------------------- */
.form-input,
.form-select,
.form-textarea,
.search-input,
.login-input {
  background-color: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--glass-edge);
  color: var(--color-text);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
}

/* --------------------------------------------------------------------------
   Раскрытый список <select>

   Сам список рисует не страница, а браузер, и цвет он берёт с элемента.
   С полупрозрачным фоном Chrome не может определить тему и раскрывает
   список системным светлым — на тёмной странице это белое полотно.

   Поэтому у select фон НЕПРОЗРАЧНЫЙ (визуально тот же, что у остальных
   полей поверх карточки), плюс явные цвета у option и color-scheme на самом
   элементе — этого браузеру достаточно, чтобы нарисовать список тёмным.
   -------------------------------------------------------------------------- */
.form-select,
select {
  background-color: #191C23;
  color-scheme: dark;
}

.form-select option,
select option,
.form-select optgroup,
select optgroup {
  background-color: #191C23;
  color: var(--color-text);
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus,
.search-input:focus,
.login-input:focus {
  border-color: var(--color-primary);
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.3),
    0 0 0 3px var(--color-primary-light);
}

/* --------------------------------------------------------------------------
   Выбор файла

   Нативный <input type="file"> в проекте спрятан, видимая часть — это
   <label class="form-file-btn">. Она брала фон страницы и на тёмной теме
   сливалась с карточкой, на которой лежит. Приводим к тому же стеклу, что
   и вторичные кнопки, чтобы «Выбрать файл» читалось как кнопка.
   -------------------------------------------------------------------------- */
.form-file-btn {
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  color: var(--color-text);
  transition: background 200ms ease, border-color 200ms ease;
}

.form-file-btn:hover {
  background: var(--glass-bg-raised);
  border-color: rgba(255, 255, 255, 0.13);
}

.form-file-name { color: var(--color-text-muted); }

/* Если где-то файл-поле осталось без обёртки, нативная кнопка внутри него
   всё равно должна попасть в тему, а не светиться серым прямоугольником. */
input[type="file"] {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

input[type="file"]::file-selector-button {
  margin-right: 10px;
  padding: 7px 14px;
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-md);
  background: var(--glass-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background 200ms ease, border-color 200ms ease;
}

input[type="file"]::file-selector-button:hover {
  background: var(--glass-bg-raised);
  border-color: rgba(255, 255, 255, 0.13);
}

/* --------------------------------------------------------------------------
   Кнопки: основная остаётся плотной, вторичные становятся стеклом
   -------------------------------------------------------------------------- */
.btn-outline,
.btn-ghost {
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  color: var(--color-text);
}

.btn-outline:hover,
.btn-ghost:hover {
  background: var(--glass-bg-raised);
  border-color: rgba(255, 255, 255, 0.13);
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   Страница входа
   -------------------------------------------------------------------------- */
.login-page { background: transparent; }

.login-card {
  background: rgba(21, 24, 30, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    var(--shadow-lg);
  backdrop-filter: blur(22px) saturate(1.25);
  -webkit-backdrop-filter: blur(22px) saturate(1.25);
}

.login-footer { border-top-color: var(--glass-edge); }

/* --------------------------------------------------------------------------
   Вкладки
   -------------------------------------------------------------------------- */
.tabs {
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--glass-edge);
}

.tab.active {
  background: var(--glass-bg-raised);
  box-shadow: inset 0 1px 0 var(--glass-edge-top);
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   Запасной вариант

   Браузер без backdrop-filter или просьба системы «меньше прозрачности» —
   отдаём плотные поверхности, чтобы текст не оказался на мутной каше.
   -------------------------------------------------------------------------- */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .header       { background: #12141A; }
  .sidebar      { background: #0F1115; }
  .modal,
  .dropdown-menu,
  .toast        { background: #191C22; }
  .login-card   { background: #171A20; }
  .card, .widget, .stat-card,
  .table-wrapper, .dashboard-hero { background: #15171C; }
}

/* --------------------------------------------------------------------------
   Печать

   Блоки ниже сгиба проявляются по прокрутке, то есть до неё лежат с
   opacity: 0 в инлайновом стиле. При печати страница выводится целиком и
   непрокрученная часть ушла бы на бумагу пустой. Инлайновый стиль бьётся
   только !important — здесь это оправдано.
   -------------------------------------------------------------------------- */
@media print {
  .page-content *,
  .card, .widget, .stat-card, .table tbody tr {
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
  }
}

/* --------------------------------------------------------------------------
   Уход со страницы

   Экран затягивается тем же цветом, что и фон страницы входа, и на нём
   остаётся один крутящийся знак. Совпадение фонов важнее анимации: если
   оверлей и следующая страница одного цвета, смены документа не видно —
   видно только знак, который не переставал крутиться.

   Знак не прячем через display: none, иначе первый кадр появления придётся
   на ещё не разложенный элемент. Держим его невидимым и вне событий.
   -------------------------------------------------------------------------- */
.page-exit {
  position: fixed;
  inset: 0;
  z-index: 300;              /* выше модалок (200): уходим со страницы целиком */
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.page-exit-mark {
  width: 108px;
  height: 108px;
  color: var(--color-primary);
}

.cosa-mark          { display: block; }
.cosa-mark svg      { width: 100%; height: 100%; display: block; }

@media (max-width: 480px) {
  .page-exit-mark { width: 84px; height: 84px; }
}

/* Без анимации оверлей только мигнёт лишним кадром — он не нужен вовсе */
html:not(.has-motion) .page-exit { display: none; }

/* --------------------------------------------------------------------------
   Воздух в области содержимого

   Фоновая графика в layout.css уже была: три косых луча и два мягких пятна
   по углам. Но их прозрачность (0.02–0.04) рассчитана на светлый интерфейс,
   а на почти чёрном фоне разница в три-четыре сотых не видна физически —
   поле выглядело мёртвым. Мотив оставляем тот же, поднимаем до порога
   различимости и берём цвет чуть светлее, чтобы он проступал на тёмном.
   -------------------------------------------------------------------------- */
.main-content::before {
  width: 120px;
  background: linear-gradient(to bottom, transparent 5%, rgba(126, 176, 235, 0.085) 25%,
              rgba(126, 176, 235, 0.045) 75%, transparent 95%);
}

.main-content::after {
  width: 78px;
  background: linear-gradient(to bottom, transparent 8%, rgba(126, 176, 235, 0.06) 30%,
              rgba(126, 176, 235, 0.032) 70%, transparent 92%);
}

.page-content::before {
  width: 54px;
  background: linear-gradient(to bottom, transparent 10%, rgba(126, 176, 235, 0.05) 35%,
              rgba(126, 176, 235, 0.028) 65%, transparent 90%);
}

/* Свет там, куда взгляд попадает первым: верх области содержимого.

   Плюс тот же мотив волн, что в шапке диспетчерской. Он там уже есть и
   работает — но жил только на одной странице, поэтому остальные разделы
   выглядели как другой продукт. Здесь он лежит фоном, за карточками, и
   виден ровно в пустой полосе сверху. Рисунок встроен прямо в стиль: это
   три кривые, отдельный файл ради них заводить незачем. */
.page-content::after {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 200' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%237EB0EB' stroke-opacity='0.22' stroke-width='1.4' stroke-linecap='round'%3E%3Cpath d='M0,160 C80,160 100,40 200,40 C300,40 280,170 370,170 C420,170 480,80 500,80'/%3E%3Cpath d='M0,180 C90,180 130,60 220,55 C310,50 310,180 400,175 C440,173 490,110 500,105'/%3E%3Cpath d='M0,195 C100,195 160,90 250,80 C330,72 340,190 420,185 C460,182 495,140 500,135'/%3E%3C/g%3E%3C/svg%3E"),
    radial-gradient(60rem 32rem at 78% -6%, rgba(86, 140, 205, 0.11) 0%, transparent 62%),
    radial-gradient(40rem 30rem at 6% 92%,  rgba(86, 140, 205, 0.055) 0%, transparent 55%);
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: right -40px top -10px, 0 0, 0 0;
  background-size: 62% 230px, auto, auto;
}

/* --------------------------------------------------------------------------
   Заголовок раздела

   Раньше страница начиналась просто строкой текста в пустоте, поэтому у
   содержимого не было верхней границы — оно как будто висело. Акцентная
   черта слева даёт заголовку опору, тонкая линия снизу отделяет шапку
   раздела от его содержимого.
   -------------------------------------------------------------------------- */
.page-header {
  padding-bottom: var(--space-md);
  border-bottom: 1px solid rgba(255, 255, 255, 0.055);
}

.page-title {
  position: relative;
  padding-left: 14px;
}

.page-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.16em;
  bottom: 0.16em;
  width: 3px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--color-primary), rgba(76, 143, 212, 0.2));
}

/* --------------------------------------------------------------------------
   Отклик таблицы

   Подсветка строки под курсором была rgba(44,95,138,0.03) — на тёмной
   поверхности это ноль: длинная таблица никак не отзывалась на курсор и
   глазом было не удержать строку при чтении вправо.
   -------------------------------------------------------------------------- */
.table tbody tr > td {
  transition: background-color 140ms ease;
}

/* Чередование — только чтобы глаз держал строку, не как полосатость */
.table tbody tr:nth-child(even) > td {
  background: rgba(255, 255, 255, 0.016);
}

.table tbody tr:hover > td {
  background: rgba(76, 143, 212, 0.09);
}

.table tbody tr:hover > td:first-child {
  box-shadow: inset 3px 0 0 var(--color-primary);
}

/* --------------------------------------------------------------------------
   Акцент: заливка и текст — разные краски

   #4C8FD4 задуман как заливка. Как текст он слабее: на плашке бейджа даёт
   4.29, белый на нём самом — 3.39 при норме 4.5 для мелкого текста. Поэтому
   для текста берём ту же краску светлее, а под белым текстом — темнее.
   Оттенок один, меняется только светлота: интерфейс остаётся тем же.
   -------------------------------------------------------------------------- */
.badge-primary,
.badge-info {
  color: #6BA6E2;                      /* 5.67 на своей плашке */
}

.btn-primary {
  background: #3675B7;                 /* белый текст: 4.79 */
  border-color: #3675B7;
}

.btn-primary:hover {
  background: #3F84CC;
  border-color: #3F84CC;
}

/* --------------------------------------------------------------------------
   Пустое состояние

   Значок стоял почти прозрачным (0.3) — на тёмном фоне пустой раздел
   выглядел не «здесь пока ничего нет», а «страница не догрузилась».
   Круг с мягким светом даёт сообщению центр.
   -------------------------------------------------------------------------- */
.empty-state-icon {
  width: 64px;
  height: 64px;
  margin: 0 auto var(--space-md);
  border-radius: 50%;
  opacity: 1;
  color: var(--color-primary);
  background: radial-gradient(circle at 50% 42%,
              rgba(76, 143, 212, 0.18), rgba(76, 143, 212, 0.035) 72%);
  border: 1px solid rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 1px 0 var(--glass-edge-top);
}

.empty-state-icon svg,
.empty-state-icon i {
  width: 26px;
  height: 26px;
}

/* --------------------------------------------------------------------------
   Перетекание между страницами

   Переход остаётся обычным: сервер отдаёт целую страницу, весь JS модулей
   отрабатывает как всегда, ничего не подменяется на лету. Браузер лишь
   снимает кадр уходящей страницы и кадр пришедшей и переводит один в
   другой. Поэтому это не «SPA со всеми его сюрпризами», а именно смена
   картинки поверх обычной навигации.

   Именованные части снимаются отдельно от остального: меню и шапка стоят
   на месте, метка активного пункта переезжает сама (браузер сводит её
   старое и новое положение), а меняется только область содержимого.

   Браузеры без поддержки просто перейдут по ссылке как раньше — работает
   всё то же самое, без перетекания.
   -------------------------------------------------------------------------- */
@view-transition { navigation: auto; }

.sidebar        { view-transition-name: cosa-sidebar; }
.header         { view-transition-name: cosa-header; }
.page-content   { view-transition-name: cosa-content; }
.sidebar-marker { view-transition-name: cosa-marker; }

::view-transition-old(cosa-content) {
  animation: cosaContentOut 190ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

::view-transition-new(cosa-content) {
  animation: cosaContentIn 380ms cubic-bezier(0.16, 1, 0.3, 1) 60ms backwards;
}

@keyframes cosaContentOut {
  to { opacity: 0; transform: translateY(-10px); }
}

@keyframes cosaContentIn {
  from { opacity: 0; transform: translateY(14px); }
}

/* Метку браузер переводит сам — задаём только характер движения */
::view-transition-group(cosa-marker) {
  animation-duration: 420ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Обвязка и фон меняются почти незаметно: их дело — остаться на месте */
::view-transition-group(root),
::view-transition-group(cosa-sidebar),
::view-transition-group(cosa-header) {
  animation-duration: 220ms;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --------------------------------------------------------------------------
   Метка активного пункта меню

   Подсветка вынесена из самого пункта в отдельный слой: пункт не может
   переехать к соседу, а слой — может. Поэтому у активного пункта своя
   заливка и полоска гасятся, но только когда метка действительно работает
   (класс ставит скрипт) — без JS всё остаётся как было.
   -------------------------------------------------------------------------- */
.sidebar-nav { position: relative; }

.sidebar-marker {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 38px;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
              rgba(76, 143, 212, 0.22), rgba(76, 143, 212, 0.06) 65%, transparent);
  border-left: 3px solid var(--color-primary);
  box-shadow: inset 1px 0 12px rgba(76, 143, 212, 0.18);
}

/* Пункты должны лежать НАД меткой, иначе она закроет текст и значки */
.sidebar-nav .sidebar-item { position: relative; z-index: 1; }

.sidebar-nav.has-marker .sidebar-item.active {
  background: transparent;
  border-left-color: transparent;
}

/* --------------------------------------------------------------------------
   Полоса перехода

   Тонкая линия поверх шапки: показывает, что переход начался, и не занимает
   места в раскладке. Растёт слева направо через scaleX — это трансформация,
   она не вызывает пересчёта страницы на каждом кадре.
   -------------------------------------------------------------------------- */
.nav-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  z-index: 400;
  transform: scaleX(0);
  transform-origin: 0 50%;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
              rgba(76, 143, 212, 0.25), var(--color-primary) 60%, #7FB6EE);
  box-shadow: 0 0 10px rgba(76, 143, 212, 0.45);
}

/* --------------------------------------------------------------------------
   Загрузка содержимого окна

   modal.js показывает <div class="spinner">, пока тянет содержимое по сети,
   но стиля для него в проекте не было — окно открывалось пустым. Кольцо с
   разрывом повторяет знак COSA: то же движение, что и на переходах.
   -------------------------------------------------------------------------- */
.spinner {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid rgba(76, 143, 212, 0.2);
  border-top-color: var(--color-primary);
  animation: cosaSpin 0.85s linear infinite;
}

@keyframes cosaSpin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

/* --------------------------------------------------------------------------
   Кнопка в работе

   Полоска по нижнему краю вместо подмены текста: ширина кнопки не меняется,
   цвет берётся из currentColor, поэтому одно правило подходит и акцентной
   кнопке, и обводке. Текст остаётся читаемым — видно, что именно делается.
   -------------------------------------------------------------------------- */
.btn.is-loading {
  position: relative;
  overflow: hidden;
  pointer-events: none;
}

.btn.is-loading::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 40%;
  background: currentColor;
  opacity: 0.55;
  border-radius: 2px;
  animation: cosaBtnBar 0.9s ease-in-out infinite;
}

@keyframes cosaBtnBar {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::after { animation: none; width: 100%; opacity: 0.3; }
}

@media (prefers-reduced-transparency: reduce) {
  .header       { background: #12141A; backdrop-filter: none; }
  .sidebar      { background: #0F1115; backdrop-filter: none; }
  .modal,
  .dropdown-menu,
  .toast        { background: #191C22; backdrop-filter: none; }
  .login-card   { background: #171A20; backdrop-filter: none; }
  .card, .widget, .stat-card,
  .table-wrapper, .dashboard-hero { background: #15171C; backdrop-filter: none; }
}
