/* Движение серии. Спека — .claude/MOTION.md. Только transform, opacity, clip-path, filter.
   Скрытые стартовые состояния живут под html.motion: класс ставит скрипт в <head>, и только без prefers-reduced-motion.
   Без скрипта и при reduced motion страница сразу в финале. */

:root {
  --dur-enter: 900ms; --dur-title: 1100ms; --dur-draw: 1400ms; --dur-count: 1600ms; --dur-fade: 600ms;
  --dur-hover: 150ms; --dur-press: 80ms;
  --ease-enter: cubic-bezier(0.16, 1, 0.3, 1);   /* expo.out лонгридов */
  --ease-move: cubic-bezier(0.45, 0, 0.55, 1);   /* power2.inOut */
  --stagger: 80ms; --stagger-rows: 120ms;
  --shift: 40px; --shift-sm: 24px;
  --hot-delay: 400ms;    /* горячее — после каркаса, отдельным событием */
  --late-delay: 1500ms;  /* подписи — после данных */
}

/* ── вход блока ─────────────────────────────────────────────────────── */
html.motion [data-r="up"]:not(.in) { opacity: 0; transform: translateY(var(--shift)); }
html.motion [data-r="fade"]:not(.in) { opacity: 0; }
html.motion [data-r="up"].in, html.motion [data-r="fade"].in {
  transition: opacity var(--dur-enter) var(--ease-enter) calc(var(--i, 0) * var(--stagger)),
              transform var(--dur-enter) var(--ease-enter) calc(var(--i, 0) * var(--stagger)); }

/* ── заголовок по строкам из маски (строки режет motion.js) ───────────── */
html.motion [data-r="lines"]:not(.split):not(.in) { opacity: 0; }
.ln { display: block; overflow: hidden; padding-bottom: .14em; margin-bottom: -.14em; }
.ln-i { display: block; white-space: nowrap; }
html.motion [data-r="lines"].split:not(.in) .ln-i { transform: translateY(135%); }
html.motion [data-r="lines"].in .ln-i { transition: transform var(--dur-title) var(--ease-enter) calc(var(--l, 0) * var(--stagger)); }

/* ── график: каркас → горячее → подписи ─────────────────────────────── */
html.motion [data-r="chart"]:not(.in) .lay-base,
html.motion [data-r="chart"]:not(.in) .c-early,
html.motion [data-r="chart"]:not(.in) .c-late,
html.motion [data-r="chart"]:not(.in) [data-count],
html.motion [data-r="chart"]:not(.in) .bar-val { opacity: 0; }
html.motion [data-r="chart"]:not(.in) .lay-hot { clip-path: inset(0 100% 0 0); }
html.motion [data-r="chart"]:not(.in) .rise .lay-hot { clip-path: inset(100% 0 0 0); }   /* столбик: горячее растёт снизу */
html.motion [data-r="chart"]:not(.in) .bar-fill { clip-path: inset(0 100% 0 0 round 4px); }

html.motion [data-r="chart"].in .lay-base,
html.motion [data-r="chart"].in .c-early { transition: opacity var(--dur-fade) linear; }
html.motion [data-r="chart"].in .lay-hot { clip-path: inset(0 0 0 0); transition: clip-path var(--dur-draw) var(--ease-move) var(--hot-delay); }
html.motion [data-r="chart"].in [data-count] { transition: opacity var(--dur-fade) linear var(--hot-delay); }
html.motion [data-r="chart"].in .c-late { transition: opacity var(--dur-fade) linear var(--late-delay); }
html.motion [data-r="chart"].in .bar-fill { clip-path: inset(0 0 0 0 round 4px);
  transition: clip-path var(--dur-draw) var(--ease-move) calc(var(--b, 0) * var(--stagger-rows) + var(--dur-fade) / 3); }
html.motion [data-r="chart"].in .bar-val { transition: opacity var(--dur-fade) linear calc(var(--b, 0) * var(--stagger-rows) + var(--dur-draw) / 2); }

/* дуги засечек (вариант 3): дуги каркаса проявляются по очереди, горячая дуга — последней, отдельным событием */
html.motion [data-r="chart"]:not(.in) .arc, html.motion [data-r="chart"]:not(.in) .arc-hot { opacity: 0; }
html.motion [data-r="chart"].in .arc { transition: opacity var(--dur-fade) linear calc(var(--b, 0) * var(--stagger-rows)); }
html.motion [data-r="chart"].in .arc-hot { transition: opacity var(--dur-enter) var(--ease-enter) calc(var(--hot-delay) + var(--b, 0) * var(--stagger-rows)); }

/* счётчик меняет текст по кадрам: пересчёт раскладки остаётся внутри самого числа и не трогает соседей */
html.motion [data-count] { contain: layout style; }

/* ── заливка текста по скроллу (data-fill, kit/fill.js) ─────────────────
   Слова стоят серым (сплошной цвет), поверх каждого — белый слой-псевдоэлемент, его opacity 0 → 1 идёт по
   view-timeline заголовка: шкала начинается, когда верх заголовка на линии входа (86 % экрана), и кончается, когда
   низ заголовка на 45 % экрана. У каждого слова своё окно на шкале (--fa … --fb, проценты диапазона cover — их
   считает fill.js), соседние перекрываются — фронт мягкий. Окна в процентах, а не в calc() из vh и px: смесь единиц
   в animation-range Chrome 152 разрешает не так, как отчитывается, и заливка уходит вперёд.
   Работает только под html.fill-on (ставит fill.js, когда есть html.motion и animation-timeline); иначе текст белый.
   --fill-start / --fill-end — доли высоты экрана (читает fill.js), --fill-soft — ширина фронта в словах. */
:root { --fill-start: 0.14; --fill-end: 0.55; --fill-soft: 3; --fill-base: var(--base-600); }
@keyframes fill-word { to { opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  html.fill-on [data-fill] { view-timeline-name: --fill; }
  html.fill-on [data-fill] .fw { display: inline-block; position: relative; color: var(--fill-base); }
  html.fill-on [data-fill] .fw::after {
    content: attr(data-w); content: attr(data-w) / "";
    position: absolute; inset: 0; white-space: nowrap; color: var(--text-primary); opacity: 0; pointer-events: none;
    animation: fill-word linear both;
    animation-timeline: --fill;
    animation-range: cover var(--fa, 0%) cover var(--fb, 100%);
  }
}

/* ── отклик кнопок: яркость и нажатие, без сдвига ───────────────────── */
.btn, .toc a { transition: filter var(--dur-hover) linear, transform var(--dur-press) linear; }
@media (hover: hover) { .btn:hover { filter: brightness(1.08); } }
.btn:active, .toc a:active { transform: scale(.98); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0ms !important; scroll-behavior: auto !important; }
  .btn:active, .toc a:active { transform: none; }
}
