/*
 * Движение интерфейса «БИТВЫ DTF» (.claude/skills/arena-motion).
 *
 * Звука в игре нет вовсе, поэтому всю обратную связь несут движение и цвет.
 * Единственное правило: анимация допустима, только если объясняет изменение
 * состояния — сколько сняли Жизни, чей ход, что пришло новое сообщение.
 * Украшений здесь нет и быть не должно: ни парения карточек, ни отскоков,
 * ни переливов. Игра выглядит как браузерка нулевых — движение короткое и сухое.
 *
 * Файл подключён ПОСЛЕ dtf-theme.css и трогает только движение: цвета, размеры
 * и раскладку задают styles.css, public/ui/*.css и dtf-theme.css.
 *
 * Двигаем всегда transform и opacity — никогда width, height, top, left, margin:
 * иначе браузер пересчитывает раскладку каждый кадр.
 *
 * Зацепки в разметке (их ставят public/ui/fight.js и public/ui/offtop.js):
 *   .fg-vital[style*="--fill"] — доля полосы Жизни или Запала, 0…1;
 *   .fg-line[data-fresh]       — строка лога текущего раунда;
 *   .fg-line.is-strike|.is-crit|.is-block|.is-dodge — что случилось;
 *   .fg-line.hits-me|.hits-foe — кому досталось;
 *   .fg-line.fall-me|.fall-foe — кто ушёл с арены;
 *   .fg-secs.is-last           — последние пять секунд хода;
 *   .fg-wait.is-thinking       — ход принят, ждём соперника;
 *   .offtop-row[data-fresh]    — последняя строка потока Оффтопа.
 */

/* ── Время и кривые ────────────────────────────────────────────────────────── */
/* Дольше 320 мс — только круговой отсчёт хода и угасание проигравшего. */

:root {
  --mo-tap: 110ms;    /* отклик под пальцем */
  --mo-row: 140ms;    /* строка чата въезжает */
  --mo-line: 160ms;   /* строка лога въезжает */
  --mo-flash: 120ms;  /* вспышка удара */
  --mo-shake: 160ms;  /* сдвиг аватара */
  --mo-crit: 200ms;   /* меткий удар: вспышка ярче и дольше */
  --mo-bar: 260ms;    /* полосы Жизни и Запала */
  --mo-panel: 200ms;  /* появление панели вызова */
  --mo-fall: 400ms;   /* аватар проигравшего гаснет */
  --mo-wait: 1200ms;  /* пульс ожидания «соперник думает» */

  --mo-hit: rgba(229, 72, 77, .55);   /* красная вспышка попадания */
  --mo-hit-hard: rgba(229, 72, 77, .8);
  --mo-guard: rgba(11, 93, 215, .55); /* синяя вспышка блока */
}

/* ── Полосы Жизни и Запала ─────────────────────────────────────────────────── */
/*
 * Полоса двигается transform: scaleX(), а не width. Долю кладёт экран боя в
 * переменную --fill на .fg-vital (значение приходит из состояния, JS-твинера
 * нет). Инлайновый width из core.js bar() перебиваем !important — иначе он
 * сильнее любого правила таблицы стилей.
 *
 * Число рядом с полосой (.bar-text) меняется скачком: цифра обязана быть
 * точной сразу, полоса её не догоняет.
 */

.bar-fill {
  transform-origin: left center;
  /* Ширину не анимируем никогда: только transform. */
  transition: transform var(--mo-bar) linear;
}

.fg-vital[style*="--fill"] .bar-fill {
  width: 100% !important;
  transform: scaleX(var(--fill, 1));
}

/* Полосе, из которой только что выбили Жизнь, даём одну короткую вспышку:
   длина уже точная, вспышка лишь показывает, какая полоса изменилась. */
@keyframes mo-bar-hit {
  from { filter: brightness(1.75); }
  to { filter: none; }
}

/* ── Часы хода ─────────────────────────────────────────────────────────────── */
/*
 * Полоса отсчёта убывает линейно все 30 секунд: экран перерисовывает её раз в
 * 250 мс, ширина каждый раз точная — ничего не сглаживаем и не привираем.
 * Последние пять секунд — цвет в кровь и пульс раз в секунду.
 *
 * Анимации вешаем на сам .fg-clock, а не на его содержимое: внутренности часов
 * меняются четыре раза в секунду, и любая анимация на них начиналась бы заново.
 */

.fg-clock .bar-fill { transition: none; }

.fg-clock:has(.fg-secs.is-last) .bar-fill { background: var(--blood); }
.fg-secs.is-last { color: var(--blood); }

.fg-clock:has(.fg-secs.is-last) { animation: mo-beat 1s ease-in-out infinite; }

@keyframes mo-beat {
  0%, 100% { opacity: 1; }
  50% { opacity: .55; }
}

/* «Ход принят, соперник думает» — медленный пульс: ждём не мы, а сеть. */
.fg-clock:has(.fg-wait.is-thinking) { animation: mo-wait var(--mo-wait) ease-in-out infinite; }

@keyframes mo-wait {
  0%, 100% { opacity: 1; }
  50% { opacity: .62; }
}

/* ── Удар: вспышка и сдвиг аватара ─────────────────────────────────────────── */
/*
 * Вздрагивает тот, кому досталось. Кто это — видно по строке лога текущего
 * раунда: .hits-me или .hits-foe. Экран перерисовывается целиком, поэтому
 * аватар каждый раз узел новый — разовая анимация играет ровно один раз.
 */

/* Кант вспышки должен лежать по кругу аватара, а не по квадрату места под него. */
.fg-face .avatar { border-radius: 50%; }

@keyframes mo-shake {
  0% { transform: translateX(0); }
  30% { transform: translateX(-4px); }
  65% { transform: translateX(2px); }
  100% { transform: translateX(0); }
}

@keyframes mo-shake-hard {
  0% { transform: translateX(0); }
  25% { transform: translateX(-7px); }
  55% { transform: translateX(4px); }
  80% { transform: translateX(-2px); }
  100% { transform: translateX(0); }
}

@keyframes mo-flash-hit {
  from { box-shadow: 0 0 0 3px var(--mo-hit); filter: brightness(1.25) saturate(1.15); }
  to { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0); filter: none; }
}

@keyframes mo-flash-crit {
  from { box-shadow: 0 0 0 5px var(--mo-hit-hard); filter: brightness(1.45) saturate(1.25); }
  to { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0); filter: none; }
}

/* Блок — синяя вспышка без единого пикселя сдвига: удар пришёл, но не прошёл. */
@keyframes mo-flash-guard {
  from { box-shadow: 0 0 0 4px var(--mo-guard); }
  to { box-shadow: 0 0 0 0 rgba(11, 93, 215, 0); }
}

/* Промах — бьющий чуть проседает по прозрачности: замах был, толку нет. */
@keyframes mo-miss {
  from { opacity: .85; }
  to { opacity: 1; }
}

/* Обычное попадание: сдвиг 3–4 px и красная вспышка. */
#app:has(.fg-line[data-fresh].is-strike.hits-me) .fg-side--me .fg-face,
#app:has(.fg-line[data-fresh].is-strike.hits-foe) .fg-side--foe .fg-face {
  animation: mo-shake var(--mo-shake) ease-out;
}
#app:has(.fg-line[data-fresh].is-strike.hits-me) .fg-side--me .fg-face .avatar,
#app:has(.fg-line[data-fresh].is-strike.hits-foe) .fg-side--foe .fg-face .avatar {
  animation: mo-flash-hit var(--mo-flash) ease-out;
}

/* Меткий удар и пробитый блок: сдвиг 6–8 px и вспышка ярче. */
#app:has(.fg-line[data-fresh].is-crit.hits-me) .fg-side--me .fg-face,
#app:has(.fg-line[data-fresh].is-crit.hits-foe) .fg-side--foe .fg-face {
  animation: mo-shake-hard var(--mo-crit) ease-out;
}
#app:has(.fg-line[data-fresh].is-crit.hits-me) .fg-side--me .fg-face .avatar,
#app:has(.fg-line[data-fresh].is-crit.hits-foe) .fg-side--foe .fg-face .avatar {
  animation: mo-flash-crit var(--mo-crit) ease-out;
}

/* Блок: вспышка есть, сдвига нет. */
#app:has(.fg-line[data-fresh].is-block.hits-me) .fg-side--me .fg-face .avatar,
#app:has(.fg-line[data-fresh].is-block.hits-foe) .fg-side--foe .fg-face .avatar {
  animation: mo-flash-guard var(--mo-flash) ease-out;
}

/* Промах: проседает аватар БЬЮЩЕГО, то есть стороны, противоположной цели. */
#app:has(.fg-line[data-fresh].is-dodge.hits-me) .fg-side--foe .fg-face,
#app:has(.fg-line[data-fresh].is-dodge.hits-foe) .fg-side--me .fg-face {
  animation: mo-miss var(--mo-flash) ease-out;
}

/* Полоса Жизни того, кому досталось, коротко вспыхивает: видно, что изменилась. */
#app:has(.fg-line[data-fresh]:is(.is-strike, .is-crit).hits-me) .fg-side--me .bar-fill.hp,
#app:has(.fg-line[data-fresh]:is(.is-strike, .is-crit).hits-foe) .fg-side--foe .bar-fill.hp {
  animation: mo-bar-hit var(--mo-bar) linear;
}

/* Смерть: аватар проигравшего гаснет до 40% за 400 мс и таким остаётся. */
@keyframes mo-fall {
  from { opacity: 1; }
  to { opacity: .4; }
}

#app:has(.fg-line[data-fresh].fall-me) .fg-side--me .fg-face,
#app:has(.fg-line[data-fresh].fall-foe) .fg-side--foe .fg-face {
  animation: mo-fall var(--mo-fall) ease-out forwards;
}

/* ── Лог боя ───────────────────────────────────────────────────────────────── */
/*
 * Въезжают только строки ТЕКУЩЕГО раунда: экран перерисовывает лог целиком, и
 * анимация всех строк разом превратилась бы в мельтешение.
 */

@keyframes mo-line-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

.fg-line[data-fresh] { animation: mo-line-in var(--mo-line) ease-out; }

/* ── Оффтоп ────────────────────────────────────────────────────────────────── */

@keyframes mo-row-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* Новая реплика приезжает снизу — оттуда, откуда её и ждут. */
.offtop-row[data-fresh] { animation: mo-row-in var(--mo-row) ease-out; }

/* Пришедший вызов: панель въезжает сверху и один раз мигает кантом.
   Больше не мигает никогда — назойливость хуже пропущенного вызова. */
@keyframes mo-call-in {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: none; }
}

@keyframes mo-call-edge {
  0% { box-shadow: 0 0 0 0 rgba(11, 93, 215, 0); }
  35% { box-shadow: 0 0 0 3px var(--mo-guard); }
  100% { box-shadow: 0 0 0 0 rgba(11, 93, 215, 0); }
}

.offtop-invite:not(.offtop-invite--sent) {
  animation: mo-call-in var(--mo-panel) ease-out, mo-call-edge 520ms ease-out;
}

/*
 * Сообщение экрана (.flash) нарочно не анимируем: оно живёт в state.flash до
 * смены экрана, и въезд повторялся бы на каждой перерисовке — то есть мигал бы
 * там, где ничего не изменилось.
 */

/* ── Чего не двигаем никогда ───────────────────────────────────────────────── */
/*
 * Всё, во что игрок целится, стоит как вкопанное: зоны удара и блока, «Вперёд!»,
 * поле чата. Отклик под пальцем у них есть, но он не сдвигает цель.
 */

.fg-pick,
.fg-pick input,
.fg-choices { transition: none; }

/* «Вперёд!» отвечает на нажатие цветом, а не смещением: под пальцем кнопка
   обязана остаться там же, где в неё целились. */
.btn-strike { transition: background-color var(--mo-tap) ease-out, color var(--mo-tap) ease-out; }
.btn-strike:active,
.offtop-say .btn:active { transform: none; filter: brightness(.92); }

/*
 * Целиком экраны и панели не анимируем нарочно: клиент перерисовывает страницу
 * одним куском, и вход всей разметки разом читался бы как перезагрузка, а не
 * как изменение состояния.
 */

/* ── Доступность и слабые машины ───────────────────────────────────────────── */
/*
 * Без движения смысл обязан оставаться прежним: вспышку заменяет цвет канта,
 * угасание — сразу приглушённый аватар, въезд строки — обычное появление.
 */

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }

  /* Вместо вспышки — кант того же цвета, он никуда не денется. */
  #app:has(.fg-line[data-fresh]:is(.is-strike, .is-crit).hits-me) .fg-side--me .fg-face .avatar,
  #app:has(.fg-line[data-fresh]:is(.is-strike, .is-crit).hits-foe) .fg-side--foe .fg-face .avatar {
    box-shadow: 0 0 0 3px var(--mo-hit-hard);
  }
  #app:has(.fg-line[data-fresh].is-block.hits-me) .fg-side--me .fg-face .avatar,
  #app:has(.fg-line[data-fresh].is-block.hits-foe) .fg-side--foe .fg-face .avatar {
    box-shadow: 0 0 0 3px var(--mo-guard);
  }

  /* Вместо угасания — сразу приглушённый аватар проигравшего. */
  #app:has(.fg-line[data-fresh].fall-me) .fg-side--me .fg-face,
  #app:has(.fg-line[data-fresh].fall-foe) .fg-side--foe .fg-face { opacity: .4; }

  /* Вместо пульса последних секунд — только цвет. */
  .fg-clock:has(.fg-secs.is-last) { opacity: 1; }
}
