/*! Navex motion-kit v1 · 2026-10-08 · CSS (vanilla, без зависимостей)
   Подключение в прототипе:
     <link rel="stylesheet" href="shared/motion-kit.css">   — после токенов цвета варианта, до стилей компонентов варианта
     <script src="shared/motion-kit.js" defer></script>
   Спецификация и обоснование значений: ../research/motion.md. Краткая справка: ./motion-kit.md
   Правило: вариант МЕНЯЕТ ЗНАЧЕНИЯ токенов, но не пишет свои cubic-bezier и длительности мимо токенов. */

/* ================================================================
   1. ТОКЕНЫ
   ================================================================ */
:root {
  /* — Кривые — */
  --ease: cubic-bezier(.23, 1, .32, 1);          /* появление/исчезновение (сильный ease-out). По умолчанию для всего */
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);  /* перемещение уже видимого: бегунок вкладок, FLIP */
  --ease-drawer: cubic-bezier(.32, .72, 0, 1);   /* шторки, боковое меню, панели (iOS / Vaul) */
  --ease-hover: ease;                            /* ховер, смена цвета */
  --ease-spring: var(--ease);                    /* пружина без отскока; ниже — linear() где поддерживается */
  --ease-bounce: var(--ease-spring);             /* «приземление» с отскоком; по умолчанию выключен (= spring) */
  --ease-pop: cubic-bezier(.34, 1.56, .64, 1);   /* «праздник»: easeOutBack (easings.net), заменяется linear() */

  /* — Длительности — */
  --instant: 100ms;      /* нажатие, смена цвета */
  --fast: 150ms;         /* ховер, тултип, чекбокс, ВЫХОД дропдауна/модалки */
  --normal: 220ms;       /* дропдаун, вкладки, сообщение, ВХОД модалки */
  --slow: 320ms;         /* боковое меню, большие панели */
  --sheet: 420ms;        /* шторка снизу (телефон), вход */
  --page: 220ms;         /* переход между страницами (View Transitions) */
  --toast-time: 400ms;   /* тосты (как Sonner: ease, чуть медленнее UI) */
  --spring-fast: 320ms;  /* полная длительность пружины --ease-spring (визуально ~200мс) */
  --spring: 480ms;       /* визуально ~300мс */
  --spring-slow: 640ms;  /* визуально ~400мс */
  --bounce-time: 590ms;  /* для --ease-bounce */
  --pop-time: 940ms;     /* для --ease-pop */
  --spinner-time: 600ms; /* один оборот спиннера (быстрый спиннер = ощущение быстрой загрузки) */
  --shimmer-time: 1400ms;
  --stagger-step: 30ms;  /* шаг каскада, максимум 8 элементов */

  /* — Амплитуды — */
  --press-scale: .97;     /* обычная кнопка */
  --press-scale-sm: .94;  /* круглая иконка-кнопка ≤ 40px */
  --press-scale-lg: .985; /* карточка/плитка ≥ 200px */
  --enter-scale: .96;     /* никогда не 0 */
  --enter-y: 8px;
  --pop-y: -4px;          /* дропдаун «выпадает» на 4px */
  --lift-y: -2px;         /* ховер карточки */
  --shake-x: 4px;
  --vt-x: 24px;           /* сдвиг страницы при переходе вперёд/назад */
  --blur-mask: 2px;       /* маскировка кроссфейда, ≤ 20px */
  --sheet-from: 0 100%;
  --sheet-closed-opacity: 1;
  --drawer-from: -100% 0;

  /* — Цвета, которые использует кит (вариант переопределяет; по умолчанию — палитра владельца) — */
  --nx-accent: #FF6D1F;
  --nx-ink: #222222;
  --nx-paper: #FAF3E1;
  --nx-focus: #FF6D1F;
  --nx-scrim: rgb(15 15 15 / .42);
  --nx-toast-bg: #222222;
  --nx-toast-ink: #FAF3E1;
  --nx-skel: rgb(34 34 34 / .07);
  --nx-skel-shine: rgb(255 255 255 / .6);
  --nx-lift-shadow: 0 18px 40px -18px rgb(34 34 34 / .38);
  --nx-drop-tint: rgb(255 109 31 / .09);
}

/* Пружины в CSS (linear(): Chrome 113, Safari 17.2, Firefox 112). Сгенерированы по модели Motion:
   ω = 2π / (1.2 · visualDuration), ζ = 1 − bounce, масса 1; точки упрощены до погрешности 0.0025. */
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    /* bounce 0 (критическое затухание): форма одна для любой длительности */
    --ease-spring: linear(0, 0.003 1%, 0.019 2.5%, 0.046 4%, 0.08 5.5%, 0.162 8.5%, 0.362 15%, 0.45 18%, 0.543 21.5%, 0.624 25%, 0.694 28.5%, 0.752 32%, 0.801 35.5%, 0.846 39.5%, 0.886 44%, 0.918 49%, 0.944 54.5%, 0.965 61%, 0.978 67.5%, 0.988 75.5%, 1);
    /* bounce .25, visualDuration .4s → 590ms, перелёт 2.7% */
    --ease-bounce-on: linear(0, 0.006 1.5%, 0.024 3%, 0.061 5%, 0.111 7%, 0.217 10.5%, 0.452 17.5%, 0.548 20.5%, 0.65 24%, 0.739 27.5%, 0.804 30.5%, 0.867 34%, 0.911 37%, 0.95 40.5%, 0.98 44%, 1.003 48%, 1.019 52.5%, 1.027 57.5%, 1.027 66.5%, 1);
    /* bounce .35, visualDuration .5s → 940ms, перелёт 6.8% — только «праздник» */
    --ease-pop: linear(0, 0.005 1%, 0.027 2.5%, 0.065 4%, 0.114 5.5%, 0.172 7%, 0.236 8.5%, 0.513 14.5%, 0.622 17%, 0.721 19.5%, 0.807 22%, 0.879 24.5%, 0.948 27.5%, 0.991 30%, 1.029 33%, 1.046 35%, 1.059 37.5%, 1.066 40%, 1.068 43%, 1.06 48%, 1.016 63%, 1.002 71%, 0.995 83%, 1);
  }
}

/* Характер движения варианта: <html data-motion-style="crisp|soft|lively">. crisp — по умолчанию. */
:root[data-motion-style="soft"] {
  --ease: cubic-bezier(.32, .72, 0, 1);
  --fast: 180ms;
  --normal: 260ms;
  --slow: 380ms;
  --page: 260ms;
  --enter-y: 10px;
  --enter-scale: .97;
}
:root[data-motion-style="lively"] {
  --ease-bounce: var(--ease-bounce-on, var(--ease-spring));
  --press-scale: .96;
  --enter-y: 12px;
}

/* Уменьшение движения: системная настройка ИЛИ <html data-motion="reduce"> (настройка в профиле).
   Убираем перемещение и масштаб, оставляем прозрачность и цвет. data-motion="full" — принудительно полное. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) {
    --enter-scale: 1; --enter-y: 0px; --pop-y: 0px; --lift-y: 0px; --shake-x: 0px; --vt-x: 0px;
    --stagger-step: 0ms; --ease-bounce: var(--ease); --ease-pop: var(--ease);
    --sheet-from: 0 0; --sheet-closed-opacity: 0; --drawer-from: 0 0; --sheet: var(--normal);
  }
}
:root[data-motion="reduce"] {
  --enter-scale: 1; --enter-y: 0px; --pop-y: 0px; --lift-y: 0px; --shake-x: 0px; --vt-x: 0px;
  --stagger-step: 0ms; --ease-bounce: var(--ease); --ease-pop: var(--ease);
  --sheet-from: 0 0; --sheet-closed-opacity: 0; --drawer-from: 0 0; --sheet: var(--normal);
}

/* ================================================================
   2. БАЗА
   ================================================================ */
.nx-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.nx-tabular { font-variant-numeric: tabular-nums; }

@keyframes nx-fade-in { from { opacity: 0; } }
@keyframes nx-rise-in { from { opacity: 0; translate: 0 var(--enter-y); } }
@keyframes nx-spin { to { rotate: 360deg; } }
@keyframes nx-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

/* ================================================================
   3. НАЖАТИЕ И ХОВЕР
   Кит берёт на себя весь transition кнопки, чтобы не было конфликта двух transition.
   Вариант задаёт только цвета (и :hover внутри @media (hover:hover) and (pointer:fine)).
   ================================================================ */
.nx-press {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition:
    scale 160ms var(--ease),
    background-color var(--fast) var(--ease-hover),
    border-color var(--fast) var(--ease-hover),
    color var(--fast) var(--ease-hover),
    opacity var(--fast) var(--ease-hover);
}
.nx-press:active:not(:disabled):not([aria-disabled="true"]) { scale: var(--press-scale); transition-duration: var(--instant); }
.nx-press--sm:active:not(:disabled):not([aria-disabled="true"]) { scale: var(--press-scale-sm); }
.nx-press--lg:active:not(:disabled):not([aria-disabled="true"]) { scale: var(--press-scale-lg); }

/* Кликабельная карточка: подъём на 2px + тень через псевдоэлемент (анимируем opacity, а не box-shadow). */
.nx-lift { position: relative; transition: translate var(--normal) var(--ease), scale 160ms var(--ease); }
.nx-lift::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: var(--nx-lift-shadow); opacity: 0; transition: opacity var(--normal) var(--ease-hover);
}
@media (hover: hover) and (pointer: fine) {
  .nx-lift:hover { translate: 0 var(--lift-y); }
  .nx-lift:hover::after { opacity: 1; }
}
.nx-lift:active { scale: var(--press-scale-lg); transition-duration: var(--instant); }

/* ================================================================
   4. КНОПКА-ЗАГРУЗКА (nxBusy). Разметка создаётся JS, но лучше писать сразу:
   <button class="btn nx-press nx-busy"><span class="nx-busy__label">Сохранить</span>
     <span class="nx-busy__spin" aria-hidden="true"></span><span class="nx-busy__done" aria-hidden="true">…svg…</span></button>
   Ширина не прыгает: подпись остаётся в потоке (opacity 0), спиннер поверх по центру.
   ================================================================ */
:where(.nx-busy) { position: relative; }
.nx-busy__label {
  display: inline-flex; align-items: center; justify-content: center; gap: inherit;
  transition: opacity var(--fast) var(--ease), filter var(--fast) var(--ease);
}
.nx-busy__spin, .nx-busy__done {
  position: absolute; inset: 0; margin: auto; width: 1.15em; height: 1.15em;
  opacity: 0; scale: .6; pointer-events: none;
  transition: opacity var(--fast) var(--ease), scale var(--fast) var(--ease);
}
.nx-busy__spin {
  box-sizing: border-box; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent;
  animation: nx-spin var(--spinner-time) linear infinite; animation-play-state: paused;
}
.nx-busy__done svg { display: block; width: 100%; height: 100%; }
.nx-busy__done path { stroke-dasharray: 1; stroke-dashoffset: 1; }
[data-busy] { cursor: progress; }
[data-busy="pending"] .nx-busy__label { opacity: .6; }
[data-busy="loading"] .nx-busy__label,
[data-busy="done"] .nx-busy__label { opacity: 0; filter: blur(var(--blur-mask)); }
[data-busy="loading"] .nx-busy__spin { opacity: 1; scale: 1; animation-play-state: running; }
[data-busy="done"] .nx-busy__done { opacity: 1; scale: 1; }
[data-busy="done"] .nx-busy__done path { animation: nx-draw 360ms var(--ease) 60ms forwards; }

/* Отдельный спиннер (в полях, списках) */
.nx-spinner {
  display: inline-block; box-sizing: border-box; width: 1em; height: 1em; flex: none;
  border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent;
  animation: nx-spin var(--spinner-time) linear infinite;
}

/* Удержание для опасных действий (nxHold): заливка 1.2с linear, отпустил — назад за 200мс. */
.nx-hold { position: relative; overflow: hidden; }
.nx-hold__fill {
  position: absolute; inset: 0; pointer-events: none; background: var(--nx-hold-fill, rgb(255 255 255 / .24));
  clip-path: inset(0 100% 0 0); transition: clip-path 200ms var(--ease);
}
.nx-hold[data-holding] .nx-hold__fill { clip-path: inset(0 0 0 0); transition: clip-path var(--hold-time, 1200ms) linear; }

/* ================================================================
   5. ВЫПАДАШКИ И ПОПОВЕРЫ (Popover API + @starting-style), точка роста — от кнопки
   <button data-nx-popover="menu1">…</button>  <div id="menu1" popover class="nx-pop">…</div>
   ================================================================ */
.nx-pop {
  position: fixed; inset: auto; margin: 0;
  transform-origin: var(--nx-origin, 50% 0);
  opacity: 1; scale: 1; translate: 0 0;
  transition:
    opacity var(--normal) var(--ease), scale var(--normal) var(--ease), translate var(--normal) var(--ease),
    display var(--normal) allow-discrete, overlay var(--normal) allow-discrete;
}
.nx-pop:not(:popover-open) { opacity: 0; scale: .98; transition-duration: var(--fast); }
@starting-style {
  .nx-pop:popover-open { opacity: 0; scale: var(--enter-scale); translate: 0 var(--nx-pop-y, var(--pop-y)); }
}

/* Те же значения для Alpine (x-show) — см. motion-kit.md, раздел «Alpine» */
.nx-t { transform-origin: var(--nx-origin, 50% 0); transition: opacity var(--normal) var(--ease), scale var(--normal) var(--ease), translate var(--normal) var(--ease); }
.nx-t-out { transition-duration: var(--fast); }
.nx-t-from { opacity: 0; scale: var(--enter-scale); translate: 0 var(--pop-y); }
.nx-t-to { opacity: 1; scale: 1; translate: 0 0; }
.nx-t-exit { opacity: 0; scale: .98; }

/* Тултип-одиночка (создаёт JS для [data-nx-tip]); не обрезается overflow родителя */
.nx-tip {
  position: fixed; z-index: 130; left: 0; top: 0; pointer-events: none; max-width: 240px;
  padding: 6px 10px; border-radius: 8px; background: var(--nx-ink); color: var(--nx-paper);
  font-size: 12px; line-height: 1.35; white-space: nowrap;
  opacity: 0; scale: .97; transform-origin: var(--nx-origin, 0 50%);
  transition: opacity 125ms var(--ease), scale 125ms var(--ease);
}
.nx-tip[data-on] { opacity: 1; scale: 1; }
.nx-tip[data-instant] { transition-duration: 0ms; }

/* ================================================================
   6. МОДАЛКА → ШТОРКА НА ТЕЛЕФОНЕ (<dialog class="nx-dialog">, nxDialog.open/close)
   ================================================================ */
dialog.nx-dialog {
  opacity: 0; scale: var(--enter-scale); transform-origin: 50% 50%;   /* модалка — из центра, не от кнопки */
  transition: opacity var(--fast) var(--ease), scale var(--fast) var(--ease), translate var(--fast) var(--ease);
}
dialog.nx-dialog[data-open] { opacity: 1; scale: 1; transition-duration: var(--normal); }
/* ::backdrop в старых браузерах не наследует переменные — значения литералами (= --fast/--normal, --ease) */
dialog.nx-dialog::backdrop { background: rgb(15 15 15 / .42); opacity: 0; transition: opacity 150ms cubic-bezier(.23, 1, .32, 1); }
dialog.nx-dialog[data-open]::backdrop { opacity: 1; transition-duration: 220ms; }

@media (max-width: 767.98px) {
  dialog.nx-dialog {
    position: fixed; inset: auto 0 0 0; margin: 0; width: 100%; max-width: none;
    max-height: calc(100dvh - 24px); border-radius: 24px 24px 0 0;
    padding-bottom: max(16px, env(safe-area-inset-bottom));
    opacity: var(--sheet-closed-opacity); scale: 1; translate: var(--sheet-from);
    transition: translate 280ms var(--ease-drawer), opacity 280ms var(--ease);
    touch-action: pan-y;
  }
  dialog.nx-dialog[data-open] { opacity: 1; translate: 0 0; transition-duration: var(--sheet); }
  dialog.nx-dialog[data-dragging] { transition: none; }
  dialog.nx-dialog[data-settle] { transition: translate var(--spring) var(--ease-spring); }
  dialog.nx-dialog[data-open]::backdrop { transition-duration: 420ms; }
}
.nx-grabber { display: none; position: relative; width: 36px; height: 5px; margin: 8px auto 4px; border-radius: 3px; background: currentColor; opacity: .22; }
.nx-grabber::after { content: ""; position: absolute; inset: -12px -60px; }   /* зона захвата 60×29px */
@media (max-width: 767.98px) {
  .nx-grabber { display: block; }
  dialog.nx-dialog .nx-grabber, dialog.nx-dialog [data-nx-drag] { touch-action: none; }
  :root:has(dialog.nx-dialog[open]) { overflow: hidden; }   /* фон под шторкой не прокручивается */
  dialog.nx-dialog { overscroll-behavior: contain; }
}

/* Боковая выезжающая панель (карточка лида справа на десктопе, меню слева на телефоне) */
.nx-drawer { translate: var(--drawer-from); opacity: var(--sheet-closed-opacity); transition: translate 260ms var(--ease-drawer), opacity 260ms var(--ease); }
.nx-drawer--right { --drawer-from: 100% 0; }
.nx-drawer[data-open] { translate: 0 0; opacity: 1; transition-duration: var(--sheet); }

/* ================================================================
   7. БОКОВОЕ МЕНЮ: свёртка 248 ↔ 76px (nxSidebar)
   Колонка сетки меняется мгновенно, меню анимирует свою ширину поверх, контент едет FLIP-ом (transform).
   ================================================================ */
:root { --sb-w-open: 248px; --sb-w-closed: 76px; --sb-w: var(--sb-w-open); }
:root[data-sidebar="collapsed"] { --sb-w: var(--sb-w-closed); }
.nx-side { width: var(--sb-w); justify-self: start; transition: width var(--slow) var(--ease-drawer); }
.nx-side__label { white-space: nowrap; overflow: hidden; transition: opacity var(--fast) var(--ease) 90ms; }
:root[data-sidebar="collapsed"] .nx-side__label { opacity: 0; transition-delay: 0ms; transition-duration: var(--instant); }
.nx-side__chevron { transition: rotate var(--slow) var(--ease-drawer); }
:root[data-sidebar="collapsed"] .nx-side__chevron { rotate: 180deg; }
:root[data-sidebar-instant] :is(.nx-side, .nx-side__label, .nx-side__chevron) { transition: none; }

/* ================================================================
   8. ВКЛАДКИ С БЕГУНКОМ (nxTabs)
   ================================================================ */
.nx-tabs { position: relative; display: flex; gap: 4px; isolation: isolate; overflow-x: auto; scrollbar-width: none; }
.nx-tabs::-webkit-scrollbar { display: none; }
.nx-tabs__ink {
  position: absolute; z-index: -1; left: 0; top: 0; width: 0; height: 100%;
  border-radius: 999px; background: var(--nx-tab-ink, var(--nx-ink)); pointer-events: none;
  transition: translate var(--normal) var(--ease-in-out), width var(--normal) var(--ease-in-out), height var(--normal) var(--ease-in-out);
}
.nx-tabs [role="tab"] { transition: color var(--normal) var(--ease-hover); }

/* ================================================================
   9. ПЕРЕКЛЮЧАТЕЛЬ, ЧЕКБОКС, РАДИО
   <input type="checkbox" role="switch" class="nx-switch">   <input type="checkbox" class="nx-check">
   ================================================================ */
.nx-switch {
  -webkit-appearance: none; appearance: none; position: relative; flex: none;
  width: 44px; height: 26px; margin: 0; border-radius: 999px; cursor: pointer;
  background: var(--nx-switch-off, rgb(34 34 34 / .2));
  transition: background-color 200ms var(--ease-hover);
}
.nx-switch::after { content: ""; position: absolute; inset: -9px -4px; }           /* зона нажатия ≥ 44px */
.nx-switch::before {
  content: ""; position: absolute; top: 2px; left: 2px; width: 22px; height: 22px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgb(0 0 0 / .25); transform-origin: 0 50%;
  transition: translate var(--spring-fast) var(--ease-bounce), scale var(--fast) var(--ease);
}
.nx-switch:checked { background: var(--nx-accent); }
.nx-switch:checked::before { translate: 18px 0; transform-origin: 100% 50%; }
.nx-switch:active::before { scale: 1.14 1; }
.nx-switch:focus-visible, .nx-check:focus-visible, .nx-radio:focus-visible { outline: 2px solid var(--nx-focus); outline-offset: 2px; }

.nx-check {
  -webkit-appearance: none; appearance: none; position: relative; flex: none;
  width: 20px; height: 20px; margin: 0; border-radius: 6px; cursor: pointer;
  border: 1.5px solid var(--nx-check-border, rgb(34 34 34 / .35)); background: transparent;
  transition: background-color var(--fast) var(--ease-hover), border-color var(--fast) var(--ease-hover), scale 160ms var(--ease);
}
.nx-check::before {
  content: ""; position: absolute; inset: 1px; background: #fff;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") center / contain no-repeat;
  clip-path: inset(0 100% 0 0); transition: clip-path 180ms var(--ease);
}
.nx-check:checked { background: var(--nx-accent); border-color: var(--nx-accent); }
.nx-check:checked::before { clip-path: inset(0 0 0 0); transition-delay: 40ms; }
.nx-check:active { scale: .9; transition-duration: var(--instant); }

.nx-radio {
  -webkit-appearance: none; appearance: none; position: relative; flex: none;
  width: 20px; height: 20px; margin: 0; border-radius: 50%; cursor: pointer;
  border: 1.5px solid var(--nx-check-border, rgb(34 34 34 / .35));
  transition: border-color var(--fast) var(--ease-hover);
}
.nx-radio::before { content: ""; position: absolute; inset: 3.5px; border-radius: 50%; background: var(--nx-accent); scale: 0.4; opacity: 0; transition: scale var(--fast) var(--ease), opacity var(--fast) var(--ease); }
.nx-radio:checked { border-color: var(--nx-accent); }
.nx-radio:checked::before { scale: 1; opacity: 1; }

/* ================================================================
   10. ТОСТЫ (nxToast) — модель Sonner: стопка, свайп, пауза
   ================================================================ */
.nx-toaster {
  position: fixed; z-index: 120; left: 50%; translate: -50% 0;
  bottom: calc(16px + env(safe-area-inset-bottom) + var(--nx-bottom-ui, 0px));
  width: min(380px, calc(100vw - 32px)); pointer-events: none;
}
.nx-toast {
  position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box;
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 12px 12px 12px 16px;
  border-radius: 18px; color: var(--nx-toast-ink); background: var(--nx-toast-bg);
  box-shadow: 0 12px 32px -12px rgb(0 0 0 / .45);
  font-size: 14px; line-height: 1.35; pointer-events: auto; touch-action: none; user-select: none; -webkit-user-select: none;
  opacity: 0;
  transition: transform var(--toast-time) ease, opacity var(--toast-time) ease;
}
.nx-toast::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 10px; }   /* заполняет зазор: ховер не теряется */
.nx-toast[data-mounted] { opacity: 1; }
.nx-toast[data-hidden] { opacity: 0; pointer-events: none; }
.nx-toast[data-removed] { opacity: 0; pointer-events: none; transition-duration: 200ms; }
.nx-toast[data-swiping] { transition: none; }
.nx-toast > * { transition: opacity var(--toast-time) ease; }
.nx-toaster:not([data-expanded]) .nx-toast:not([data-front]) > * { opacity: 0; }
.nx-toast__icon { flex: none; width: 18px; height: 18px; display: grid; place-items: center; }
.nx-toast__icon::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; opacity: .6; }
.nx-toast--success .nx-toast__icon::before { background: #34d399; opacity: 1; }
.nx-toast--error .nx-toast__icon::before { background: #f87171; opacity: 1; }
.nx-toast--warning .nx-toast__icon::before { background: #fbbf24; opacity: 1; }
.nx-toast--info .nx-toast__icon::before { background: var(--nx-accent); opacity: 1; }
.nx-toast--loading .nx-toast__icon::before { display: none; }
.nx-toast__text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.nx-toast__action {
  flex: none; min-height: 32px; padding: 0 12px; border: 1px solid rgb(255 255 255 / .35); border-radius: 999px;
  color: inherit; background: transparent; font: inherit; font-weight: 600; cursor: pointer;
}
.nx-toast__close {
  flex: none; width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 50%;
  color: inherit; background: rgb(255 255 255 / .1); cursor: pointer; opacity: 0; transition: opacity var(--fast) var(--ease-hover);
}
.nx-toast:focus-within .nx-toast__close { opacity: 1; }
@media (hover: hover) and (pointer: fine) { .nx-toast:hover .nx-toast__close { opacity: 1; } }
@media (hover: none) { .nx-toast__close { display: none; } }   /* на телефоне закрывают свайпом */

/* ================================================================
   11. СКЕЛЕТОН И ПОЯВЛЕНИЕ ДАННЫХ
   ================================================================ */
.nx-skel { position: relative; overflow: hidden; border-radius: 8px; background: var(--nx-skel); }
.nx-skel::after {
  content: ""; position: absolute; inset: 0; translate: -100% 0;
  background: linear-gradient(90deg, transparent, var(--nx-skel-shine), transparent);
  animation: nx-shimmer var(--shimmer-time) linear infinite;
}
@keyframes nx-shimmer { to { translate: 100% 0; } }
.nx-loaded { animation: nx-fade-in var(--normal) var(--ease) both; }

/* ================================================================
   12. СПИСКИ: каскад (CSS-вариант; JS-вариант — nxStagger)
   <ul class="nx-stagger" data-nx-play> — анимация только когда есть data-nx-play
   ================================================================ */
.nx-stagger > * { --i: 0; }
@supports (order: sibling-index()) { .nx-stagger > * { --i: calc(sibling-index() - 1); } }
[data-nx-play].nx-stagger > * {
  animation: nx-rise-in 260ms var(--ease) both;
  animation-delay: calc(min(var(--i), 7) * var(--stagger-step));
}

/* Аккордеон без JS-измерений: grid-template-rows 0fr → 1fr */
.nx-acc { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--normal) var(--ease); }
.nx-acc > * { min-height: 0; overflow: hidden; }
.nx-acc[data-open], [aria-expanded="true"] + .nx-acc { grid-template-rows: 1fr; }
details.nx-details { interpolate-size: allow-keywords; }   /* Chrome 129+: плавно и закрытие; остальные — мгновенно */
details.nx-details::details-content { block-size: 0; overflow: clip; transition: block-size var(--normal) var(--ease), content-visibility var(--normal) allow-discrete; }
details.nx-details[open]::details-content { block-size: auto; }
.nx-chevron { transition: rotate var(--normal) var(--ease); }
[aria-expanded="true"] > .nx-chevron, details[open] > summary .nx-chevron { rotate: 180deg; }

/* ================================================================
   13. ЧАТ: сообщения, «печатает», новые снизу
   ================================================================ */
.nx-msg--new {
  transform-origin: 0 100%;
  transition: opacity var(--normal) var(--ease), translate var(--normal) var(--ease), scale var(--normal) var(--ease);
}
.nx-msg--new.nx-msg--own { transform-origin: 100% 100%; transition-duration: 180ms; }
@starting-style { .nx-msg--new { opacity: 0; translate: 0 var(--enter-y); scale: .98; } }

.nx-typing {
  display: inline-flex; align-items: center; gap: 4px; padding: 12px 14px; border-radius: 18px 18px 18px 6px;
  background: var(--nx-bubble, rgb(34 34 34 / .06)); transform-origin: 0 100%;
  transition: opacity 160ms var(--ease), scale 160ms var(--ease);
}
@starting-style { .nx-typing { opacity: 0; scale: .9; } }
.nx-typing i { display: block; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .4; animation: nx-typing 1.2s ease-in-out infinite; }
.nx-typing i:nth-child(2) { animation-delay: 150ms; }
.nx-typing i:nth-child(3) { animation-delay: 300ms; }
@keyframes nx-typing { 0%, 60%, 100% { translate: 0 0; opacity: .4; } 30% { translate: 0 -3px; opacity: 1; } }
@keyframes nx-typing-r { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

.nx-newmsg {
  position: absolute; left: 50%; bottom: 12px; translate: -50% 0; z-index: 5;
  transition: opacity var(--normal) var(--ease), translate var(--normal) var(--ease);
}
.nx-newmsg[hidden] { display: block !important; opacity: 0; translate: -50% var(--enter-y); pointer-events: none; }

/* AI «думает»: переливающийся текст (только пока идёт генерация) */
.nx-ai-shimmer {
  background: linear-gradient(90deg, currentColor 35%, var(--nx-accent) 50%, currentColor 65%) 0 0 / 300% 100%;
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
  animation: nx-text-shimmer 1.6s linear infinite;
}
@keyframes nx-text-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* AI-обводка поля ввода во время генерации — НЕ БОЛЬШЕ ОДНОЙ на экране */
@property --nx-angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.nx-ai-glow { position: relative; isolation: isolate; }
.nx-ai-glow::before {
  content: ""; position: absolute; inset: -1.5px; z-index: -1; border-radius: inherit; padding: 1.5px; pointer-events: none;
  background: conic-gradient(from var(--nx-angle), #FF6D1F, #F5E7C6, #FF6D1F 40%, #222222 60%, #FF6D1F);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0; transition: opacity var(--normal) var(--ease);
}
.nx-ai-glow[data-ai-busy]::before { opacity: 1; animation: nx-angle 3s linear infinite; }
@keyframes nx-angle { to { --nx-angle: 360deg; } }

/* ================================================================
   14. ИКОНКИ (inline SVG). Одноразовые анимации — класс nx-play-<имя> (ставит nxIcon), постоянные — атрибуты.
   Линия «рисуется»: у <path> нужны pathLength="1" и data-draw.
   ================================================================ */
.nx-ico { display: inline-block; width: 1.25em; height: 1.25em; flex: none; overflow: visible; vertical-align: middle; }
.nx-ico [data-draw] { stroke-dasharray: 1; }
.nx-ico [data-part] { transform-box: fill-box; }

.nx-play-ring { transform-origin: 50% 8%; animation: nx-ring 700ms var(--ease) both; }
@keyframes nx-ring { 0% { rotate: 0deg; } 12% { rotate: 16deg; } 28% { rotate: -14deg; } 44% { rotate: 10deg; } 60% { rotate: -6deg; } 76% { rotate: 3deg; } 100% { rotate: 0deg; } }
.nx-play-draw [data-draw] { animation: nx-draw 420ms var(--ease) both; }
.nx-play-draw [data-draw]:nth-of-type(2) { animation-delay: 120ms; }
.nx-play-nudge { animation: nx-nudge 420ms var(--ease-in-out) both; }
@keyframes nx-nudge { 0%, 100% { translate: 0 0; } 45% { translate: 3px 0; } }
.nx-play-spin { animation: nx-spin 600ms var(--ease-in-out) both; }
.nx-ico[data-spinning] { animation: nx-spin 800ms linear infinite; }
.nx-play-fly { animation: nx-fly 520ms var(--ease-in-out) both; }
@keyframes nx-fly { 0% { translate: 0 0; opacity: 1; } 40% { translate: 9px -9px; opacity: 0; } 41% { translate: -9px 9px; opacity: 0; } 100% { translate: 0 0; opacity: 1; } }
.nx-play-pop { animation: nx-pop 380ms var(--ease) both; }
@keyframes nx-pop { 0% { scale: 1; } 35% { scale: 1.22; } 100% { scale: 1; } }
.nx-play-wiggle { animation: nx-wiggle 320ms var(--ease-in-out) both; }
@keyframes nx-wiggle { 0%, 100% { translate: 0 0; } 20% { translate: calc(var(--shake-x) * -1) 0; } 40% { translate: var(--shake-x) 0; } 60% { translate: calc(var(--shake-x) * -.6) 0; } 80% { translate: calc(var(--shake-x) * .6) 0; } }
.nx-play-blink { transform-origin: 50% 50%; animation: nx-blink 240ms ease-in-out both; }
@keyframes nx-blink { 0%, 100% { scale: 1 1; } 50% { scale: 1 .1; } }

/* Ховер-детали (переходы, не ключевые кадры): крышка корзины, стрелка, «гамбургер ↔ крестик» */
.nx-ico [data-part="lid"] { transform-origin: 90% 100%; transition: rotate 200ms var(--ease), translate 200ms var(--ease); }
@media (hover: hover) and (pointer: fine) {
  :hover > .nx-ico [data-part="lid"] { rotate: -14deg; translate: 0 -1px; }
  :hover > .nx-ico [data-part="arrow"] { translate: 2px 0; }
}
.nx-ico [data-part="arrow"] { transition: translate 200ms var(--ease); }
.nx-ico [data-part="top"], .nx-ico [data-part="mid"], .nx-ico [data-part="bot"] { transform-origin: 50% 50%; transition: rotate var(--normal) var(--ease-in-out), translate var(--normal) var(--ease-in-out), opacity var(--fast) var(--ease); }
[aria-expanded="true"] > .nx-ico [data-part="top"] { translate: 0 6px; rotate: 45deg; }
[aria-expanded="true"] > .nx-ico [data-part="mid"] { opacity: 0; }
[aria-expanded="true"] > .nx-ico [data-part="bot"] { translate: 0 -6px; rotate: -45deg; }
/* AI-искры мерцают, только пока AI работает */
[data-ai-busy] .nx-ico [data-part="spark"] { transform-origin: 50% 50%; animation: nx-twinkle 1.2s ease-in-out infinite; }
[data-ai-busy] .nx-ico [data-part="spark"]:nth-of-type(2) { animation-delay: 200ms; }
[data-ai-busy] .nx-ico [data-part="spark"]:nth-of-type(3) { animation-delay: 400ms; }
@keyframes nx-twinkle { 0%, 100% { scale: .7; opacity: .5; } 50% { scale: 1; opacity: 1; } }
/* Запись голосового: пульс-кольцо */
.nx-rec { position: relative; }
.nx-rec::before { content: ""; position: absolute; inset: 0; border-radius: 50%; background: currentColor; opacity: .35; animation: nx-rec 1.2s var(--ease) infinite; }
@keyframes nx-rec { from { scale: 1; opacity: .35; } to { scale: 1.8; opacity: 0; } }

/* ================================================================
   15. КАНБАН (SortableJS: классы по умолчанию) — см. nxKanban
   ================================================================ */
.nx-col { transition: background-color var(--instant) var(--ease-hover); }
.nx-col.is-over { background-color: var(--nx-drop-tint); }
.nx-kanban .sortable-ghost { opacity: .35; outline: 2px dashed currentColor; outline-offset: -2px; }
.sortable-fallback.nx-card, .nx-kanban .sortable-drag {
  opacity: 1 !important; rotate: 2deg; scale: 1.03; box-shadow: var(--nx-lift-shadow); cursor: grabbing;
  transition: rotate var(--fast) var(--ease), scale var(--fast) var(--ease);
}
@starting-style { .sortable-fallback.nx-card { rotate: 0deg; scale: 1; } }
.nx-kanban[data-dragging] { scroll-snap-type: none !important; }
.nx-kanban[data-dragging] * { cursor: grabbing; }

/* ================================================================
   16. ГРАФИКИ И ЦИФРЫ — только при первом показе за сессию (контейнер с data-nx-play)
   ================================================================ */
.nx-bars > * { --i: 0; transform-origin: 50% 100%; }
@supports (order: sibling-index()) { .nx-bars > * { --i: calc(sibling-index() - 1); } }
[data-nx-play] .nx-bars > * { animation: nx-grow-y 480ms var(--ease) both; animation-delay: calc(min(var(--i), 11) * 20ms); }
@keyframes nx-grow-y { from { scale: 1 0; } }
.nx-line-draw { stroke-dasharray: 1; }                       /* <path pathLength="1" class="nx-line-draw"> */
[data-nx-play] .nx-line-draw { animation: nx-draw 700ms var(--ease-in-out) both; }
[data-nx-play] .nx-chart-fade { animation: nx-fade-in 300ms var(--ease) 300ms both; }
.nx-bars--live > * { transition: scale 300ms var(--ease-in-out); }   /* смена периода: столбик меняет высоту, не растёт с нуля */

/* ================================================================
   17. ПРОКРУТКА (только лендинг/онбординг): появление при прокрутке
   ================================================================ */
.nx-js .nx-reveal { opacity: 0; translate: 0 16px; transition: opacity 600ms var(--ease), translate 600ms var(--ease); }
.nx-js .nx-reveal.is-in { opacity: 1; translate: 0 0; }

/* ================================================================
   18. ПЕРЕХОДЫ МЕЖДУ СТРАНИЦАМИ (cross-document View Transitions: Chrome 126, Safari 18.2; Firefox — мгновенно)
   Классы: .nx-vt-main (контент), .nx-vt-side, .nx-vt-top, .nx-vt-tabbar (неподвижные части).
   Направление: <a data-vt="forward|back"> + скрипт в <head> (см. motion-kit.md).
   ================================================================ */
@view-transition { navigation: auto; }
.nx-vt-main { view-transition-name: nx-main; }
.nx-vt-side { view-transition-name: nx-side; }
.nx-vt-top { view-transition-name: nx-top; }
.nx-vt-tabbar { view-transition-name: nx-tabbar; }
::view-transition-group(root) { animation-duration: var(--page); }
::view-transition-group(nx-main) { animation-duration: var(--page); animation-timing-function: var(--ease); }
::view-transition-old(nx-main) { animation: nx-vt-out var(--fast) var(--ease) both; }
::view-transition-new(nx-main) { animation: nx-vt-in var(--page) var(--ease) 40ms both; }
::view-transition-group(nx-side), ::view-transition-group(nx-top), ::view-transition-group(nx-tabbar),
::view-transition-new(nx-side), ::view-transition-new(nx-top), ::view-transition-new(nx-tabbar) { animation: none; }
::view-transition-old(nx-side), ::view-transition-old(nx-top), ::view-transition-old(nx-tabbar) { animation: none; opacity: 0; }
@keyframes nx-vt-out { to { opacity: 0; } }
@keyframes nx-vt-in { from { opacity: 0; translate: 0 var(--enter-y); } }
@keyframes nx-vt-in-fwd { from { opacity: 0; translate: var(--vt-x) 0; } }
@keyframes nx-vt-in-back { from { opacity: 0; translate: calc(var(--vt-x) * -1) 0; } }
:root:active-view-transition-type(forward)::view-transition-new(nx-main) { animation-name: nx-vt-in-fwd; }
:root:active-view-transition-type(back)::view-transition-new(nx-main) { animation-name: nx-vt-in-back; }

/* ================================================================
   19. УМЕНЬШЕНИЕ ДВИЖЕНИЯ — компоненты (токены выше уже обнулили сдвиги)
   ================================================================ */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) .nx-skel::after { animation: none; opacity: 0; }
  :root:not([data-motion="full"]) .nx-typing i { animation-name: nx-typing-r; }
  :root:not([data-motion="full"]) :is([class*="nx-play-"], [class*="nx-play-"] *) { animation: none !important; }
  :root:not([data-motion="full"]) .nx-ai-glow[data-ai-busy]::before, :root:not([data-motion="full"]) .nx-ai-shimmer, :root:not([data-motion="full"]) .nx-rec::before { animation: none; }
  :root:not([data-motion="full"]) [data-ai-busy] .nx-ico [data-part="spark"] { animation: none; }
  :root:not([data-motion="full"]) [data-nx-play] :is(.nx-bars > *, .nx-line-draw) { animation: none; }
  :root:not([data-motion="full"]) .nx-toast { transition-property: opacity; }
  :root:not([data-motion="full"]) .nx-side { transition: none; }
  :root:not([data-motion="full"]) .nx-switch::before { transition-duration: var(--instant); }
}
:root[data-motion="reduce"] .nx-skel::after { animation: none; opacity: 0; }
:root[data-motion="reduce"] .nx-typing i { animation-name: nx-typing-r; }
:root[data-motion="reduce"] :is([class*="nx-play-"], [class*="nx-play-"] *) { animation: none !important; }
:root[data-motion="reduce"] .nx-ai-glow[data-ai-busy]::before, :root[data-motion="reduce"] .nx-ai-shimmer, :root[data-motion="reduce"] .nx-rec::before { animation: none; }
:root[data-motion="reduce"] [data-ai-busy] .nx-ico [data-part="spark"] { animation: none; }
:root[data-motion="reduce"] [data-nx-play] :is(.nx-bars > *, .nx-line-draw) { animation: none; }
:root[data-motion="reduce"] .nx-toast { transition-property: opacity; }
:root[data-motion="reduce"] .nx-side { transition: none; }
:root[data-motion="reduce"] .nx-switch::before { transition-duration: var(--instant); }

/* Меньше прозрачности (Chrome 118+; Safari пока нет): стекло → почти сплошное */
@media (prefers-reduced-transparency: reduce) {
  .nx-glass { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; background-color: var(--nx-glass-solid, rgb(250 243 225 / .97)) !important; }
}
