/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   RealSkills.in — Animations v3 "Mira Clean" (A5 deliverable)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

   Additive micro-interaction layer. Loads AFTER style.css (A1) so my rules
   compose with A1's design system. The HTML pages already link these files
   in the correct order:

     <link rel="stylesheet" href="/css/style.css?v=20260901b">
     <link rel="stylesheet" href="/css/animations.css?v=20260901b">

   PROTOCOL (per DESIGN-SPEC.md §Cross-agent coordination):
   - I do NOT redefine classes A1 already wrote.
   - I only ADD new animation-specific classes OR override animation properties
     on existing classes (transition, animation, transform, opacity).
   - I do not touch A1's color, layout, padding, or typography rules.
   - Keyframes use the `rsa-` prefix ("animations.css A5") so they cannot
     collide with A1's `rs-` keyframes.

   GUARDRAILS (research-grounded):
   - Surma (2017) — every animation/transition uses `transform` + `opacity`
     only. NO box-shadow animation, NO filter animation, NO width/height
     animation. (Static box-shadow on a `::before`/`::after` is fine — it's
     a paint, not an animation; we transition its opacity to fade it in.)
   - prefers-reduced-motion respected — A1 already kills every animation
     globally via `*` + `!important`; I add an explicit block at the bottom
     of this file to disable MY animations specifically (belt + suspenders).
   - Schultz (1998, 2016) — anticipation cues: Mira pulse ring + streak
     fire flicker (A1) + leading-edge glow + Mira idle double pulse (mine).
     Dopamine fires at CUES, not rewards — these cues are calm, never
     slot-machine.
   - Dixon et al. (2010, 2013) — LDW rule: win sounds only on real wins.
     No visual "win" cues are added on non-win interactions. The achievement
     veil + confetti + correct pulse only fire on real achievements.
   - Hattie & Timperley (2007) — quiz feedback cue (correct pulse) is
     task-focused, never self-focused.
   - Berney & Betrancourt (2016) — animation only when change is intrinsic.
     Every animation here communicates a state change (focus, press, win,
     progress, arrival) — never decorative.

   20 micro-interactions, each documented inline below.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */


/* ── 0. Reduced-motion (explicit; A1's global * rule already covers this) ─ */
/* This block is intentionally near the top so it's easy to audit. It does
   NOT use !important because A1's existing `*` rule already wins by
   `!important`. The rules below are belt-and-suspenders for my classes
   and pseudo-elements specifically. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
@media (prefers-reduced-motion: reduce) {
  /* Disable every animation I add. Transitions are reduced to 0.001ms by
     A1's global rule already, but I repeat the key ones here for clarity. */
  .btn::after,
  .progress-fill::before,
  .rs-mira-svg,
  .rs-notif,
  .achievement-veil::after,
  .rs-confetti-piece::after,
  .module-chip::after,
  .spinner,
  .quote-card .quote-text,
  .quote-card .quote-author,
  .bento .stat-card,
  .stat-card-value,
  .slide-enter,
  .quiz-option.correct,
  .form-field::after,
  .btn-red::before,
  .rs-sound-toggle:active,
  .rs-icon.is-spinning,
  .is-spinning.rs-icon,
  .fade-in-up {
    animation: none !important;
  }
  /* The Mira idle pulse + spinner pulse + correct pulse + settle bounce
     also need their transforms reset to the rest state so the element
     doesn't get stuck mid-animation. */
  .rs-mira-svg,
  .stat-card-value,
  .quiz-option.correct,
  .fade-in-up,
  .slide-enter,
  .quote-card .quote-text,
  .quote-card .quote-author,
  .bento .stat-card {
    transform: none !important;
    opacity: 1 !important;
  }
  /* Hide decorative pseudo-elements that are pure motion. */
  .achievement-veil::after,
  .rs-confetti-piece::after,
  .module-chip::after {
    display: none !important;
  }
  /* The button ripple + focus ring expand are transition-based; reset. */
  .btn::after { transform: scale(0) !important; opacity: 0 !important; }
  .form-field::after { transform: scale(0.96) !important; opacity: 0 !important; }
  .btn-red::before { opacity: 0 !important; }
  .progress-fill::before { opacity: 0.6 !important; }  /* keep a static glow */
}


/* ── 1. Button press ripple ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* CSS-only ripple on :active. A1's .btn already transitions `transform`
   on :hover/:active (lift + scale 0.98). I add a ::after radial-gradient
   that expands while the button is held. On release, the transition
   smoothly reverses (which reads as a press-and-release tactile cue).
   Pure transform + opacity. Requires position:relative + overflow:hidden
   on .btn — both additive (A1's .btn has neither). ━━━━━━━━━━━━━━━━━━ */
.btn { position: relative; overflow: hidden; }
.btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: radial-gradient(circle, currentColor 0%, transparent 70%);
  opacity: 0;
  transform: scale(0);
  pointer-events: none;
  transition:
    transform 0.4s var(--rs-ease-out),
    opacity 0.4s var(--rs-ease-out);
  will-change: transform, opacity;
}
.btn:active::after {
  transform: scale(20);
  opacity: 0.4;
}
/* Disabled buttons shouldn't ripple. */
.btn:disabled::after { display: none; }


/* ── 2. Card hover lift — shadow growth ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .card-hover:hover lifts 2px + steps the shadow up to --rs-shadow-md.
   I extend the box-shadow transition duration to --rs-dur-slow so the
   shadow grows in a softer, more pronounced arc, and I bump the :hover
   shadow to --rs-shadow-lg for a clearer "lift" feeling. Both overrides
   are pure animation-property overrides (transition + box-shadow on the
   :hover state) — no layout/color changes. ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.card-hover {
  transition:
    transform var(--rs-dur-base) var(--rs-ease-out),
    box-shadow var(--rs-dur-slow) var(--rs-ease-out),
    border-color var(--rs-dur-base) var(--rs-ease-out);
}
.card-hover:hover {
  box-shadow: var(--rs-shadow-lg);
}
/* Touch / keyboard equivalent: focus-visible also lifts (per spec rule 2:
   every hover has a focus-visible + active equivalent for touch). */
.card-hover:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--rs-shadow-lg);
  border-color: var(--rs-border-strong);
}


/* ── 3. Progress bar leading-edge glow ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .progress-fill::after is the shimmer (translateX sweep). A1 does
   NOT use .progress-fill::before, so I take it for the leading-edge glow:
   a soft saffron-tinted radial highlight at the right edge of the fill
   (the "leading edge" — the boundary between filled and unfilled). The
   fill has `overflow:hidden` (A1), so the glow is clipped to the fill
   width — exactly the filled portion, which is what we want.
   The glow pulses subtly (opacity only — Surma-compliant) to draw the eye
   to progress (Schultz 1998 anticipation cue). ━━━━━━━━━━━━━━━━━━━━━━ */
.progress-fill::before {
  content: '';
  position: absolute;
  top: -3px;
  right: -3px;
  bottom: -3px;
  width: 14px;
  border-radius: var(--rs-r-pill);
  background: radial-gradient(ellipse at center,
    rgba(255, 255, 255, 0.75) 0%,
    rgba(255, 107, 53, 0.45) 35%,
    transparent 75%);
  pointer-events: none;
  opacity: 0.6;
  animation: rsa-edge-glow 2s ease-in-out infinite alternate;
  will-change: opacity;
  z-index: 1;
}


/* ── 4. Stat-card value count-up entrance ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* The number itself fades in + scales 0.92 → 1.0 with a spring curve on
   first paint. Pure transform + opacity. `both` filling mode holds the
   invisible initial state until the animation starts (so the number
   doesn't flash before fading in). ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.stat-card-value {
  animation: rsa-count-in 0.6s var(--rs-ease-spring) both;
  will-change: transform, opacity;
}


/* ── 5. Slide transition — softer entrance ━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .slide already animates via `rs-slide-in` (translateY 8px → 0,
   spring curve). Authors who want a SOFTER entrance add `.slide-enter`
   alongside `.slide` — this overrides the animation with a slower,
   longer-displacement, non-spring curve. Pure additive: existing .slide
   markup without .slide-enter is unaffected. ━━━━━━━━━━━━━━━━━━━━━━━ */
.slide-enter {
  animation: rsa-slide-enter 0.5s var(--rs-ease-out) both;
  will-change: transform, opacity;
}


/* ── 6. Quiz option hover — shift + border thicken ━━━━━━━━━━━━━━━━━ */
/* A1's .quiz-option:hover changes border-color + bg. I add a 2px right
   shift (transform — A1 already lists transform in the transition) and
   a thicker-LOOKING border via an inset box-shadow (avoids 0.5px layout
   shift that changing border-width would cause). The inset shadow is
   STATIC (not animated); only the existing border-color transition
   animates. The transform is added to A1's existing transition list. */
.quiz-option {
  transition:
    border-color var(--rs-dur-fast) var(--rs-ease-out),
    background var(--rs-dur-fast) var(--rs-ease-out),
    transform var(--rs-dur-fast) var(--rs-ease-spring),
    box-shadow var(--rs-dur-fast) var(--rs-ease-out);
}
.quiz-option:hover {
  transform: translateX(2px);
  box-shadow: inset 0 0 0 1.5px var(--rs-saffron);
}
.quiz-option:active {
  transform: translateX(2px) scale(0.99);
}
/* The correct/incorrect states shouldn't drift right — pin them. */
.quiz-option.correct:hover,
.quiz-option.incorrect:hover {
  transform: translateX(0);
  box-shadow: none;
}


/* ── 7. Quiz correct pulse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .quiz-option.correct sets colors only; no animation. I add a
   one-shot 400ms scale 1.0 → 1.02 → 1.0 pulse on appearance (Hattie 2007
   feed-Back cue — the pulse says "this answer is right" without text).
   Pure transform. `both` holds the final scale(1) state. ━━━━━━━━━━━━ */
.quiz-option.correct {
  animation: rsa-correct-pulse 0.4s var(--rs-ease-spring) both;
  will-change: transform;
}


/* ── 8. Mira button idle attention — 30s double pulse ━━━━━━━━━━━━━━ */
/* A1's .rs-mira-btn::before + ::after are the 2.6s anticipation pulse
   rings (Schultz 1998). I add a 30s cycle "double pulse" — two subtle
   scale bumps (one at t≈0.3s, one at t≈15.3s) — to draw the user's eye
   back to Mira periodically without nagging.
   I apply this to .rs-mira-svg (the inner SVG) so it composes cleanly
   with A1's hover/active transforms on .rs-mira-btn itself (those are
   transitions on transform; an animation on the same element would
   override them, but on the inner SVG it's a different element). Pure
   transform. Pause on hover so the user can interact without the icon
   squirming under their cursor. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.rs-mira-svg {
  animation: rsa-mira-idle 30s var(--rs-ease-in-out) infinite;
  transform-origin: 50% 50%;
  will-change: transform;
}
.rs-mira-btn:hover .rs-mira-svg,
.rs-mira-btn:active .rs-mira-svg,
.rs-mira-btn:focus-visible .rs-mira-svg {
  animation-play-state: paused;
}


/* ── 9. Notification settle bounce ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .rs-notif plays `rs-notif-in` (translateX 20px → 0, spring) on
   entrance. I append a second animation: `rsa-notif-settle` runs once
   250ms AFTER entrance, scaling 1.0 → 0.98 → 1.0 over 200ms — a small
   settling bounce after the spring arrival ( tactile "I'm here" cue).
   The two animations are comma-chained on the `animation` shorthand
   (last declaration wins, so I extend A1's list rather than replacing
   the entrance). Pure transform. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.rs-notif {
  animation:
    rs-notif-in var(--rs-dur-base) var(--rs-ease-spring),
    rsa-notif-settle 0.2s var(--rs-ease-spring) 0.25s both;
  will-change: transform, opacity;
}
/* Leaving notifications shouldn't settle — they should just exit. */
.rs-notif.is-leaving {
  animation: rs-notif-out var(--rs-dur-fast) var(--rs-ease-in) forwards;
}


/* ── 10. Achievement veil particle orbit ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* 8 small saffron dots orbit the achievement card center via box-shadow
   (8 offset positions on a single 0×0 ::after element) + a single rotate
   keyframe on the ::after. The ::after lives on .achievement-veil (the
   full-screen overlay), not on .achievement-card, because A1's card has
   `overflow:hidden` which would clip the orbit. The card is centered in
   the veil via place-items:center, so the veil's center IS the card's
   center — the orbit visually wraps the card. Pure transform. ━━━━━━━ */
.achievement-veil::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--rs-saffron);
  opacity: 0;
  pointer-events: none;
  box-shadow:
    240px 0 0 var(--rs-saffron),
    170px 170px 0 var(--rs-saffron),
    0 240px 0 var(--rs-saffron),
    -170px 170px 0 var(--rs-saffron),
    -240px 0 0 var(--rs-saffron),
    -170px -170px 0 var(--rs-saffron),
    0 -240px 0 var(--rs-saffron),
    170px -170px 0 var(--rs-saffron);
  animation: rsa-orbit 8s linear infinite, rsa-orbit-fade 0.4s var(--rs-ease-out) both;
  will-change: transform, opacity;
  z-index: 91;  /* above the veil backdrop, below the card */
}


/* ── 11. Confetti wobble via opacity flicker ━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .rs-confetti-piece animates transform (translate + rotate) +
   opacity (1 → 0.35) via `rs-confetti-fall`. Per spec instruction, I add
   the wobble via opacity flicker on a ::after pseudo — the ::after
   overlays the piece, inherits its background, and flickers opacity
   (1 → 0.3, alternate) at 2s — giving a "wobble-like" shimmer without
   disrupting A1's fall animation on the parent. Pure opacity on the
   ::after; the parent's transform/opacity animation is untouched.
   `background: inherit` picks up the inline background-color set by
   app.js when each piece is spawned. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.rs-confetti-piece { position: relative; }
.rs-confetti-piece::after {
  content: '';
  position: absolute;
  inset: 0;
  background: inherit;
  border-radius: inherit;
  pointer-events: none;
  animation: rsa-confetti-wobble 2s ease-in-out infinite alternate;
  will-change: opacity, transform;
}


/* ── 12. Module chip shimmer ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .module-chip is a solid-color pill. I add a `::after` overlay
   with a diagonal light gradient that sweeps across the chip every 8s
   (mostly idle, sweeps briefly). Position:relative + overflow:hidden
   are additive (A1's .module-chip has neither). Pure transform on the
   ::after. The sweep happens between 70% and 100% of the 8s cycle so
   the chip is calm most of the time. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.module-chip {
  position: relative;
  overflow: hidden;
}
.module-chip::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg,
    transparent 0%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 100%);
  transform: translateX(-100%);
  pointer-events: none;
  animation: rsa-module-shimmer 8s ease-in-out infinite;
  will-change: transform;
}


/* ── 13. Loading spinner soft pulse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .spinner runs `rs-spin` (rotate 360deg, 0.85s linear infinite).
   I extend the animation shorthand with a second animation: a 1.6s
   opacity pulse (1 → 0.55 → 1, ease-in-out) so the spinner also
   "breathes" while it spins. Pure opacity (paired with A1's transform).
   The two animations are comma-chained. ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.spinner {
  animation:
    rs-spin 0.85s linear infinite,
    rsa-spinner-pulse 1.6s ease-in-out infinite;
  will-change: transform, opacity;
}


/* ── 14. Quote card text + author fade-in (200ms stagger) ━━━━━━━━━ */
/* On page load, the quote text fades in first, then the author 200ms
   later. Pure transform + opacity. `both` holds the invisible initial
   state so the text doesn't flash before fading in. ━━━━━━━━━━━━━━━━ */
.quote-card .quote-text {
  animation: rsa-fade-in-up 0.5s var(--rs-ease-out) both;
  will-change: transform, opacity;
}
.quote-card .quote-author {
  animation: rsa-fade-in-up 0.5s var(--rs-ease-out) 0.2s both;
  will-change: transform, opacity;
}


/* ── 15. Stat-card entrance stagger (100ms per card, up to 4) ━━━━━━ */
/* Each .stat-card in a group fades + lifts in with a 100ms stagger
   (card 1 at 0s, card 2 at 0.1s, card 3 at 0.2s, card 4 at 0.3s).
   Pure transform + opacity. The .stat-card-value inside also runs
   rsa-count-in (#4) — the two compose (card fades in, then the number
   inside count-ins). Scoped to .bento children so this only triggers
   on dashboard grids, not on standalone stat cards elsewhere. */
.bento .stat-card:nth-child(1) { animation: rsa-fade-in-up 0.5s var(--rs-ease-out) 0s both; }
.bento .stat-card:nth-child(2) { animation: rsa-fade-in-up 0.5s var(--rs-ease-out) 0.1s both; }
.bento .stat-card:nth-child(3) { animation: rsa-fade-in-up 0.5s var(--rs-ease-out) 0.2s both; }
.bento .stat-card:nth-child(4) { animation: rsa-fade-in-up 0.5s var(--rs-ease-out) 0.3s both; }
.bento .stat-card { will-change: transform, opacity; }


/* ── 16. Form input focus ring expand ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* <input> elements are replaced elements and don't support ::after
   pseudo-elements, so the spec's "via ::after pseudo" can't apply
   directly to .form-input. Instead, I add a NEW .form-field wrapper
   class: authors wrap their input in <div class="form-field">…<input
   class="form-input">…</div> and the wrapper's ::after expands as a
   focus ring when the input inside receives focus (:focus-within).
   A1's existing .form-input:focus box-shadow still works on inputs
   that aren't wrapped — the two effects compose. Pure transform +
   opacity transition on the ::after. The ring is saffron-tinted,
   matches A1's focus glow color, and uses var(--rs-r-md) so it
   matches the input's border-radius. ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.form-field { position: relative; }
.form-field::after {
  content: '';
  position: absolute;
  inset: -2px;
  border: 2px solid var(--rs-saffron);
  border-radius: calc(var(--rs-r-md) + 2px);
  opacity: 0;
  transform: scale(0.96);
  pointer-events: none;
  transition:
    opacity var(--rs-dur-base) var(--rs-ease-out),
    transform var(--rs-dur-base) var(--rs-ease-spring);
  will-change: transform, opacity;
}
.form-field:focus-within::after {
  opacity: 0.45;
  transform: scale(1.02);
}


/* ── 17. Sign-out button hover — soft red glow ━━━━━━━━━━━━━━━━━━━ */
/* A1's .btn-red:hover tints the background. I add a ::before pseudo
   behind the button that fades in a soft rose-colored glow on hover
   (and focus-visible, for touch parity). The glow is a STATIC box-
   shadow on the ::before (not animated); only the ::before's opacity
   transitions (Surma-compliant). position:relative + z-index:-1 on the
   ::before keep it behind the button content. ━━━━━━━━━━━━━━━━━━━━ */
.btn-red { position: relative; }
.btn-red::before {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: var(--rs-r-pill);
  box-shadow: 0 8px 22px rgba(225, 29, 72, 0.35);
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  transition: opacity var(--rs-dur-base) var(--rs-ease-out);
  will-change: opacity;
}
.btn-red:hover::before,
.btn-red:focus-visible::before { opacity: 1; }


/* ── 18. Sound toggle bounce on :active ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* A1's .rs-sound-toggle:active scales to 0.94 (a press). I replace
   that single-step press with a 3-step bounce: scale 0.92 → 1.04 → 1.0
   over 0.3s with the spring curve (tactile "tock" feel). The `both`
   filling mode holds scale(1) when the animation finishes, so the
   button doesn't snap. `transform: none` on the :active rule disables
   A1's static press transform so the animation controls transform
   alone (no conflict). ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.rs-sound-toggle:active {
  transform: none;
  animation: rsa-bounce 0.3s var(--rs-ease-spring) both;
  will-change: transform;
}


/* ── 19. Icon-spin modifier ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* Add `class="rs-icon is-spinning"` (or `class="is-spinning"` on a
   parent that contains `.rs-icon`s — covered below) and the icon
   rotates continuously at 1s/rev. Useful for the icon-refresh button
   while a reload is in flight. Pure transform. transform-origin is
   center so the icon pivots in place. ━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.rs-icon.is-spinning,
.is-spinning.rs-icon {
  animation: rsa-icon-spin 1s linear infinite;
  transform-origin: 50% 50%;
  will-change: transform;
}


/* ── 20. Fade-in-up utility ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* Generic entrance utility for any element. Add `class="fade-in-up"`
   and the element fades in + slides up 12px on page load. Pure
   transform + opacity. `both` holds the invisible initial state. */
.fade-in-up {
  animation: rsa-fade-in-up 0.5s var(--rs-ease-out) both;
  will-change: transform, opacity;
}


/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   Keyframes — all `rsa-` prefixed (animations.css A5)
   Every keyframe uses transform + opacity only (Surma 2017 60 FPS rule).
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */

/* #1 Button ripple is transition-based (no keyframe needed). */

/* #3 Progress leading-edge glow — opacity pulse */
@keyframes rsa-edge-glow {
  0%   { opacity: 0.45; }
  100% { opacity: 0.9; }
}

/* #4 Stat-card value count-up — scale 0.92 → 1.0 with spring overshoot */
@keyframes rsa-count-in {
  0%   { opacity: 0; transform: scale(0.92); }
  60%  { opacity: 1; transform: scale(1.04); }
  100% { opacity: 1; transform: scale(1); }
}

/* #5 Slide-enter — softer curve, more displacement than A1's rs-slide-in */
@keyframes rsa-slide-enter {
  0%   { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* #7 Quiz correct pulse — 1.0 → 1.02 → 1.0 */
@keyframes rsa-correct-pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.02); }
  100% { transform: scale(1); }
}

/* #8 Mira idle double pulse — two subtle scale bumps every 30s */
@keyframes rsa-mira-idle {
  0%, 1.5%, 49%, 50.5%, 100% { transform: scale(1); }
  1%   { transform: scale(1.04); }
  50%  { transform: scale(1.04); }
}

/* #9 Notification settle bounce — 1.0 → 0.98 → 1.0 */
@keyframes rsa-notif-settle {
  0%   { transform: scale(1); }
  50%  { transform: scale(0.98); }
  100% { transform: scale(1); }
}

/* #10 Achievement veil orbit — rotate 360deg */
@keyframes rsa-orbit {
  0%   { transform: translate(-50%, -50%) rotate(0deg); }
  100% { transform: translate(-50%, -50%) rotate(360deg); }
}
/* Companion fade-in for the orbit dots (one-shot) */
@keyframes rsa-orbit-fade {
  0%   { opacity: 0; }
  100% { opacity: 0.85; }
}

/* #11 Confetti wobble — opacity flicker (alternate) */
@keyframes rsa-confetti-wobble {
  0%   { opacity: 0.35; transform: scale(1); }
  100% { opacity: 1;    transform: scale(0.85); }
}

/* #12 Module chip shimmer — sweep every 8s (idle 70% of the time) */
@keyframes rsa-module-shimmer {
  0%, 70%   { transform: translateX(-100%); }
  85%, 100% { transform: translateX(100%); }
}

/* #13 Spinner soft pulse — opacity 1 → 0.55 → 1 */
@keyframes rsa-spinner-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* #14, #15, #20 Fade-in-up — opacity 0 → 1, translateY 12px → 0 */
@keyframes rsa-fade-in-up {
  0%   { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* #18 Sound toggle bounce — scale 0.92 → 1.04 → 1.0 */
@keyframes rsa-bounce {
  0%   { transform: scale(0.92); }
  50%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/* #19 Icon spin — rotate 360deg */
@keyframes rsa-icon-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}


/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   End of animations.css — A5 deliverable.
   Total: 20 micro-interactions + 13 rsa- prefixed keyframes.
   Guardrails honored: Surma 60FPS transform/opacity only, prefers-
   reduced-motion respected, Schultz anticipation cues, Dixon LDW (no
   win cues on non-wins), Hattie feed-Back cue, Berney/Betrancourt
   "animation only when change is intrinsic".
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
