/*
 * Memory Games motion (plan 016): tokens, the loop switch, reduced motion,
 * the hidden-tab pause and the shared Sun pop. Linked after frame.css by the
 * hub and (through build/gameConfig.ts FRAME_LINK) by both games.
 *
 * Contract for every animation in the app:
 *   One-off  keyframes named mg-once-*, on an element with .mg-once (or
 *            inside one), fill-mode both, 2000ms or less with its delay.
 *            Its last keyframe is the rest frame: reduced motion, ?anim=off
 *            and a tap (anim.js skipAll) jump straight there.
 *   Loop     keyframes named mg-loop-*, on or inside .mg-loop. The element's
 *            own (unanimated) style is the rest pose. Loops run only when
 *            <html data-mg-scene> is start, paused, level or end. Anything
 *            else (play, wait, none) stops them: off is the default.
 *   Wait     .mg-loop-wait runs only in data-mg-scene="wait" (worM barn,
 *            Play only). Opacity only: nothing in it may move.
 * Transform and opacity only. No flashing: a loop moves at most once every
 * 2.4 s. Nothing ever goes inside the worM garden grid or the Recall Garden
 * playfield.
 * Created: 2026-10-21 · Plan: docs/plans/016-animations.md
 */
:root {
  --mg-ease-out: cubic-bezier(.22, 1, .36, 1);      /* arrive and settle */
  --mg-ease-pop: cubic-bezier(.34, 1.4, .64, 1);    /* pop up, small overshoot */
  --mg-ease-in: cubic-bezier(.55, 0, 1, .45);       /* duck down */
  --mg-ease-breathe: cubic-bezier(.37, 0, .63, 1);  /* loops */

  --mg-once-sun: 800ms;     /* Sun pop */
  --mg-once-short: 1200ms;  /* hello, wave */
  --mg-once: 1600ms;        /* shirt swap, peek */
  --mg-once-max: 2000ms;    /* harvest; nothing is longer */

  --mg-loop-quick: 2400ms;  /* dig: the fastest loop */
  --mg-loop: 3600ms;        /* worm wriggle */
  --mg-loop-slow: 4800ms;   /* sleep, barn glow */
}

/* ------------------------------------------------------------ loop switch */
/* (Pseudo-elements cannot go inside :is(), so each rule names them after it.) */
/* Off unless the page says this scene may move. A missing or unknown scene is off. */
html:not([data-mg-scene="start"], [data-mg-scene="paused"], [data-mg-scene="level"], [data-mg-scene="end"]) :is(.mg-loop, .mg-loop *),
html:not([data-mg-scene="start"], [data-mg-scene="paused"], [data-mg-scene="level"], [data-mg-scene="end"]) :is(.mg-loop, .mg-loop *)::before,
html:not([data-mg-scene="start"], [data-mg-scene="paused"], [data-mg-scene="level"], [data-mg-scene="end"]) :is(.mg-loop, .mg-loop *)::after,
html:not([data-mg-scene="wait"]) :is(.mg-loop-wait, .mg-loop-wait *) {
  animation: none !important;
}
/* A hidden tab holds its loops where they are (anim.js sets data-mg-hidden). */
html[data-mg-hidden] :is(.mg-loop, .mg-loop *, .mg-loop-wait, .mg-loop-wait *),
html[data-mg-hidden] :is(.mg-loop, .mg-loop *)::before,
html[data-mg-hidden] :is(.mg-loop, .mg-loop *)::after {
  animation-play-state: paused !important;
}

/* ------------------------------------------------------------ reduced motion and ?anim=off */
/* Loops stop at their rest pose; one-offs show their last frame at once. */
html[data-mg-anim="off"] :is(.mg-loop, .mg-loop *, .mg-loop-wait, .mg-loop-wait *),
html[data-mg-anim="off"] :is(.mg-loop, .mg-loop *)::before,
html[data-mg-anim="off"] :is(.mg-loop, .mg-loop *)::after {
  animation: none !important;
}
html[data-mg-anim="off"] :is(.mg-once, .mg-once *),
html[data-mg-anim="off"] :is(.mg-once, .mg-once *, .mg-sun-pop, .mg-bar)::before,
html[data-mg-anim="off"] :is(.mg-once, .mg-once *, .mg-sun-pop, .mg-bar)::after {
  animation-duration: 0s !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
}
@media (prefers-reduced-motion: reduce) {
  :is(.mg-loop, .mg-loop *, .mg-loop-wait, .mg-loop-wait *),
  :is(.mg-loop, .mg-loop *)::before,
  :is(.mg-loop, .mg-loop *)::after {
    animation: none !important;
  }
  :is(.mg-once, .mg-once *),
  :is(.mg-once, .mg-once *, .mg-sun-pop, .mg-bar)::before,
  :is(.mg-once, .mg-once *, .mg-sun-pop, .mg-bar)::after {
    animation-duration: 0s !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
  }
}

/* ------------------------------------------------------------ Sun pop */
/*
 * One warm pulse behind a gold "Level N", then a faint glow stays. Rays turn
 * a little and fade out; they are gone in the last frame. Any level up, never
 * a level down. Automatic behind every gold bar title (.mg-title.is-sun); put
 * .mg-sun-pop on any other host (a hub card's level) to use it there. The
 * host gets its own layer, so the sun sits behind its text. It never takes a
 * tap. The bar does not move: only the pseudo-elements are added.
 */
.mg-sun-pop,
.mg-bar:has(> .mg-title.is-sun) {
  position: relative; isolation: isolate;
  --mg-sun-disc: 64px;
  --mg-sun-rays: 108px;
}
:is(.mg-sun-pop, .mg-bar:has(> .mg-title.is-sun))::before,
:is(.mg-sun-pop, .mg-bar:has(> .mg-title.is-sun))::after {
  content: ""; position: absolute; z-index: -1; left: 50%; top: 50%;
  pointer-events: none;
  animation: var(--mg-once-sun) var(--mg-ease-out) both;
}
/* disc: a soft gold glow, not a hard circle */
:is(.mg-sun-pop, .mg-bar:has(> .mg-title.is-sun))::before {
  width: var(--mg-sun-disc); height: var(--mg-sun-disc);
  margin: calc(var(--mg-sun-disc) / -2) 0 0 calc(var(--mg-sun-disc) / -2);
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--mg-sun) 0 55%, transparent);
  opacity: .16;
  animation-name: mg-once-sun-disc;
}
/* rays: twelve short strokes, coloured by the theme's --mg-sun through a mask */
:is(.mg-sun-pop, .mg-bar:has(> .mg-title.is-sun))::after {
  width: var(--mg-sun-rays); height: var(--mg-sun-rays);
  margin: calc(var(--mg-sun-rays) / -2) 0 0 calc(var(--mg-sun-rays) / -2);
  background: var(--mg-sun);
  -webkit-mask: var(--mg-sun-ray-mask) center / contain no-repeat;
  mask: var(--mg-sun-ray-mask) center / contain no-repeat;
  opacity: 0;
  animation-name: mg-once-sun-rays;
}
:root {
  --mg-sun-ray-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-50 -50 100 100'%3E%3Cg stroke='%23000' stroke-width='5' stroke-linecap='round'%3E%3Cpath d='M0-46v12' transform='rotate(0)'/%3E%3Cpath d='M0-46v12' transform='rotate(30)'/%3E%3Cpath d='M0-46v12' transform='rotate(60)'/%3E%3Cpath d='M0-46v12' transform='rotate(90)'/%3E%3Cpath d='M0-46v12' transform='rotate(120)'/%3E%3Cpath d='M0-46v12' transform='rotate(150)'/%3E%3Cpath d='M0-46v12' transform='rotate(180)'/%3E%3Cpath d='M0-46v12' transform='rotate(210)'/%3E%3Cpath d='M0-46v12' transform='rotate(240)'/%3E%3Cpath d='M0-46v12' transform='rotate(270)'/%3E%3Cpath d='M0-46v12' transform='rotate(300)'/%3E%3Cpath d='M0-46v12' transform='rotate(330)'/%3E%3C/g%3E%3C/svg%3E");
}

@keyframes mg-once-sun-disc {
  0%   { opacity: 0;   transform: scale(.6); }
  40%  { opacity: .38; transform: scale(1.06); }
  100% { opacity: .16; transform: scale(1); }
}
@keyframes mg-once-sun-rays {
  0%   { opacity: 0;   transform: rotate(-15deg) scale(.85); }
  30%  { opacity: .85; }
  100% { opacity: 0;   transform: rotate(0deg) scale(1); }
}
/* light theme: the darker gold needs a little less glow to keep the title crisp */
:root[data-theme="light"] :is(.mg-sun-pop, .mg-bar:has(> .mg-title.is-sun))::before { opacity: .12; animation-name: mg-once-sun-disc-light; }
@keyframes mg-once-sun-disc-light {
  0%   { opacity: 0;   transform: scale(.6); }
  40%  { opacity: .3;  transform: scale(1.06); }
  100% { opacity: .12; transform: scale(1); }
}
