/* ═══════════════════════════════════════════════════════════════════════
   Mascot presentation.

   mascot_rig.js drives breath, gaze and blink from one rAF loop. This file
   owns everything the rig expresses through classes: the blink itself, the
   one-shot reactions, and the entrance.

   The reactions are deliberately short and physical. A guide that leans in
   when you hesitate and nods when you get it right is a character; one that
   floats and glows forever is a decoration.
   ═══════════════════════════════════════════════════════════════════════ */

.qq-rig { display: inline-block; will-change: transform; }
.qq-rig svg { display: block; overflow: visible; transform-origin: 50% 62%; }

/* Eyes are moved by the rig; keep the transition short so gaze reads as
   muscle rather than as a slide. */
.qq-rig .aca-eyes {
  transition: transform 90ms linear;
  transform-origin: center;
}

/* ── blink ───────────────────────────────────────────────────────────────
   Scaling the eye group on Y is the cheapest convincing blink: no extra
   geometry, and it keeps whatever expression is drawn underneath. */
.qq-rig.is-blinking .aca-eyes {
  transform: scaleY(.08) translateY(1px);
  transition: transform 55ms cubic-bezier(.4, 0, .2, 1);
}

/* ── one-shot reactions ─────────────────────────────────────────────────
   Each is triggered by something the player just did. */

/* Agreement. Two beats, second smaller — a real nod decays. */
.qq-rig.react-nod svg { animation: rigNod .52s var(--qq-ease) both; }
@keyframes rigNod {
  0%   { transform: translateY(0) rotate(0deg); }
  28%  { transform: translateY(3.5px) rotate(2deg); }
  55%  { transform: translateY(-1.5px) rotate(-1deg); }
  78%  { transform: translateY(1.6px) rotate(.6deg); }
  100% { transform: translateY(0) rotate(0deg); }
}

/* Disagreement. Horizontal, decaying, never symmetric. */
.qq-rig.react-shake svg { animation: rigShake .48s var(--qq-ease) both; }
@keyframes rigShake {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  18%      { transform: translateX(-4px) rotate(-3deg); }
  40%      { transform: translateX(3.4px) rotate(2.4deg); }
  62%      { transform: translateX(-2.2px) rotate(-1.5deg); }
  82%      { transform: translateX(1.2px) rotate(.7deg); }
}

/* Arrival / attention. Overshoot and settle. */
.qq-rig.react-pop svg { animation: rigPop .42s var(--qq-spring) both; }
@keyframes rigPop {
  0%   { transform: scale(.82); }
  58%  { transform: scale(1.075); }
  100% { transform: scale(1); }
}

/* Considering. A slow tilt and a held beat — reads as thought, not motion. */
.qq-rig.react-think svg { animation: rigThink .9s var(--qq-ease) both; }
@keyframes rigThink {
  0%   { transform: rotate(0deg) translateY(0); }
  30%  { transform: rotate(-6deg) translateY(-1.5px); }
  70%  { transform: rotate(-5deg) translateY(-1px); }
  100% { transform: rotate(0deg) translateY(0); }
}

/* Celebration. Two hops, the second lower, plus a little spin-out. */
.qq-rig.react-cheer svg { animation: rigCheer .76s var(--qq-spring) both; }
@keyframes rigCheer {
  0%   { transform: translateY(0) scale(1) rotate(0deg); }
  22%  { transform: translateY(-11px) scale(1.06) rotate(-4deg); }
  44%  { transform: translateY(0) scale(.97) rotate(2deg); }
  62%  { transform: translateY(-5px) scale(1.03) rotate(3deg); }
  100% { transform: translateY(0) scale(1) rotate(0deg); }
}

/* Something went wrong. Recoil, then recover — never a comic wobble. */
.qq-rig.react-flinch svg { animation: rigFlinch .38s var(--qq-ease) both; }
@keyframes rigFlinch {
  0%   { transform: translate(0,0) scale(1); }
  30%  { transform: translate(-2px, 2.5px) scale(.955); }
  100% { transform: translate(0,0) scale(1); }
}

/* Curiosity — used when the player stalls. The guide leans toward the thing
   they are stuck on instead of nagging with a popup. */
.qq-rig.react-lean svg { animation: rigLean .52s var(--qq-ease) both; }
@keyframes rigLean {
  0%   { transform: translateX(0) rotate(0deg); }
  45%  { transform: translateX(4px) rotate(5deg); }
  100% { transform: translateX(0) rotate(0deg); }
}

.qq-rig.react-bounce svg { animation: rigBounce .62s var(--qq-spring) both; }
@keyframes rigBounce {
  0%   { transform: translateY(0); }
  35%  { transform: translateY(-9px); }
  60%  { transform: translateY(0); }
  78%  { transform: translateY(-3px); }
  100% { transform: translateY(0); }
}

/* ── entrance ────────────────────────────────────────────────────────────
   Characters arrive with weight: up from below, slight overshoot, settle.
   A fade-in makes them feel like images being loaded. */
.qq-mascot-enter, .qq-cast-mascot-enter {
  animation: rigEnter .6s var(--qq-spring) both;
}
@keyframes rigEnter {
  0%   { opacity: 0; transform: translateY(18px) scale(.8) rotate(-6deg); }
  60%  { opacity: 1; transform: translateY(-4px) scale(1.04) rotate(1.5deg); }
  100% { opacity: 1; transform: translateY(0) scale(1) rotate(0deg); }
}

/* ── speaking ────────────────────────────────────────────────────────────
   A tiny bob while text types out. Cheap, and it ties the words to the
   character saying them. */
.qq-rig.is-speaking svg { animation: rigSpeak .42s ease-in-out infinite; }
@keyframes rigSpeak {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-1.4px) scale(1.008); }
}

/* ── the antenna tip, when a character has one ──────────────────────── */
.qq-rig .aca-antenna-tip { animation: rigAntenna 2.8s var(--qq-ease) infinite; transform-origin: center; }
@keyframes rigAntenna {
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}
.qq-rig.react-cheer .aca-antenna-tip,
.qq-rig.react-pop .aca-antenna-tip { animation: rigAntennaFlare .6s var(--qq-spring) both; }
@keyframes rigAntennaFlare {
  0%   { opacity: 1; transform: scale(1); }
  40%  { opacity: 1; transform: scale(1.9); }
  100% { opacity: .7; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .qq-rig svg,
  .qq-rig.is-speaking svg,
  .qq-rig .aca-antenna-tip { animation: none !important; }
  .qq-rig .aca-eyes { transition: none; }
}

/* ── Character idles ──────────────────────────────────────────────────────
   The rig gives every mascot breath, gaze and blink, so nothing was actually
   frozen — but only Orion had a moving part of his own, and a cast where four
   of five faces share one generic idle reads as one character wearing four
   costumes.

   Each of these animates the prop that says who the character is, at a rate
   that matches their temperament: Vega scans, Nova sparks, Atlas settles,
   Lyra resonates, Orion listens. Long periods and small amplitudes on
   purpose — an idle you consciously notice is a distraction, one you never
   notice is what makes a drawing feel alive. */

/* Vega · the analyst. The lens catches light as it sweeps a chart. */
.mascot-vega .qq-m-lens{
  transform-box: fill-box;
  transform-origin: center;
  animation: qqVegaScan 7.5s cubic-bezier(.45,.05,.35,1) infinite;
}
.mascot-vega .qq-m-lens-arm{
  transform-box: fill-box;
  transform-origin: 0% 0%;
  animation: qqVegaArm 7.5s cubic-bezier(.45,.05,.35,1) infinite;
}
@keyframes qqVegaScan{
  0%,58%   { transform: translateX(0) scale(1); opacity: 1; }
  66%      { transform: translateX(-1.6px) scale(1.09); opacity: .82; }
  74%      { transform: translateX(.9px) scale(1.02); opacity: 1; }
  82%,100% { transform: translateX(0) scale(1); opacity: 1; }
}
@keyframes qqVegaArm{
  0%,58%   { transform: rotate(0deg); }
  66%      { transform: rotate(-5deg); }
  74%      { transform: rotate(2deg); }
  82%,100% { transform: rotate(0deg); }
}

/* Nova · momentum. A spark does not glow steadily, it catches. */
.mascot-nova .qq-m-spark{
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: qqNovaSpark 3.4s steps(1, end) infinite;
}
@keyframes qqNovaSpark{
  0%,62%  { opacity: 1;  transform: scaleY(1); }
  66%     { opacity: .35; transform: scaleY(.9); }
  69%     { opacity: 1;  transform: scaleY(1.14); }
  72%     { opacity: .5; transform: scaleY(.96); }
  76%,100%{ opacity: 1;  transform: scaleY(1); }
}

/* Atlas · risk. Heavy, slow, and it always comes back to the same place —
   the whole character is a thing that holds. */
.mascot-atlas .qq-m-brace{
  transform-box: fill-box;
  transform-origin: center;
  animation: qqAtlasHold 9s cubic-bezier(.5,0,.5,1) infinite;
}
@keyframes qqAtlasHold{
  0%,100% { transform: scaleX(1);    opacity: 1; }
  46%     { transform: scaleX(.965); opacity: .88; }
}

/* Lyra · structure. The arc rings and settles, like a plucked string. */
.mascot-lyra .qq-m-arc{
  stroke-dasharray: 92;
  animation: qqLyraRing 8.2s ease-in-out infinite;
}
@keyframes qqLyraRing{
  0%,54%  { stroke-dashoffset: 0;  opacity: 1; }
  62%     { stroke-dashoffset: 10; opacity: .72; }
  70%     { stroke-dashoffset: -6; opacity: .92; }
  78%,100%{ stroke-dashoffset: 0;  opacity: 1; }
}

/* Orion · the guide. He is listening, so the antenna leans toward you. */
.mascot-orion .qq-m-antenna{
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: qqOrionListen 6.4s ease-in-out infinite;
}
@keyframes qqOrionListen{
  0%,100% { transform: rotate(0deg); }
  30%     { transform: rotate(3.5deg); }
  64%     { transform: rotate(-2.5deg); }
}

/* A reaction is louder than an idle: it is the character responding to the
   player, so each one responds in their own way rather than all nodding. */
.qq-rig.is-reacting.mascot-vega .qq-m-lens{ animation: qqVegaScan .9s ease-out 1; }
.qq-rig.is-reacting.mascot-nova .qq-m-spark{ animation: qqNovaSpark .7s steps(1,end) 2; }
.qq-rig.is-reacting.mascot-atlas .qq-m-brace{ animation: qqAtlasHold 1.1s ease-out 1; }
.qq-rig.is-reacting.mascot-lyra .qq-m-arc{ animation: qqLyraRing 1.1s ease-out 1; }
.qq-rig.is-reacting.mascot-orion .qq-m-antenna{ animation: qqOrionListen 1s ease-out 1; }

@media (prefers-reduced-motion: reduce){
  .mascot-vega .qq-m-lens,
  .mascot-vega .qq-m-lens-arm,
  .mascot-nova .qq-m-spark,
  .mascot-atlas .qq-m-brace,
  .mascot-lyra .qq-m-arc,
  .mascot-orion .qq-m-antenna{ animation: none !important; }
}
