/* =====================================================================
   gamelog — анимации интерфейса (для всех страниц и любой темы).
   Подключать после app.css (и темы, если есть):
     <link rel="stylesheet" href="motion.css">
     <script src="motion.js">   ← в <head>, без defer

   Подключены в base.html на всех страницах, после app.css и стиля темы
   профиля (doc/animacii.md). Нарисовано в Claude Design, 16.10.2026.

   ВКЛ/ВЫКЛ решает motion.js — он ставит html.motion-on:
     • по умолчанию — если в системе НЕ включено «уменьшить движение»;
     • <html data-motion="on">  — анимации всегда (настройка пользователя);
     • <html data-motion="off"> — анимаций нет.
   Все правила ниже работают только при html.motion-on.

   1. Появление страницы — блоки и карточки «всплывают» по очереди.
   2. Отклик на наведение и нажатие — карточки, строки, кнопки, логотип.
   3. Смена статуса — перекраска бейджа, «сохраняю…», вспышка «сохранено».
   4. Прогресс и цифры — полоски, кольца, графики растут, числа набегают.
   5. Меню и окна — колокольчик, подсказки, список статусов, <dialog>.
   6. Колокольчик с непрочитанным — покачивается при загрузке.
   7. Слово у названия в шапке — достижения → платина → игры.
   ===================================================================== */

:root{
  --dur-fast: 120ms;
  --dur:      180ms;
  --dur-rise: 420ms;
  --dur-fill: 900ms;
  --ease-out: cubic-bezier(.2,.8,.2,1);
  --stagger:  60ms;
}

@property --reveal{ syntax:"<angle>"; inherits:false; initial-value:360deg; }

/* ============ 1. Появление страницы ============
   fill-mode backwards: после анимации у блока не остаётся transform,
   иначе он сломал бы position:fixed у вложенных окон. */

.motion-on :is(
    .content > :not(.page-layout),
    .page-main > *, .page-aside > *,
    .card-grid > *, .community-grid > *, .game-row){
  animation: m-rise var(--dur-rise) var(--ease-out) backwards;
  animation-delay: calc(var(--n, 0) * var(--stagger));
}
/* порядковый номер для очереди: дальше 8-го все появляются вместе */
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(2),
.motion-on .game-row:nth-child(2){ --n:1; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(3),
.motion-on .game-row:nth-child(3){ --n:2; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(4),
.motion-on .game-row:nth-child(4){ --n:3; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(5),
.motion-on .game-row:nth-child(5){ --n:4; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(6),
.motion-on .game-row:nth-child(6){ --n:5; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(7),
.motion-on .game-row:nth-child(7){ --n:6; }
.motion-on :is(.content, .page-main, .page-aside, .card-grid, .community-grid) > :nth-child(n+8),
.motion-on .game-row:nth-child(n+8){ --n:7; }
/* правая колонка — чуть позже левой */
.motion-on .page-aside > *{ animation-delay: calc((var(--n, 0) + 2) * var(--stagger)); }

@keyframes m-rise{ from{ opacity:0; translate: 0 12px; } }

/* ============ 2. Наведение и нажатие ============ */

.motion-on .game-card{
  transition: translate var(--dur) var(--ease-out), box-shadow var(--dur) ease, border-color var(--dur) ease;
}
.motion-on .game-card:hover{ translate: 0 -3px; box-shadow: var(--shadow-md); }
.motion-on :is(.game-card, .game-row) .cover img,
.motion-on .game-row .row-cover img{ transition: scale 400ms var(--ease-out); }
.motion-on .game-card:hover .cover img,
.motion-on .game-row:hover .row-cover img{ scale: 1.06; }

.motion-on .game-row{ transition: background-color var(--dur) ease; }

.motion-on :is(.btn-primary, .btn-ghost){ transition: scale var(--dur-fast) ease, filter var(--dur-fast) ease,
  background-color var(--dur) ease, border-color var(--dur) ease, color var(--dur) ease, box-shadow var(--dur) ease; }
.motion-on :is(.btn-primary, .btn-ghost):active{ scale: .96; }

.motion-on .nav-item{ transition: color var(--dur) ease, background-color var(--dur) ease, border-color var(--dur) ease; }

/* логотип — оборот при наведении */
.motion-on .brand-mark{ transition: rotate 700ms var(--ease-out); }
.motion-on .brand:hover .brand-mark{ rotate: 1turn; }

/* ============ 3. Смена статуса ============ */

.motion-on :is(.status-badge, button.status-badge){
  transition:
    color var(--dur) ease, background-color var(--dur) ease,
    border-color var(--dur) ease, box-shadow var(--dur) ease,
    filter var(--dur-fast) ease, opacity var(--dur-fast) ease;
}
.motion-on .status-badge .dot{ transition: background-color var(--dur) ease; }
.motion-on .status-select.is-saving{ animation: m-saving 1s ease-in-out infinite; cursor:progress; }
.motion-on .status-select.is-saved{ animation: m-saved 650ms var(--ease-out); }

@keyframes m-saving{ 50%{ opacity:.55; } }
@keyframes m-saved{
  0%  { transform: scale(1.08); box-shadow: 0 0 0 0 currentColor; }
  100%{ transform: none;        box-shadow: 0 0 0 10px transparent; }
}

/* ============ 4. Прогресс и цифры при появлении на экране ============
   Элементу ставится .m-in, когда он виден. До этого — нулевое состояние. */

.motion-on :is(.progress-fill, .status-chart-bar):not(.m-in){ transform: scaleX(0); }
.motion-on .mchart-bar:not(.m-in){ transform: scaleY(0); }
.motion-on .quarter-fill:not(.m-in){ stroke-dashoffset: 157.08; }
.motion-on .status-donut:not(.m-in){ --reveal: 0deg; }
.motion-on .radar-area:not(.m-in){ transform: scale(0); opacity:0; }
.motion-on :is(.radar-point, .m-count):not(.m-in){ opacity:0; }

.progress-fill, .status-chart-bar{ transform-origin: left center; }
.mchart-bar{ transform-origin: bottom center; }
.radar-area{ transform-box: view-box; transform-origin: center; }
.status-donut{
  -webkit-mask: conic-gradient(#000 var(--reveal), transparent 0);
          mask: conic-gradient(#000 var(--reveal), transparent 0);
}

.motion-on :is(.progress-fill, .status-chart-bar).m-in{ animation: m-grow-x var(--dur-fill) var(--ease-out) both; }
.motion-on .mchart-bar.m-in{
  animation: m-grow-y var(--dur-fill) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 40ms);       /* волна слева направо */
}
.motion-on .quarter-fill.m-in{ animation: m-quarter var(--dur-fill) var(--ease-out) both; }
.motion-on .status-donut.m-in{ animation: m-donut var(--dur-fill) var(--ease-out) both; }
.motion-on .radar-area.m-in{ animation: m-radar var(--dur-fill) var(--ease-out) both; }
.motion-on .radar-point.m-in{ animation: m-fade 300ms ease 500ms both; }
.motion-on .m-count.m-in{ animation: m-fade 200ms ease both; }

@keyframes m-grow-x { from{ transform: scaleX(0); } }
@keyframes m-grow-y { from{ transform: scaleY(0); } }
@keyframes m-quarter{ from{ stroke-dashoffset: 157.08; } }  /* длина дуги r=100 */
@keyframes m-donut  { from{ --reveal: 0deg; } to{ --reveal: 360deg; } }
@keyframes m-radar  { from{ transform: scale(0); opacity:0; } }
@keyframes m-fade   { from{ opacity:0; } }

/* ============ 5. Меню и окна ============ */

.motion-on :is(.bell[open] .bell-panel, .help-tip:hover .help-tip-body, .help-tip-btn:focus + .help-tip-body){
  animation: m-pop var(--dur) var(--ease-out);
}
@keyframes m-pop{ from{ opacity:0; transform: translateY(-6px); } }

@supports (appearance: base-select){
  .motion-on select.status-select::picker(select){
    transition:
      opacity var(--dur) ease, translate var(--dur) var(--ease-out),
      overlay var(--dur) allow-discrete, display var(--dur) allow-discrete;
    opacity:0; translate: 0 -6px;
  }
  .motion-on select.status-select:open::picker(select){ opacity:1; translate:0 0; }
  @starting-style{
    .motion-on select.status-select:open::picker(select){ opacity:0; translate: 0 -6px; }
  }
}

.motion-on .confirm-dialog{
  transition:
    opacity var(--dur) ease, translate var(--dur) var(--ease-out), scale var(--dur) var(--ease-out),
    overlay var(--dur) allow-discrete, display var(--dur) allow-discrete;
  opacity:0; translate: 0 10px; scale:.98;
}
.motion-on .confirm-dialog[open]{ opacity:1; translate:0 0; scale:1; }
@starting-style{
  .motion-on .confirm-dialog[open]{ opacity:0; translate: 0 10px; scale:.98; }
}
.motion-on .confirm-dialog::backdrop{
  transition: opacity var(--dur) ease, overlay var(--dur) allow-discrete, display var(--dur) allow-discrete;
  opacity:0;
}
.motion-on .confirm-dialog[open]::backdrop{ opacity:.75; }
@starting-style{
  .motion-on .confirm-dialog[open]::backdrop{ opacity:0; }
}

/* ============ 6. Колокольчик с непрочитанным ============ */

.motion-on .bell:has(.bell-count) > summary svg{
  transform-origin: 50% 10%;
  animation: m-ring 900ms ease-in-out 1.2s 2;
}
.motion-on .bell-count{ animation: m-pop-in 400ms var(--ease-out) 1s backwards; }
@keyframes m-ring{
  0%,100%{ rotate: 0deg; } 15%{ rotate: 14deg; } 30%{ rotate: -12deg; }
  45%{ rotate: 8deg; } 60%{ rotate: -5deg; } 75%{ rotate: 2deg; }
}
@keyframes m-pop-in{ from{ scale: 0; } 70%{ scale: 1.25; } }

/* Чат (doc/chat.md): окно вырастает из кнопки, новое сообщение всплывает. */
.motion-on .chat.is-open .chat-window{ animation: m-chat-open .18s ease-out; transform-origin: 100% 100%; }
@keyframes m-chat-open{ from{ opacity:0; transform: translateY(8px) scale(.97); } }
.motion-on .chat-fab.has-unread{ animation: m-pop-in .35s ease-out; }


/* ============ 7. Слово у названия в шапке ============
   Три слова по 3 секунды: всплывает снизу, уходит вверх. Сдвиг по времени —
   задержкой, у всех одна анимация на 9 секунд. Без motion-on видно первое
   слово (app.css, «Brand»). */

.motion-on .brand-words > span{
  visibility:visible; opacity:0;
  animation: brand-word 9s var(--ease-out) infinite;
}
.motion-on .brand-words > span:nth-child(2){ animation-delay:3s; }
.motion-on .brand-words > span:nth-child(3){ animation-delay:6s; }
@keyframes brand-word{
  0%      { opacity:0; transform: translateY(5px); }
  4%, 30% { opacity:1; transform: none; }
  34%,100%{ opacity:0; transform: translateY(-5px); }
}
