/* Движение лендинга. Спека 16, раздел 2.5.

   Мир — казуальная игра (регистр Playrix), а не сайт. На эталонном кадре
   предметы объёмные, тени жёсткие, у кнопки виден тёмный борт снизу. Такие
   предметы обязаны и двигаться соответственно: тяжело, с отскоком, с посадкой
   на место. Поэтому кривая везде с возвратом (--m-back), а не ease-out:
   линейное затухание читается как «интерфейс», а нам нужна «игрушка».

   Три ограничения, которые здесь важнее красоты:

   1. Анимируются ТОЛЬКО transform и opacity. Всё остальное тянет за собой
      пересчёт разметки или перерисовку, а страница живёт на слабых машинах
      рядом с OBS. На это правило поставлен сторож в
      tests/cases/landing-motion.test.js — он читает этот файл.

   2. Конечное состояние задаётся классом, а не анимацией. Ни одного
      animation-fill-mode: forwards, от которого зависит вид. Выключили
      движение — .is-in всё равно ставит панель в конечный вид, и страница
      не остаётся на середине.

   3. Начальное состояние (.m-enter без .is-in — прозрачно и смещено) ставит
      ТОЛЬКО скрипт. В разметке этих классов быть не должно: при выключенном
      JavaScript панель с opacity: 0 никогда не появится, а спека 16 (раздел 6)
      требует, чтобы без скрипта страница читалась.

   Файл отдельный от landing.css намеренно: там мир и раскладка, здесь только
   движение. Классы начинаются с m- или is-, чтобы не столкнуться с чужими.

   Длительности продублированы в motion.js константами TIMINGS. Расхождение
   ловит тест — иначе скрипт снимет класс раньше, чем анимация доиграет. */

:root {
  /* Перелёт: 1.56 по второй контрольной точке — это ~9 % за цель и возврат. */
  --m-back: cubic-bezier(0.34, 1.56, 0.64, 1);
  --m-out: cubic-bezier(0.22, 0.61, 0.36, 1);

  --m-panel-ms: 320ms;
  --m-card-ms: 380ms;
  --m-press-ms: 80ms;
  --m-release-ms: 200ms;
  --m-shake-ms: 420ms;
  --m-pop-ms: 260ms;

  /* Высота борта леденцовой кнопки: на неё она и проседает. */
  --m-drop: 5px;
}

/* ── появление панели ──────────────────────────────────────────────────
   Скрипт ставит .m-enter (состояние «до»), следующим кадром — .is-in.
   Два кадра нужны обязательно: добавленные подряд классы браузер склеит
   в одно вычисление стиля, и перехода не будет вообще.

   will-change здесь намеренно нет: класс остаётся на узле навсегда, а
   will-change держит отдельный слой в памяти столько же. Панелей и карточек
   на странице десятки, и платить за них после того, как движение кончилось,
   не за что — transform и opacity и без подсказки уходят на композитор. */
.m-enter {
  opacity: 0;
  transform: translateY(18px) scale(0.94);
  transition: transform var(--m-panel-ms) var(--m-back), opacity 180ms var(--m-out);
}

.m-enter.is-in {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* Карточка догадки въезжает сверху: она падает в список, а не всплывает. */
.m-enter.m-enter-card {
  transform: translateY(-26px) scale(0.96);
  transition-duration: var(--m-card-ms);
}

.m-enter.m-enter-card.is-in {
  transform: translateY(0) scale(1);
}

/* ── кнопка-леденец ────────────────────────────────────────────────────
   Просадка быстрая и жёсткая (80 мс, ease-out), отпускание — с перелётом:
   так кнопка ощущается упругой, а не мягкой. Тень и борт рисует landing.css,
   здесь только движение. */
.m-press {
  transition: transform var(--m-release-ms) var(--m-back);
}

.m-press.is-press {
  transform: translateY(var(--m-drop)) scale(0.985);
  transition-duration: var(--m-press-ms);
  transition-timing-function: ease-out;
}

/* ── дрожание при ошибке ввода ─────────────────────────────────────────
   Два колебания с затуханием: −10, +8, −5, +3. Первый и последний кадры —
   ровно ноль, поэтому анимация ничего за собой не оставляет. */
.is-shake {
  animation: m-shake var(--m-shake-ms) var(--m-out);
}

@keyframes m-shake {
  0% {
    transform: translateX(0);
  }
  18% {
    transform: translateX(-10px);
  }
  40% {
    transform: translateX(8px);
  }
  62% {
    transform: translateX(-5px);
  }
  82% {
    transform: translateX(3px);
  }
  100% {
    transform: translateX(0);
  }
}

/* ── число доехало ─────────────────────────────────────────────────────
   Счётчик крутит motion.js пружиной, а плашка в конце делает «чпок». */
.is-pop {
  animation: m-pop var(--m-pop-ms) var(--m-back);
}

@keyframes m-pop {
  0% {
    transform: scale(1);
  }
  45% {
    transform: scale(1.14);
  }
  100% {
    transform: scale(1);
  }
}

/* ── конфетти ──────────────────────────────────────────────────────────
   Раскладку холста задаёт landing.css, здесь одно: холст поверх страницы
   не имеет права ловить клики. Иначе после победы кнопка «Скопировать»
   перестанет нажиматься, и найдут это уже в эфире. */
canvas#confetti {
  pointer-events: none;
}

/* ── движение выключено ────────────────────────────────────────────────
   Два выключателя, потому что источника два.

   .motion-off вешает motion.js: он получает reduced аргументом, и это
   единственный способ проверить выключенное движение тестом.

   Медиазапрос — на случай, если скрипт не поднялся: страница обязана
   уважать системную настройку и без него.

   Гасим широко, а не только свои классы: у соседних файлов лендинга своя
   разметка, а обещание «движение выключено» одно на страницу. Конечный вид
   при этом не страдает — он держится на классах, а не на анимациях.

   !important обязателен: у селектора со звёздочкой специфичность нулевая,
   и без него любое правило соседнего файла вернёт длительность обратно —
   молча. Гасим длительность, а не сами свойства: правило, снимающее
   transition целиком, оборвало бы уже идущий переход на полпути. */
.motion-off *,
.motion-off *::before,
.motion-off *::after {
  transition-duration: 1ms !important;
  transition-delay: 0s !important;
  animation-duration: 1ms !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 1ms !important;
    transition-delay: 0s !important;
    animation-duration: 1ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
  }
}
