/* ==========================================================================
   animations.css — motion only. Every rule here is disabled wholesale by the
   reduced-motion block at the bottom, so the app stays fully usable without it.
   ========================================================================== */

@keyframes sheet-in {
  from { transform: translateY(14%); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}

@keyframes sheet-out {
  from { transform: translateY(0);   opacity: 1; }
  to   { transform: translateY(10%); opacity: 0; }
}

@keyframes card-in {
  from { transform: translateY(10px) scale(0.97); opacity: 0; }
  to   { transform: translateY(0)    scale(1);    opacity: 1; }
}

@keyframes card-out {
  from { transform: translateY(0)   scale(1);    opacity: 1; }
  to   { transform: translateY(6px) scale(0.98); opacity: 0; }
}

@keyframes fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }

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

@keyframes fab-in {
  from { transform: scale(0.4) rotate(-45deg); opacity: 0; }
  to   { transform: scale(1)   rotate(0);      opacity: 1; }
}

@keyframes toast-in {
  from { transform: translate(-50%, 12px); opacity: 0; }
  to   { transform: translate(-50%, 0);    opacity: 1; }
}

@keyframes toast-out {
  from { transform: translate(-50%, 0);    opacity: 1; }
  to   { transform: translate(-50%, 10px); opacity: 0; }
}

@keyframes banner-in {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

@keyframes alert-in {
  from { transform: scale(0.9);  opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}

@keyframes alert-out {
  from { transform: scale(1);    opacity: 1; }
  to   { transform: scale(0.96); opacity: 0; }
}

@keyframes label-swap {
  from { transform: translateY(-5px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-5px); }
  75%      { transform: translateX(5px); }
}

/* --- Dialogs -------------------------------------------------------------- */
/* The classes are toggled by js/dialog.js around showModal()/close() so the
   exit animation gets a chance to finish before the dialog is torn down. */

.sheet.is-open    { animation: sheet-in  var(--dur-slow) var(--ease-spring) both; }
.sheet.is-closing { animation: sheet-out var(--dur-mid)  var(--ease-out)    both; }

.sheet.is-open::backdrop    { animation: fade-in  var(--dur-mid)  var(--ease-out) both; }
.sheet.is-closing::backdrop { animation: fade-out var(--dur-mid)  var(--ease-out) both; }

@media (min-width: 700px) {
  .sheet.is-open    { animation: card-in  var(--dur-mid) var(--ease-spring) both; }
  .sheet.is-closing { animation: card-out var(--dur-fast) var(--ease-out)   both; }
}

/* --- Moving between screens ------------------------------------------------ */

@keyframes view-in-forward {
  from { transform: translateX(16%);  opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

@keyframes view-in-back {
  from { transform: translateX(-24%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

.view-in-forward { animation: view-in-forward var(--dur-slow) var(--ease-out) both; }
.view-in-back    { animation: view-in-back    var(--dur-mid)  var(--ease-out) both; }

/* Dragged rightwards to leave a folder; see js/nav-swipe.js. The header moves
   with the list so the screen reads as one piece rather than a sliding panel
   under a fixed bar. --nav-x is set on <body> and inherited by both. */
body.is-sliding .app-header,
body.is-sliding .app-main,
body.is-settling .app-header,
body.is-settling .app-main {
  transform: translateX(var(--nav-x, 0px));
  will-change: transform;
}

body.is-settling .app-header,
body.is-settling .app-main {
  /* js/nav-swipe.js sets --nav-duration from how far there is left to go. */
  transition: transform var(--nav-duration, var(--dur-mid)) var(--ease-out);
}

/* --- Swipe to dismiss ------------------------------------------------------ */
/* A finished animation with fill-mode "both" keeps overriding transform, so
   the drag has to cancel it outright. js/dialog.js drops .is-open once the
   entrance animation ends, which keeps the two from fighting. */

.sheet.is-grabbed {
  animation: none;
  transform: translateY(var(--drag-y, 0px));
  will-change: transform;
}

.sheet.is-settling {
  transition: transform var(--dur-mid) var(--ease-out);
}

/* --- Confirmation popup ---------------------------------------------------- */

.alert.is-open    { animation: alert-in  var(--dur-mid)  var(--ease-spring) both; }
.alert.is-closing { animation: alert-out var(--dur-fast) var(--ease-out)    both; }

.alert.is-open::backdrop    { animation: fade-in  var(--dur-mid) var(--ease-out) both; }
.alert.is-closing::backdrop { animation: fade-out var(--dur-mid) var(--ease-out) both; }

/* --- Word list ------------------------------------------------------------ */
/* Staggered only on the first paint after a full render. The delay comes from
   nth-child rather than an inline custom property so the CSP can stay strict. */

.word-list.is-entering .word-row { animation: row-in var(--dur-mid) var(--ease-out) both; }

.word-list.is-entering li:nth-child(1)  .word-row { animation-delay: 0ms; }
.word-list.is-entering li:nth-child(2)  .word-row { animation-delay: 22ms; }
.word-list.is-entering li:nth-child(3)  .word-row { animation-delay: 44ms; }
.word-list.is-entering li:nth-child(4)  .word-row { animation-delay: 66ms; }
.word-list.is-entering li:nth-child(5)  .word-row { animation-delay: 88ms; }
.word-list.is-entering li:nth-child(6)  .word-row { animation-delay: 110ms; }
.word-list.is-entering li:nth-child(7)  .word-row { animation-delay: 132ms; }
.word-list.is-entering li:nth-child(8)  .word-row { animation-delay: 154ms; }
.word-list.is-entering li:nth-child(9)  .word-row { animation-delay: 176ms; }
.word-list.is-entering li:nth-child(10) .word-row { animation-delay: 198ms; }
.word-list.is-entering li:nth-child(11) .word-row { animation-delay: 220ms; }
.word-list.is-entering li:nth-child(12) .word-row { animation-delay: 242ms; }
/* Anything past the twelfth row is below the fold on every phone. */
.word-list.is-entering li:nth-child(n+13) .word-row { animation-delay: 264ms; }

/* A row that has just been created slides in on its own. */
.word-row.is-new { animation: row-in var(--dur-mid) var(--ease-spring) both; }

/* --- Chrome --------------------------------------------------------------- */

.fab { animation: fab-in var(--dur-slow) var(--ease-spring) both; }

.empty:not([hidden]) { animation: fade-in var(--dur-slow) var(--ease-out) both; }

.toast:not([hidden])   { animation: toast-in  var(--dur-mid) var(--ease-spring) both; }
.toast.is-leaving      { animation: toast-out var(--dur-fast) var(--ease-out)   both; }

.banner:not([hidden]) { animation: banner-in var(--dur-slow) var(--ease-spring) both; }

.form.is-invalid { animation: shake var(--dur-slow) var(--ease-out) both; }

/* The new sort label drops in, so the tap reads as a change of state. */
.sort__label.is-changed { animation: label-swap var(--dur-mid) var(--ease-spring) both; }

/* --- Reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after,
  .sheet.is-open::backdrop,
  .sheet.is-closing::backdrop {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
