/* ═══════════════════════════════════════════════════════════════════════════
   TOLK AI · Motion5 — layer 3: the only motion layer in the product.
   Rules: transform / opacity / filter only; every duration comes from a token;
   nothing animates on a display:none element; prefers-reduced-motion switches
   off all decorative movement while keeping state legible.
   ═══════════════════════════════════════════════════════════════════════════ */

@keyframes rise{from{opacity:0;transform:translate3d(0,16px,0)}to{opacity:1;transform:none}}
@keyframes riseSm{from{opacity:0;transform:translate3d(0,8px,0)}to{opacity:1;transform:none}}
@keyframes fade{from{opacity:0}to{opacity:1}}
@keyframes popIn{0%{opacity:0;transform:translate3d(0,-6px,0) scale(.96)}100%{opacity:1;transform:none}}
@keyframes sheetIn{from{transform:translate3d(0,100%,0)}to{transform:none}}
@keyframes zoomIn{from{opacity:0;transform:scale(.94) translate3d(0,10px,0)}to{opacity:1;transform:none}}
@keyframes drift{
  0%{transform:translate3d(0,0,0) scale(1)}
  33%{transform:translate3d(28px,-22px,0) scale(1.06)}
  66%{transform:translate3d(-18px,16px,0) scale(.97)}
  100%{transform:translate3d(0,0,0) scale(1)}
}
@keyframes float{0%,100%{transform:translate3d(0,0,0) rotate(0)}50%{transform:translate3d(0,-9px,0) rotate(1.4deg)}}
@keyframes shimmer{from{background-position:200% 0}to{background-position:-80% 0}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes bounceDot{0%,80%,100%{transform:translateY(0);opacity:.45}40%{transform:translateY(-5px);opacity:1}}
@keyframes ripple{to{transform:scale(2.6);opacity:0}}
@keyframes sweep{from{transform:translateX(-120%)}to{transform:translateX(120%)}}
@keyframes pulseRing{0%{box-shadow:0 0 0 0 rgba(23,187,168,.42)}70%{box-shadow:0 0 0 12px rgba(23,187,168,0)}100%{box-shadow:0 0 0 0 rgba(23,187,168,0)}}
@keyframes nudgeX{0%,100%{transform:translateX(0)}50%{transform:translateX(3px)}}
@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-5px)}40%{transform:translateX(5px)}60%{transform:translateX(-3px)}80%{transform:translateX(3px)}}

/* ── Boot ───────────────────────────────────────────────────────────────── */
/* Стартовый экран (§3). Без него страница появлялась рывком: сначала пустой
   фон, потом сразу весь интерфейс. Экран держится ровно до первого кадра
   интерфейса и снимается скриптом.

   Страховка: если скрипт не загрузился (ошибка сети, старый браузер), экран
   убирает собственная анимация через 2,2 с. Остаться навсегда на логотипе —
   худший исход из возможных, хуже, чем рывок. */
.boot{
  position:fixed;inset:0;z-index:calc(var(--z-toast) + 10);
  display:grid;place-content:center;justify-items:center;gap:18px;
  background:
    radial-gradient(60% 50% at 50% 38%,rgba(80,216,199,.28),transparent 70%),
    var(--bg);
  animation:bootAway 2.2s var(--e-inout) forwards;
}
.boot-mark{
  width:96px;height:96px;
  filter:drop-shadow(0 18px 34px rgba(10,120,110,.3));
  animation:bootMark 1.5s var(--e-spring) infinite alternate;
}
.boot-mark img{width:100%;height:auto;display:block}
.boot-name{
  font-size:22px;font-weight:800;letter-spacing:-.02em;color:var(--text);
  animation:riseSm var(--d-emphasized) var(--e-out) 120ms backwards;
}
.boot-name span{color:var(--brand)}
.boot-bar{
  width:132px;height:4px;border-radius:999px;overflow:hidden;
  background:var(--surface-sunken);
}
.boot-bar i{
  display:block;width:40%;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--brand-2),var(--brand-3));
  animation:bootBar 1.15s var(--e-inout) infinite;
}
@keyframes bootMark{from{transform:translateY(0) scale(1)}to{transform:translateY(-7px) scale(1.03)}}
@keyframes bootBar{0%{transform:translateX(-110%)}100%{transform:translateX(260%)}}
@keyframes bootAway{
  0%,88%{opacity:1;visibility:visible}
  100%{opacity:0;visibility:hidden}
}
html.ready .boot{animation:bootOut var(--d-standard) var(--e-out) forwards}
@keyframes bootOut{to{opacity:0;visibility:hidden}}
@media(prefers-reduced-motion:reduce){
  .boot{animation:none;opacity:0;visibility:hidden}
  .boot-mark,.boot-bar i{animation:none}
}

html.booting body{opacity:0}
body{animation:fade var(--d-standard) var(--e-out) backwards}
@media(min-width:901px){.sidebar{animation:slideSide var(--d-emphasized) var(--e-out) backwards}}
@keyframes slideSide{from{opacity:0;transform:translate3d(-14px,0,0)}to{opacity:1;transform:none}}
.topbar,.mtop{animation:riseSm var(--d-standard) var(--e-out) backwards}
.bnav{animation:navUp var(--d-emphasized) var(--e-out) backwards}
@keyframes navUp{from{transform:translate3d(0,100%,0)}to{transform:none}}

/* ── Ambient ────────────────────────────────────────────────────────────── */
.blob.b1{animation:drift var(--d-ambient) var(--e-inout) infinite}
.blob.b2{animation:drift calc(var(--d-ambient) * 1.35) var(--e-inout) infinite reverse}
.blob.b3{animation:drift calc(var(--d-ambient) * 1.1) var(--e-inout) infinite}
.blob.b4{animation:drift calc(var(--d-ambient) * .85) var(--e-inout) infinite reverse}
.hero-mark{animation:float 7s var(--e-inout) infinite}
.hero-deco{animation:float 9s var(--e-inout) infinite reverse}

/* ── View transition ──────────────────────────────────────────────────────
   Раздел появляется сам, а его содержимое — каскадом. Раньше здесь стояло
   `.view.on>*{animation:none}`: это глушило появление всех детей, и
   переключение раздела выглядело подменой картинки. Теперь дети внутри
   групп data-stagger въезжают по очереди, а анимации самого раздела
   достаточно короткой — иначе два движения спорят друг с другом. */
.view.on{animation:fade var(--d-fast) var(--e-out) backwards}
.view.on>.wrap{animation:rise var(--d-standard) var(--e-out) backwards}

/* ── Каскад в группах ─────────────────────────────────────────────────────
   Один универсальный механизм вместо списка «этот грид, тот грид»:
   контейнеру ставится data-stagger, номер элемента приходит из --i
   (его расставляет TolkMotion.stagger). */
[data-stagger]>*{
  animation:rise var(--d-emphasized) var(--e-out) backwards;
  animation-delay:calc(var(--i,0) * var(--stagger));
}
[data-stagger=sm]>*{animation-name:riseSm;animation-duration:var(--d-standard)}
[data-stagger=zoom]>*{animation-name:zoomIn}
[data-stagger=fade]>*{animation-name:fade;animation-duration:var(--d-standard)}

/* ── Staggered reveal ───────────────────────────────────────────────────── */
.quicks .quick{animation:rise var(--d-emphasized) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 38ms)}
.mini-grid .mini{animation:rise var(--d-emphasized) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 46ms)}
.feed .feed-row{animation:riseSm var(--d-standard) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 34ms)}
.model-grid .model-card{animation:rise var(--d-emphasized) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 26ms)}
.gallery .shot{animation:zoomIn var(--d-emphasized) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 24ms)}
.picker .list .model-row{animation:riseSm var(--d-standard) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 26ms)}
.amount-grid .amount{animation:riseSm var(--d-standard) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 24ms)}
.rows .row-link{animation:riseSm var(--d-standard) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 26ms)}
.devs .dev{animation:riseSm var(--d-standard) var(--e-out) backwards;animation-delay:calc(var(--i,0) * 30ms)}

/* Появление секций при скролле (§7).

   Progressive enhancement: скрытое начальное состояние существует только
   после того, как наблюдатель точно работает, — содержимое никогда не
   остаётся невидимым. Три варианта на выбор: подъём (по умолчанию),
   мягкий зум для крупных блоков и чистое проявление для текста.
   Сдвиг маленький — 18 px: большое движение на каждой секции превращает
   страницу в карусель. */
html.reveal-ready [data-reveal]{opacity:0;transform:translate3d(0,18px,0)}
html.reveal-ready [data-reveal=scale]{transform:scale(.985) translate3d(0,14px,0)}
html.reveal-ready [data-reveal=fade]{transform:none}
html.reveal-ready [data-reveal].seen{
  opacity:1;transform:none;
  transition:opacity var(--d-emphasized) var(--e-out),transform var(--d-emphasized) var(--e-out);
}
/* Задержка внутри секции: заголовок, потом содержимое. */
html.reveal-ready [data-reveal].seen>.section-head{animation:riseSm var(--d-standard) var(--e-out) backwards}

/* ── Ripple ─────────────────────────────────────────────────────────────── */
.rip{
  position:absolute;border-radius:50%;pointer-events:none;
  background:currentColor;opacity:.20;transform:scale(0);
  animation:ripple var(--d-emphasized) var(--e-out) forwards;
}
.new-chat .rip,.btn .rip,.send .rip,.circle .rip{background:#fff;opacity:.28}

/* ── Composer sweep while generating ───────────────────────────────────── */
.composer.busy::before{
  content:'';position:absolute;inset:-1px;border-radius:inherit;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(23,187,168,.45),transparent);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;padding:1px;
  animation:fade var(--d-fast) var(--e-out) backwards;
}
.composer.busy::after{
  content:'';position:absolute;left:0;right:0;top:0;height:2px;overflow:hidden;
  border-radius:inherit;pointer-events:none;
  background:linear-gradient(90deg,transparent,var(--brand-2),transparent);
  animation:sweep 1.4s var(--e-inout) infinite;
}

/* ── Messages ───────────────────────────────────────────────────────────── */
.msg{animation:rise var(--d-standard) var(--e-out) backwards}
.typing i{animation:bounceDot 1.15s var(--e-inout) infinite}
.typing i:nth-child(2){animation-delay:.14s}
.typing i:nth-child(3){animation-delay:.28s}

/* ── Overlays ───────────────────────────────────────────────────────────── */
.pop{animation:popIn var(--d-spring) var(--e-spring) backwards}
.dialog-host{animation:fade var(--d-fast) var(--e-out) backwards}
.dialog{animation:zoomIn var(--d-spring) var(--e-spring) backwards}
.sheet-host{animation:fade var(--d-fast) var(--e-out) backwards}
.sheet{animation:sheetIn var(--d-spring) var(--e-spring) backwards}
.toast{animation:toastIn var(--d-spring) var(--e-spring) backwards}
@keyframes toastIn{from{opacity:0;transform:translate3d(0,14px,0) scale(.97)}to{opacity:1;transform:none}}
.toast.out{animation:toastOut var(--d-fast) var(--e-out) both}
@keyframes toastOut{to{opacity:0;transform:translate3d(0,8px,0) scale(.98)}}

/* Уход оверлеев. Пока его не было, модалка и лист исчезали мгновенно: приход
   плавный, уход рывком — это читается как сбой. */
.pop.out{animation:popOut var(--d-fast) var(--e-in) both}
@keyframes popOut{to{opacity:0;transform:translate3d(0,-4px,0) scale(.97)}}
.dialog-host.out{animation:fadeOut var(--d-fast) var(--e-in) both}
.dialog-host.out .dialog{animation:zoomOut var(--d-fast) var(--e-in) both}
@keyframes fadeOut{to{opacity:0}}
@keyframes zoomOut{to{opacity:0;transform:scale(.97) translate3d(0,6px,0)}}
.sheet-host.out{animation:fadeOut var(--d-standard) var(--e-in) both}
.sheet-host.out .sheet{animation:sheetOut var(--d-standard) var(--e-in) both}
@keyframes sheetOut{to{transform:translate3d(0,100%,0)}}
.drawer-host.out{animation:fadeOut var(--d-standard) var(--e-in) both}
.drawer-host.out .drawer{animation:drawerOut var(--d-standard) var(--e-in) both}
@keyframes drawerOut{to{transform:translate3d(-100%,0,0)}}
.cookie{animation:rise var(--d-emphasized) var(--e-out) backwards}
.offline{animation:rise var(--d-standard) var(--e-out) backwards}

/* ── Loading / result states ───────────────────────────────────────────── */
.sk{animation:shimmer 1.5s linear infinite}
.spin{animation:spin .8s linear infinite}
.is-loading{pointer-events:none}
.is-error{animation:shake var(--d-emphasized) var(--e-out)}
.pulse{animation:pulseRing 1.8s var(--e-out) infinite}
.stage.live .dot{animation:pulseRing 1.8s var(--e-out) infinite}

/* ── Theme crossfade ───────────────────────────────────────────────────── */
html.theming *,html.theming *::before,html.theming *::after{
  transition:background-color var(--d-standard) var(--e-out),
             color var(--d-standard) var(--e-out),
             border-color var(--d-standard) var(--e-out) !important;
}

/* ══════════════════════ Reduced motion ══════════════════════
   Decorative movement stops. State changes stay visible, because a user who
   asks for less motion still needs to see that a button did something. */
@media(prefers-reduced-motion:reduce){
  .blob,.hero-mark,.hero-deco{animation:none !important}
  .composer.busy::after{animation:none;opacity:.7}
  *,*::before,*::after{
    animation-duration:1ms !important;
    animation-delay:0ms !important;
    animation-iteration-count:1 !important;
    transition-duration:1ms !important;
  }
  .typing i{animation:none !important;opacity:.6}
  .sk{animation:none !important;background:var(--surface-sunken)}
  .spin{animation:spin 1.1s linear infinite !important}
  .quick:hover,.mini:hover,.model-card:hover,.pill:hover,.btn:hover,
  .btn-ghost:hover,.chip:hover,.account:hover,.shot:hover,.feed-row:hover{transform:none !important}
  html.reveal-ready [data-reveal]{opacity:1;transform:none}
  .rip{display:none}
}

/* ── Отделка ──────────────────────────────────────────────────────────── */
@keyframes sheen{from{transform:translate3d(-60%,0,0)}to{transform:translate3d(60%,0,0)}}
@keyframes auroraSpin{to{transform:rotate(360deg)}}
@keyframes bump{0%{transform:scale(1)}40%{transform:scale(1.09)}100%{transform:scale(1)}}
@keyframes sendIn{
  from{opacity:0;transform:translate3d(0,22px,0) scale(.94)}
  to{opacity:1;transform:none}
}
@keyframes glowPulse{0%,100%{opacity:.42}50%{opacity:.62}}
@keyframes livePulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}
@keyframes caretBlink{0%,50%{opacity:1}50.01%,100%{opacity:0}}

.hero::before{animation:auroraSpin 26s linear infinite,glowPulse 7s var(--e-inout) infinite}
.msg.user{animation:sendIn var(--d-spring) var(--e-spring) backwards}

@media(prefers-reduced-motion:reduce){
  .hero::before{animation:none !important}
  .quick:hover::before{animation:none !important;opacity:0}
  .quick:hover{transform:none !important}
  .bnav-pill,.mode-thumb{transition:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════════
   Микро-взаимодействия (§7, §19)
   Правило: отклик должен быть заметен за 120–200 мс и не сдвигать соседей.
   Анимируем только transform, opacity, color, background и box-shadow.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Иконка-стрелка уезжает в сторону движения. Это самый дешёвый способ
   показать «здесь переход», не добавляя ещё одну подпись. */
.link .ico,.row-link .go,.act-card .go,.mini .go,.quick .go{
  transition:transform var(--d-fast) var(--e-out),opacity var(--d-fast) var(--e-out);
}
.link:hover .ico,.row-link:hover .go,.act-card:hover .go,.mini:hover .go,.quick:hover .go{
  transform:translateX(3px);
}
/* Шеврон разворачивается, когда меню открыто: состояние читается по форме,
   а не только по появившейся панели. */
[aria-expanded] .chev,[aria-expanded] [data-chev]{transition:transform var(--d-standard) var(--e-out)}
[aria-expanded=true] .chev,[aria-expanded=true] [data-chev]{transform:rotate(180deg)}

/* Плитка иконки в карточке слегка оживает вместе с самой карточкой. */
.quick:hover .tile,.mini:hover .tile,.act-card:hover .tile,.state:hover .tile{
  transform:scale(1.06) rotate(-2deg);
}

/* Нажатие. Палец должен получать ответ раньше, чем сеть. */
.quick:active,.mini:active,.act-card:active,.row-link:active,.feed-row:active,
.model-card:active,.plan:active,.amount:active,.drawer-item:active{
  transform:scale(.985);
}

/* Подчёркивание ссылок в тексте: линия вырастает от начала слова. */
.prose a,.inline-link{
  background-image:linear-gradient(var(--brand-2),var(--brand-2));
  background-size:0% 1.5px;background-repeat:no-repeat;background-position:0 100%;
  transition:background-size var(--d-standard) var(--e-out),color var(--d-fast) var(--e-out);
}
.prose a:hover,.inline-link:hover{background-size:100% 1.5px;color:var(--brand-deep)}

/* Смена набора карточек (фильтр, поиск, категория). Новый набор приходит
   каскадом — тогда фильтрация выглядит как пересборка, а не как мигание. */
[data-filtering]{animation:filterOut var(--d-instant) var(--e-in) both}
@keyframes filterOut{to{opacity:.35;transform:scale(.995)}}

/* Индикатор активного раздела и переезжающая пилюля. */
.bnav-pill,.mode-thumb,.seg-thumb,.tabs-thumb{
  transition:transform var(--d-spring) var(--e-spring),width var(--d-spring) var(--e-spring),
             opacity var(--d-fast) var(--e-out);
}
.side-bar{transition:transform var(--d-spring) var(--e-spring),opacity var(--d-fast) var(--e-out)}

/* Появление содержимого, которое дорисовал скрипт (список, результат,
   галерея). Класс ставится один раз, поэтому повторной анимации при
   каждом обновлении нет. */
.fresh{animation:riseSm var(--d-standard) var(--e-out) backwards}

@media(prefers-reduced-motion:reduce){
  .link:hover .ico,.row-link:hover .go,.act-card:hover .go,.quick:hover .go,
  .quick:hover .tile,.mini:hover .tile,.act-card:hover .tile{transform:none !important}
  [data-stagger]>*{animation:none !important}
  .prose a,.inline-link{transition:none}
  [data-filtering]{animation:none !important}
}
