/* UGC Пульс — движение (16.10.2026, «премиальные переходы»). Подключается последним. Только transform и opacity (и clip-path у
   «рисования»); кривые и длительности — токены из tokens.css; уход быстрее прихода; при prefers-reduced-motion — мгновенно.
   Поведение, которому нужен JS (переход разделов, подсчёт цифр, свайп шторки, раскрытие разделов, индикатор сегментов), —
   web/app-motion.js. Описание — docs/design-system.md, раздел «Движение». */

/* ---------------------------------------------------------------- нажатие: мгновенно вниз, мягко обратно */
.btn, .more-btn, .nav button, .segmented button, .sheet-item, .chip-x, .dialog-close, .guide-close, .tip-btn, .attention-toggle, .demo-roles button, .exit, .project-chip {
  transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform 160ms var(--ease-out);
}
.btn:active:not(:disabled), .more-btn:active, .segmented button:active, .chip-x:active, .dialog-close:active, .guide-close:active, .tip-btn:active,
.attention-toggle:active, .demo-roles button:active, .exit:active { transform: scale(.97); transition-duration: 40ms; }
.nav button:active { transform: scale(.98); transition-duration: 40ms; }
.sheet-item:active { transform: scale(.99); transition-duration: 40ms; }

/* наведение — только там, где есть мышь: карточки чуть приподнимаются */
@media (hover: hover) and (pointer: fine) {
  .brief-card, .member-card { transition: transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out); }
  .brief-card:hover, .member-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-raise); }
}

/* ---------------------------------------------------------------- подложка выбранного пункта меню (общий элемент перехода: скользит к новому пункту);
   заголовок страницы меняется вместе с содержимым — у слов разной длины «перетекание» выглядит как растяжение */
.nav button { isolation: isolate; }
.nav button.active { background: transparent; }
.nav-pill { position: absolute; inset: 0; z-index: -1; border-radius: 8px; background: rgba(0, 0, 0, .07); view-transition-name: nav-pill; pointer-events: none; }
@media (max-width: 680px) {
  .nav-pill { inset: 3px auto auto 50%; width: 56px; height: 30px; margin-left: -28px; border-radius: 15px; background: rgba(0, 0, 0, .06); }
}
.workspace { view-transition-name: workspace; }

/* ---------------------------------------------------------------- переход между разделами (View Transitions) */
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
/* старое исчезает сразу, новое проявляется на своём месте: ни одного кадра, где две страницы видны друг сквозь друга, и никакого
   «перелёта» снимка, когда страницы разной высоты или прокрутка сбросилась (группа не анимирует размер и положение) */
::view-transition-group(workspace) { animation: none; }
::view-transition-old(workspace) { animation: none; opacity: 0; }
/* новое — тоже сразу: проявление снимка с нуля давало один совсем пустой кадр (на месте раздела и шапки — только фон, видно на
   компьютере как мигание, 16.10.2026); мягкость даёт каскад блоков внутри (.workspace.enter, от .4), шапка не мигает вовсе */
::view-transition-new(workspace) { animation: none; }
::view-transition-group(nav-pill) { animation-duration: 320ms; animation-timing-function: var(--spring); }
::view-transition-old(nav-pill), ::view-transition-new(nav-pill) { animation: none; }
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; } }

/* ---------------------------------------------------------------- появление раздела: подъём и каскад (до 8 элементов) */
@keyframes rise { from { opacity: 0; transform: translateY(var(--rise)); } }
/* смена раздела: блоки не начинают с полной прозрачности — иначе первый кадр нового раздела пустой и читается как мигание */
@keyframes rise-soft { from { opacity: .4; transform: translateY(calc(var(--rise) * .6)); } }
@keyframes fade { from { opacity: 0; } }
@keyframes rise-glide { from { opacity: 0; transform: translate3d(0, var(--rise-glide), 0); } }
.workspace.enter > .page-head, .workspace.enter > .filters, .workspace.enter > .notice, .workspace.enter > .guide-banner,
.workspace.enter > .attention-more, .workspace.enter > .segmented, .workspace.enter > .ov-hero, .workspace.enter > .panel,
.workspace.enter > section, .workspace.enter > article, .workspace.enter > details, .workspace.enter > .subheading {
  animation: rise-soft var(--dur-3) var(--ease-out) both;
}
.workspace.enter > .page-head { animation: none; }
.workspace.enter > :nth-child(3) { animation-delay: calc(var(--stagger) * 1); }
.workspace.enter > :nth-child(4) { animation-delay: calc(var(--stagger) * 2); }
.workspace.enter > :nth-child(5) { animation-delay: calc(var(--stagger) * 3); }
.workspace.enter > :nth-child(6) { animation-delay: calc(var(--stagger) * 4); }
.workspace.enter > :nth-child(7) { animation-delay: calc(var(--stagger) * 5); }
.workspace.enter > :nth-child(n+8) { animation-delay: calc(var(--stagger) * 6); }
/* плитки, карточки и первые строки таблиц — своим каскадом внутри блока */
.workspace.enter .stats > *, .workspace.enter .cards > *, .workspace.enter .table tbody tr:nth-child(-n+8), .workspace.enter .pub-card:nth-child(-n+8) {
  animation: rise-soft var(--dur-3) var(--ease-out) both;
}
.workspace.enter :is(.stats, .cards) > :nth-child(2), .workspace.enter .table tbody tr:nth-child(2) { animation-delay: calc(var(--stagger) * 1 + 40ms); }
.workspace.enter :is(.stats, .cards) > :nth-child(3), .workspace.enter .table tbody tr:nth-child(3) { animation-delay: calc(var(--stagger) * 2 + 40ms); }
.workspace.enter :is(.stats, .cards) > :nth-child(4), .workspace.enter .table tbody tr:nth-child(4) { animation-delay: calc(var(--stagger) * 3 + 40ms); }
.workspace.enter :is(.stats, .cards) > :nth-child(5), .workspace.enter .table tbody tr:nth-child(5) { animation-delay: calc(var(--stagger) * 4 + 40ms); }
.workspace.enter :is(.stats, .cards) > :nth-child(6), .workspace.enter .table tbody tr:nth-child(6) { animation-delay: calc(var(--stagger) * 5 + 40ms); }
.workspace.enter :is(.stats, .cards) > :nth-child(n+7), .workspace.enter .table tbody tr:nth-child(n+7) { animation-delay: calc(var(--stagger) * 6 + 40ms); }
/* каскад внутри блока, который сам проявляется (карточки заданий в section.cards, строки таблицы в панели): только подъём, без
   своей прозрачности — иначе прозрачности перемножаются (.4 × .4) и первый кадр раздела почти пустой: это и есть «мигание» */
@keyframes rise-move { from { transform: translateY(calc(var(--rise) * .6)); } }
.workspace.enter > :is(.notice, .guide-banner, .attention-more, .ov-hero, .panel, section, article, details) :is(.stats > *, .cards > *, .table tbody tr, .pub-card),
.workspace.enter > :is(section, article).cards > * { animation-name: rise-move; }
.workspace.enter .chart .chart-pt, .workspace.enter .ovh-dot, .workspace.enter .ovh-caps { animation: fade var(--dur-3) var(--ease-out) 700ms both; }
/* нажали пункт меню, а данные раздела ещё идут (медленная сеть): старый раздел тихо приглушается (app-live.js) */
.workspace { transition: opacity var(--dur-2) var(--ease-out); }
.workspace.live-wait { opacity: .55; pointer-events: none; }
/* вход в кабинет со страницы входа: мягкое проявление без белой вспышки (фон у обоих один, #f5f5f7) */
.shell.shell-in { animation: fade var(--dur-glide) var(--ease-glide) both; }

/* ---------------------------------------------------------------- вход: каскад при первом показе */
html[data-intro] .login-art > :not(.pulse-scene), html[data-intro] .login-box, html[data-intro] .login-box > *, html[data-intro] .login-box .form > * {
  animation: rise-glide var(--dur-glide) var(--ease-glide) both;
}
html[data-intro] .login-art > :nth-child(2) { animation-delay: 60ms; }
html[data-intro] .login-art > :nth-child(3) { animation-delay: 120ms; }
html[data-intro] .login-art > :nth-child(n+5) { animation-delay: 400ms; }
html[data-intro] .login-box { animation-delay: 140ms; }
html[data-intro] .login-box > :nth-child(1) { animation-delay: 200ms; }
html[data-intro] .login-box > :nth-child(2) { animation-delay: 230ms; }
html[data-intro] .login-box .form > :nth-child(1) { animation-delay: 260ms; }
html[data-intro] .login-box .form > :nth-child(2) { animation-delay: 285ms; }
html[data-intro] .login-box .form > :nth-child(3) { animation-delay: 310ms; }
html[data-intro] .login-box .form > :nth-child(4) { animation-delay: 335ms; }
html[data-intro] .login-box .form > :nth-child(n+5) { animation-delay: 360ms; }
html[data-intro] .login-box > :nth-child(n+4) { animation-delay: 400ms; }

/* ---------------------------------------------------------------- окна: пружинное появление, уход быстрее (без JS: @starting-style
   и allow-discrete держат окно на экране, пока оно уходит; close() остаётся синхронным). Телефон — шторка снизу. */
dialog {
  opacity: 0; transform: scale(.96);
  transition: opacity var(--dur-2) var(--ease-exit), transform var(--dur-2) var(--ease-exit), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete;
}
dialog[open] { opacity: 1; transform: none; transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-4) var(--spring), overlay var(--dur-4) allow-discrete, display var(--dur-4) allow-discrete; }
@starting-style { dialog[open] { opacity: 0; transform: scale(.96); } }
dialog::backdrop { opacity: 0; transition: opacity var(--dur-2) var(--ease-exit), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete; }
dialog[open]::backdrop { opacity: var(--sheet-dim, 1); transition: opacity var(--dur-3) var(--ease-out), overlay var(--dur-3) allow-discrete, display var(--dur-3) allow-discrete; }
@starting-style { dialog[open]::backdrop { opacity: 0; } }
dialog.sheet-dragging, dialog.sheet-dragging::backdrop, dialog.sheet-swiped, dialog.sheet-swiped::backdrop { transition: none; }
@media (max-width: 680px) {
  dialog { opacity: 1; transform: translateY(100%); }
  dialog[open] { transform: none; transition: transform var(--dur-5) var(--spring), overlay var(--dur-5) allow-discrete, display var(--dur-5) allow-discrete; }
  @starting-style { dialog[open] { transform: translateY(100%); } }
  dialog:not([open]) { transition: transform 220ms var(--ease-drawer), overlay 220ms allow-discrete, display 220ms allow-discrete; }
  /* ручка и шапка шторки — за них тянут вниз; браузер не прокручивает страницу этим жестом */
  #dialog .dialog-head { touch-action: none; cursor: grab; }
  #dialog::before { cursor: grab; }
}

/* ---------------------------------------------------------------- тост: выезжает снизу на пружине, уходит быстро */
#toast { transform: translateX(-50%) translateY(18px) scale(.98); transition: transform 180ms var(--ease-exit), opacity 160ms var(--ease-exit); }
#toast.show { transform: translateX(-50%) translateY(0) scale(1); transition: transform var(--dur-5) var(--spring), opacity var(--dur-2) var(--ease-out); }

/* ---------------------------------------------------------------- сегменты: индикатор скользит (app-motion.js) */
.segmented { position: relative; }
.seg-glide { position: absolute; z-index: 0; pointer-events: none; will-change: transform; }
.segmented.seg-gliding button { position: relative; z-index: 1; }
.segmented.seg-gliding button.active { background: transparent; box-shadow: none; }

/* ---------------------------------------------------------------- свёрнутые разделы: шеврон, мягкая высота (app-motion.js) */
.fold-chev { transition: transform var(--dur-4) var(--ease-out); }
.fold.fold-closing > summary .fold-chev { transform: none; transition-duration: var(--dur-2); }

/* ---------------------------------------------------------------- скелетоны: мягкое мерцание */
.skel::after { animation-duration: 1.6s; }

/* ---------------------------------------------------------------- загрузка приложения */
.loading.boot { animation: fade var(--dur-4) var(--ease-out) both; }

/* ---------------------------------------------------------------- меньше движения: всё мгновенно (короткий fade у окон) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-delay: 0ms !important; transition-duration: .01ms !important; transition-delay: 0ms !important; scroll-behavior: auto !important; }
  dialog, dialog[open], dialog::backdrop { transform: none !important; }
  .brief-card:hover, .member-card:hover { transform: none; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .rv-wait { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------- вход через Telegram (app-tglogin.js): карточка входа сменила
   содержимое (форма «Email или логин Telegram», затем форма кода) — тот же каскад, что при первом показе, только короче */
.login-box.tg-swap > *, .login-box.tg-swap .form > * { animation: rise var(--dur-4) var(--ease-out) both; }
.login-box.tg-swap > :nth-child(2) { animation-delay: 30ms; }
.login-box.tg-swap .form > :nth-child(1) { animation-delay: 60ms; }
.login-box.tg-swap .form > :nth-child(2) { animation-delay: 85ms; }
.login-box.tg-swap .form > :nth-child(n+3) { animation-delay: 110ms; }

/* ---------------------------------------------------------------- нижняя панель телефона как таб-бар iOS (16.10.2026, владелец:
   «серое выделение в меню внизу как-то не гармонично»): без подложки под выбранным пунктом — выбранный тёмный и чуть жирнее,
   остальные приглушённо-серые; значок выбранного пункта — с чуть более толстой линией */
@media (max-width: 680px) {
  .nav-pill, .nav button.active::before { display: none; }
  .nav button, .nav > button.nav-more { color: #737378; }
  .nav button .symbol { color: #737378; transition: color var(--dur-1) var(--ease-out); }
  .nav button.active, .nav > button.nav-more.active { color: var(--text); font-weight: 650; }
  .nav button.active .symbol { color: var(--text); }
  .nav button.active .symbol svg { stroke-width: 2.1; }
}

/* плашка «Вернуться к незаконченному» (app-session.js) — выезд снизу на пружине, как тост */
.resume-bar { animation: resume-in var(--dur-5) var(--spring) both; }
@keyframes resume-in { from { opacity: 0; transform: translateX(-50%) translateY(18px) scale(.98); } }

/* ---------------------------------------------------------------- постоянная оболочка (16.10.2026, app-frame.js): панель меню и
   шапка не пересоздаются. Подложка есть у каждого пункта, видна только у выбранного (на компьютере в View Transition скользит
   от старого пункта к новому — у видимой подложки одно имя). Приглушение «раздел грузится» — только содержимого, шапка не тускнеет. */
.nav > button:not(.active) > .nav-pill { display: none; }
.workspace, .workspace.live-wait { opacity: 1; transition: none; }
.workspace > :not(.topbar):not(.viewing-bar) { transition: opacity var(--dur-2) var(--ease-out); }
.workspace.live-wait > :not(.topbar):not(.viewing-bar) { opacity: .55; }
/* Телефон и любой сенсорный экран: новый раздел встаёт одним цельным движением — короткое проявление с еле заметным подъёмом, без
   каскада (на слабом Android каскад десятка блоков выглядел дрожанием). Шапка и панель меню неподвижны. */
@keyframes ws-in { from { opacity: .6; transform: translate3d(0, 5px, 0); } }
.workspace.ws-swap > :not(.topbar):not(.viewing-bar) { animation: ws-in 180ms var(--ease-out) both; }

/* ---------------------------------------------------------------- первое впечатление (16.10.2026, приём kukuruza.io): «Заявка принята»
   встаёт той же долгой мягкой кривой, что и вход (--ease-glide, --dur-glide); частые переходы разделов остаются короткими */
.pending-box, .pending-box > * { animation: rise-glide var(--dur-glide) var(--ease-glide) both; }
.pending-box > :nth-child(2) { animation-delay: 60ms; }
.pending-box > :nth-child(3) { animation-delay: 110ms; }
.pending-box > :nth-child(4) { animation-delay: 160ms; }
.pending-box > :nth-child(n+5) { animation-delay: 210ms; }
.pending-box > * { animation-name: rise-move-glide; }   /* внутри проявляющейся карточки — только подъём: прозрачности не перемножаются */
@keyframes rise-move-glide { from { transform: translate3d(0, var(--rise-glide), 0); } }

/* ---------------------------------------------------------------- появление при прокрутке (app-reveal.js): блок ниже окна ждёт
   скрытым, пока до него не долистают. !important — его не перебивают каскад раздела (.enter, .ws-swap) и приглушение «грузится»;
   само проявление — Web Animations поверх, класс снимается в тот же кадр. Печать и «меньше движения» — всё видно. */
.rv-wait { opacity: 0 !important; transform: translate3d(0, var(--rise-glide), 0) !important; }
@media print { .rv-wait { opacity: 1 !important; transform: none !important; } }

/* ---------------------------------------------------------------- плавная прокрутка якорей и «наверх»; смена раздела открывает его
   сверху мгновенно (app-ux.js, behavior: 'instant'), «меньше движения» — без плавности (правило выше, scroll-behavior: auto) */
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
