/* ==========================================================================
   Math Memory — the effects layer's own styling
   The particles are drawn on a canvas (js/fx.js); everything here is the DOM
   half: the banners, the floating scores, the flash and the screen shake.
   ========================================================================== */

.fx-canvas,
.fx-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

/* Particles render BEHIND the app, not over it.
   They used to sit at z-index 900, above everything, and a correct answer
   buried the answer cards under ~90 pieces of confetti at the exact moment
   the child was looking to see which card they had tapped. The celebration
   was covering the thing being celebrated. Below .app-shell (z-index 1) and
   above the backdrop layers (z-index 0, painted earlier in the body), the
   confetti fills the space around the cards and never the cards themselves. */
.fx-canvas { z-index: 0; }

/* The DOM half that carries meaning stays on top: banners and floating
   scores, each small, brief, or deliberately placed clear of the gameplay. */
.fx-layer { z-index: 901; }

/* The half that is only mood sits with the particles, behind the app. */
.fx-layer--back { z-index: 0; }

/* --- the full-screen colour wash on a big moment ------------------------ */
.fx-flash {
  position: absolute;
  inset: 0;
  animation: fxflash .46s ease-out forwards;
}
@keyframes fxflash {
  0%   { opacity: .85; }
  100% { opacity: 0; }
}

/* --- expanding ring, used where something was tapped -------------------- */
.fx-ripple {
  position: absolute;
  width: 24px; height: 24px;
  margin: -12px 0 0 -12px;
  border: 3px solid;
  border-radius: 50%;
  animation: fxripple .78s cubic-bezier(.1, .7, .3, 1) forwards;
}
@keyframes fxripple {
  0%   { transform: scale(.3); opacity: .9; }
  100% { transform: scale(9);  opacity: 0; }
}

/* --- +10, COMBO, and other numbers that fly out of a thing -------------- */
.fx-float {
  position: absolute;
  transform: translate(-50%, -50%);
  font: 800 26px/1 var(--font-ui), system-ui, sans-serif;
  color: var(--gold, #ffd76a);
  text-shadow: 0 3px 0 rgba(0, 0, 0, .3), 0 0 18px rgba(255, 215, 106, .5);
  white-space: nowrap;
  animation: fxfloat 1.15s cubic-bezier(.2, .9, .3, 1) forwards;
}
.fx-float--big  { font-size: 40px; }
.fx-float--good { color: #5cf0ae; text-shadow: 0 3px 0 rgba(0,0,0,.3), 0 0 18px rgba(92,240,174,.55); }
.fx-float--soft { color: #ffd09a; font-size: 20px; }
@keyframes fxfloat {
  0%   { transform: translate(-50%, -50%) scale(.5)  rotate(-6deg); opacity: 0; }
  22%  { transform: translate(-50%, -78%) scale(1.2) rotate(3deg);  opacity: 1; }
  45%  { transform: translate(-50%, -95%) scale(1)   rotate(0);     opacity: 1; }
  100% { transform: translate(-50%, -190%) scale(.92);              opacity: 0; }
}

/* --- the big moment ----------------------------------------------------- */
/* A badge on a scrim, not bare text. The old version was a drop-shadowed word
   dropped over a celebration frame: on a busy background it was hard to pick
   out, and 1.5s was not long enough to read. */

.fx-scrim {
  position: absolute;
  inset: 0;
  background: radial-gradient(56% 38% at 50% 46%,
              rgba(9, 3, 26, .74), rgba(9, 3, 26, .40) 58%, transparent 80%);
  opacity: 0;
  animation: fxscrimin .24s ease-out forwards;
}
.fx-scrim.is-out { animation: fxfadeout .32s ease-in forwards; }
@keyframes fxscrimin { to { opacity: 1; } }
@keyframes fxfadeout { to { opacity: 0; } }

.fx-banner {
  position: absolute;
  left: 50%; top: 46%;
  transform: translate(-50%, -50%);
  display: flex;
  justify-content: center;
  max-width: 92vw;
}
.fx-banner__badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 14px 30px 16px;
  border-radius: 26px;
  background: linear-gradient(160deg, #ff9f3d, #ff5a2b 55%, #ff3d8b);
  border: 4px solid #fffdf6;
  box-shadow: 0 12px 0 rgba(0, 0, 0, .28), 0 22px 50px rgba(0, 0, 0, .55);
  text-align: center;
  animation: fxbadgein .5s cubic-bezier(.2, 1.7, .35, 1) both;
}
.fx-banner.is-out .fx-banner__badge { animation: fxbadgeout .32s ease-in forwards; }

.fx-banner__icon {
  font-size: clamp(28px, 9vw, 40px);
  line-height: 1;
  animation: fxiconpop .6s cubic-bezier(.2, 1.9, .35, 1) both .08s;
}
.fx-banner__text {
  font: 600 clamp(26px, 9vw, 46px)/1 var(--font-display), system-ui, sans-serif;
  color: #fffdf6;
  letter-spacing: .01em;
  white-space: nowrap;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .3);
}
.fx-banner__sub {
  font: 800 clamp(12px, 3.6vw, 15px) var(--font-ui), system-ui, sans-serif;
  color: rgba(255, 255, 255, .92);
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* --- the streak badge, parked out of the way ----------------------------
   Anchored in the band between the top bar and the duck. Measured at 380px
   wide: the bar ends at 78px and the duck starts at 141px on the shortest
   phone the game is built for, so a badge from 82px to ~134px clears both,
   and the question card (303px at its highest) is never in reach. */
.fx-banner--top {
  top: 82px;
  transform: translateX(-50%);
  align-items: flex-start;
}

/* Small enough to read as a nudge rather than an interruption: one row, no
   drop shadow theatre, and the streak count sits beside the words instead of
   under them. */
.fx-banner--mini .fx-banner__badge {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 6px 13px;
  border-radius: 999px;
  border-width: 2px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, .22), 0 10px 22px rgba(0, 0, 0, .4);
}
.fx-banner--mini .fx-banner__icon { font-size: 17px; animation-duration: .45s; }
.fx-banner--mini .fx-banner__text {
  font-size: clamp(14px, 4vw, 17px);
  line-height: 1.1;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .25);
}
.fx-banner--mini .fx-banner__sub {
  font-size: 10px;
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, .35);
}

.fx-banner--good .fx-banner__badge { background: linear-gradient(160deg, #5cf0ae, #12b06c 60%, #0d8073); }
.fx-banner--fire .fx-banner__badge { background: linear-gradient(160deg, #ffd76a, #ff8a2b 45%, #ff3d8b); }
.fx-banner--soft .fx-banner__badge { background: linear-gradient(160deg, #8b8bd6, #5b3ad6); }
.fx-banner--soft .fx-banner__text  { font-size: clamp(20px, 6.5vw, 30px); }

/* Calm mode: the badge still says what happened, without the bounce, the
   rotation or the heavy drop shadow. */
[data-calm] .fx-banner__badge {
  animation: fxfadein .3s ease-out both;
  box-shadow: 0 3px 0 rgba(0, 0, 0, .2), 0 6px 16px rgba(0, 0, 0, .35);
}
[data-calm] .fx-banner__icon { animation: none; }
@keyframes fxfadein { from { opacity: 0; } to { opacity: 1; } }

@keyframes fxbadgein {
  0%   { transform: scale(.3) rotate(-7deg); opacity: 0; }
  55%  { transform: scale(1.12) rotate(2deg); opacity: 1; }
  75%  { transform: scale(.97) rotate(-1deg); }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}
@keyframes fxbadgeout {
  from { transform: scale(1); opacity: 1; }
  to   { transform: scale(.86) translateY(-26px); opacity: 0; }
}
@keyframes fxiconpop {
  0%   { transform: scale(0) rotate(-40deg); }
  60%  { transform: scale(1.25) rotate(8deg); }
  100% { transform: scale(1) rotate(0); }
}

/* --- screen shake ------------------------------------------------------- */
.fx-shake--1 { animation: fxshake1 .34s ease; }
.fx-shake--2 { animation: fxshake2 .42s ease; }
.fx-shake--3 { animation: fxshake3 .52s ease; }
@keyframes fxshake1 {
  0%,100% { transform: translate(0,0); }
  25% { transform: translate(-3px, 2px); } 50% { transform: translate(3px,-2px); }
  75% { transform: translate(-2px, 1px); }
}
@keyframes fxshake2 {
  0%,100% { transform: translate(0,0) rotate(0); }
  20% { transform: translate(-7px, 4px) rotate(-.5deg); }
  45% { transform: translate(7px, -4px) rotate(.5deg); }
  70% { transform: translate(-4px, 2px) rotate(-.2deg); }
}
@keyframes fxshake3 {
  0%,100% { transform: translate(0,0) rotate(0) scale(1); }
  15% { transform: translate(-12px, 7px) rotate(-1deg) scale(1.012); }
  35% { transform: translate(11px, -7px) rotate(1deg)  scale(1.012); }
  55% { transform: translate(-8px, 5px)  rotate(-.6deg); }
  78% { transform: translate(5px, -3px)  rotate(.3deg); }
}

@media (prefers-reduced-motion: reduce) {
  .fx-canvas { display: none; }
  .fx-flash, .fx-ripple { display: none; }
  .fx-shake--1, .fx-shake--2, .fx-shake--3 { animation: none; }
  /* The banner carries meaning, so it still shows — it just arrives without
     the bounce. Its entrance and exit are separate animations for exactly
     this reason: one keyframe ending at opacity 0 would collapse to invisible
     the moment durations are forced to zero. */
  .fx-scrim { opacity: 1; animation: none; }
  .fx-scrim.is-out { opacity: 0; }
  .fx-banner__badge, .fx-banner__icon { animation: none; opacity: 1; transform: none; }
}
