/* Motion. Every animation in the app is in this file.

   One file for two reasons. It is the only way to answer "what moves?"
   without reading ten stylesheets, and it gives the reduced-motion kill
   switch in tokens.css exactly one thing to beat. That switch —
   html[data-motion="reduced"] * { transition:none; animation:none } — is
   what makes everything here safe to add: a person who has asked for less
   motion, in Settings or in their operating system, gets none of it.

   The rules this follows, and they are rules rather than taste:

   Motion points at what changed. It is not decoration and it is not reward.
   Every animation below is attached to something that just became true.

   Nothing waits for it. Every entrance animates a property that does not
   affect layout — opacity and transform only — and nothing here delays a
   control becoming usable. An interface that makes you wait for its own
   animation is slower than one that has none.

   It is short. 160-240ms for anything a learner is looking at directly.
   The one sequence that runs longer is the skill-unlocked screen, which is
   staged rather than slow, and which nobody is trying to get past.

   It stays inside the voice. docs/DECISIONS.md records a dry, unencouraging
   mascot: "Nothing learned yet. Let's fix that", not "You've got this!".
   Confetti would make that writing wrong. What is here instead is timing —
   the facts arrive in an order, which reads as ceremony without ever
   congratulating anybody. */

/* The easing and the three durations are in tokens.css with every other
   token. There is exactly one :root in this app and it is not here:
   contrast.test.js resolves variables by collecting :root rules in source
   order, so a second declaration would keep working in the browser while
   quietly changing what that audit measures. */

/* ---------------------------------------------------------------------
   BETWEEN SCREENS  (TODO 25)
   These are display:none -> display:block swaps, which cannot be
   transitioned — a transition needs two rendered states and there is only
   ever one. An animation runs on the frame the element starts being drawn,
   which is exactly the event worth marking, so all of these are keyframes.
   --------------------------------------------------------------------- */
@keyframes screen-in{
  from{ opacity:0; transform:translateY(6px); }
  to  { opacity:1; transform:none; }
}

/* The tab screens. #path-screen is display-toggled by goTab like the rest,
   so it gets the same entrance.

   No class and no JavaScript: goTab toggles display, and going from
   display:none back to display:block restarts an element's animations by
   itself. The event is already in the DOM; it only had to be listened to. */
#profile-screen.show,
#settings-screen.show,
#sandbox-screen.show,
#path-screen{
  animation:screen-in var(--t-screen) var(--ease-out);
}

/* The overlays: lesson, setup, the confirm dialog, the ad. Slightly more
   travel than a tab screen because they arrive over something rather than
   instead of it, and the eye needs to be told which layer is new. */
@keyframes overlay-in{
  from{ opacity:0; transform:translateY(10px) scale(.995); }
  to  { opacity:1; transform:none; }
}
#lesson.show,
#placement.show,
#ask.show,
#ad-modal.show{
  animation:overlay-in var(--t-base) var(--ease-out);
}

/* ---------------------------------------------------------------------
   INSIDE A LESSON  (TODO 36)
   --------------------------------------------------------------------- */

/* The feedback panel. It appears below the options, under the thumb, which
   is the part of the screen the eye is least likely to be on — so this is
   the animation that earns its place most clearly. Travel is downward-origin
   so it reads as having come from the answer that caused it. */
@keyframes fb-in{
  from{ opacity:0; transform:translateY(-4px); }
  to  { opacity:1; transform:none; }
}
.feedback{ animation:fb-in var(--t-base) var(--ease-out); }

/* A right answer settles. One small scale, out and back, on the option that
   was chosen — the shape says "that one", which is the whole job, because
   after an answer every option is coloured and the learner has to find
   their own in a list that has all changed at once. */
@keyframes opt-right{
  0%  { transform:none; }
  45% { transform:scale(1.02); }
  100%{ transform:none; }
}
.opt.correct{ animation:opt-right var(--t-base) var(--ease-out); }

/* A wrong answer is nudged, not shaken. Three pixels, one cycle back and
   forth, and then it is over.

   The size is the decision. A hard shake is the standard way to do this and
   it is wrong here: docs/DECISIONS.md records that the failure screen was
   rewritten because it made people feel stupid, and an interface that
   physically recoils from an answer says the same thing in less time. This
   is the amount of movement that reads as "no", and no more. */
@keyframes opt-wrong{
  0%,100%{ transform:none; }
  25%    { transform:translateX(-3px); }
  75%    { transform:translateX(3px); }
}
.opt.wrong{ animation:opt-wrong 220ms var(--ease-out); }

/* XP arriving. The number is in the top bar, far from wherever the learner
   was looking when they earned it, so without this the total simply differs
   the next time they happen to glance up. main.js adds the class only when
   the figure has actually grown, and removes it when the animation ends. */
@keyframes xp-bump{
  0%  { transform:none; }
  40% { transform:translateY(-2px) scale(1.14); }
  100%{ transform:none; }
}
#xp-val.gained{
  display:inline-block;      /* a span cannot be transformed while inline */
  animation:xp-bump 380ms var(--ease-out);
}

/* ---------------------------------------------------------------------
   FINISHING A SKILL  (TODO 25, the second half)
   The one place a sequence is right. Passing a module test used to look
   exactly like failing one; both simply appeared. This stages the screen
   instead: the mascot, then what you can do now, then how far that puts
   you. Three facts in the order somebody would say them.

   Staggered with nth-child rather than with JavaScript timers, so the
   reduced-motion switch removes the whole thing — including the delays.
   A timer-driven version would still hold content back from someone who
   had asked for no motion, which is worse than the animation was.
   --------------------------------------------------------------------- */
@keyframes rise-in{
  from{ opacity:0; transform:translateY(8px); }
  to  { opacity:1; transform:none; }
}
.done-wrap > *{
  animation:rise-in var(--t-base) var(--ease-out) backwards;
}
.done-wrap > :nth-child(1){ animation-delay:0ms; }
.done-wrap > :nth-child(2){ animation-delay:70ms; }
.done-wrap > :nth-child(3){ animation-delay:140ms; }
.done-wrap > :nth-child(4){ animation-delay:210ms; }
.done-wrap > :nth-child(5){ animation-delay:280ms; }
.done-wrap > :nth-child(6){ animation-delay:350ms; }
.done-wrap > :nth-child(7){ animation-delay:420ms; }

/* The mascot is the one thing that gets a flourish, and it is a small one:
   it arrives a little larger than it settles. This is the only place in the
   app where anything overshoots. */
@keyframes mascot-land{
  from{ opacity:0; transform:scale(.9); }
  60% { opacity:1; transform:scale(1.03); }
  to  { opacity:1; transform:none; }
}
.done-wrap > .mascot-big{ animation:mascot-land 340ms var(--ease-out) backwards; }

/* ---------------------------------------------------------------------
   THE PATH MOVING ON
   Closing a finished lesson redraws the card that says what is next. It is
   a different skill than it was a moment ago and nothing said so.
   --------------------------------------------------------------------- */
#next-slot.advanced{ animation:rise-in var(--t-screen) var(--ease-out); }

/* ---------------------------------------------------------------------
   PRESSES
   Not an entrance — a response. Every control gets the same one, so the
   app answers a touch consistently rather than only where somebody
   remembered to add it. transition rather than animation because there are
   two real states and the finger controls which.
   --------------------------------------------------------------------- */
.btn,.opt,.nav-btn,.small{ transition:transform 90ms var(--ease-out); }
.btn:active,.opt:not(.disabled):active,.small:active{ transform:scale(.985); }
