/* feel.css — the shared "game feel" layer for Mini Games Sayangku.

   Every class here is decoration. A game that loads this file and adds none of
   its classes looks exactly as it did before: there are no element selectors,
   no resets, and nothing outside the `feel-` namespace, so this file cannot
   collide with a game's own stylesheet.

   Three rules this file keeps:

   1. Nothing here moves under `prefers-reduced-motion: reduce`. The block at
      the bottom turns every animation and transition off, and confetti is not
      merely stilled but removed from the layout entirely. A stilled state is
      still the *final* state, never a half-played frame: each animation uses
      `both` fill so its end frame is what you see when the animation is off.
   2. Nothing here catches a pointer. Every decorative overlay is
      `pointer-events:none`, so an effect can never swallow a tap meant for a
      button underneath it.
   3. Nothing here is announced. Decorative nodes are created with
      `aria-hidden="true"` by feel.js; the styles just keep them out of the
      way.

   Colours come from custom properties with neutral fallbacks, so a game can
   re-tint the whole layer by setting `--feel-a/--feel-b/--feel-c` on :root
   without touching this file. */

:root {
  /* Confetti palette. Fallbacks are the house colours shared by the existing
     games (ink blue, marigold, terracotta). */
  --feel-a: #204ce0;
  --feel-b: #f5ba25;
  --feel-c: #ed4b22;
  /* Focus ring colour and the ink used by press/pulse shadows. */
  --feel-ring: #204ce0;
  --feel-ink: #242331;
  /* One knob for the whole layer's tempo. Halving this halves every duration
     below, because each duration is expressed as a multiple of it. */
  --feel-tempo: 1;
  /* Confetti sits above game UI but below modal dialogs a game may own. */
  --feel-z: 60;
}

/* ---------------------------------------------------------------------------
   1. Press, focus, and hover — the microinteractions that apply to controls.
   These are opt-in per element (`class="feel-press"`) rather than a blanket
   `button` selector, so a game keeps whatever active state it already has
   unless it asks for this one.
--------------------------------------------------------------------------- */

.feel-press {
  transition: transform calc(90ms * var(--feel-tempo)) ease-out,
              box-shadow calc(90ms * var(--feel-tempo)) ease-out,
              filter calc(90ms * var(--feel-tempo)) ease-out;
}
.feel-press:hover:not(:disabled):not([aria-disabled="true"]) {
  filter: brightness(1.04);
}
/* The press itself: a small, immediate dip. 1px of travel reads as "the
   control took the tap" without moving neighbouring layout. */
.feel-press:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(1px) scale(0.985);
  filter: brightness(0.97);
}

/* A focus ring that is visible on any background: an inner light halo and an
   outer dark one, so it survives both a white card and a dark panel. It is
   drawn with `outline` (not `box-shadow`) so it follows border-radius and is
   never clipped by an ancestor's `overflow:hidden`. */
.feel-focus:focus-visible,
.feel-press:focus-visible {
  outline: 3px solid var(--feel-ring);
  outline-offset: 2px;
  border-radius: inherit;
}
/* Mouse users do not get the ring, but keyboard users always do — including in
   browsers where `:focus-visible` is missing, which fall through to :focus. */
.feel-focus:focus:not(:focus-visible),
.feel-press:focus:not(:focus-visible) {
  outline: none;
}

/* ---------------------------------------------------------------------------
   2. State animations. feel.js adds each class, then removes it on a timer
   that matches the duration below, so a class is never left stuck on an
   element if `animationend` does not fire.
--------------------------------------------------------------------------- */

/* "Pop" — something just appeared and should be noticed. Overshoots slightly
   and settles. */
.feel-pop {
  animation: feel-pop calc(280ms * var(--feel-tempo)) cubic-bezier(0.2, 1.3, 0.4, 1) both;
}
@keyframes feel-pop {
  from { opacity: 0; transform: scale(0.82); }
  60%  { opacity: 1; transform: scale(1.04); }
  to   { opacity: 1; transform: scale(1); }
}

/* "Shake" — a rejection. Short, horizontal, decaying, and it never changes
   the element's size, so a shaking input does not reflow the form around it. */
.feel-shake {
  animation: feel-shake calc(360ms * var(--feel-tempo)) cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}
@keyframes feel-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
  to { transform: translateX(0); }
}

/* "Pulse" — it is your turn. This one repeats, so unlike the others it is a
   state a game turns on and off rather than a one-shot. It animates only
   `box-shadow` and `transform`, both compositor-friendly, so an indefinitely
   pulsing element costs nothing per frame beyond its own layer. */
.feel-pulse {
  animation: feel-pulse calc(1600ms * var(--feel-tempo)) ease-in-out infinite;
}
@keyframes feel-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--feel-ring) 45%, transparent); }
  50%      { transform: scale(1.02); box-shadow: 0 0 0 8px color-mix(in srgb, var(--feel-ring) 0%, transparent); }
}
/* `color-mix` is recent. Where it is unsupported the rule above is dropped by
   the parser, so this plain fallback (declared first in source order but
   overridden when color-mix parses) keeps the pulse visible. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  @keyframes feel-pulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50%      { transform: scale(1.02); opacity: 0.82; }
  }
}

/* "Flash" — a cheap positive acknowledgement for a cell or tile that does not
   warrant confetti. */
.feel-flash {
  animation: feel-flash calc(520ms * var(--feel-tempo)) ease-out both;
}
@keyframes feel-flash {
  from { filter: brightness(1.6) saturate(1.3); }
  to   { filter: none; }
}

/* ---------------------------------------------------------------------------
   3. Screen / card transitions. `Feel.transition(from, to)` adds these for the
   length of the animation and then sets `hidden` on the outgoing element.
--------------------------------------------------------------------------- */

.feel-enter {
  animation: feel-enter calc(320ms * var(--feel-tempo)) cubic-bezier(0.16, 0.84, 0.44, 1) both;
}
@keyframes feel-enter {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

.feel-leave {
  animation: feel-leave calc(240ms * var(--feel-tempo)) ease-in both;
  /* The outgoing screen must not take taps while it fades. */
  pointer-events: none;
}
@keyframes feel-leave {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-10px); }
}

/* ---------------------------------------------------------------------------
   4. Count-up. The number itself is written by JS; this only keeps the digits
   from reflowing their neighbours as the width changes, which is what makes a
   fast count look unstable.
--------------------------------------------------------------------------- */

.feel-counting {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   5. Confetti. No canvas: `Feel.burst()` appends one container of <i> chips,
   each carrying its own trajectory in custom properties, and removes the
   container when the longest chip has finished.

   `position:fixed` means the burst is placed in viewport coordinates and is
   therefore unaffected by any ancestor's overflow, transform or scroll
   position — a burst over a cell inside a scrolling board lands where the cell
   actually is on screen.
--------------------------------------------------------------------------- */

.feel-confetti {
  position: fixed;
  left: var(--feel-x, 50%);
  top: var(--feel-y, 50%);
  width: 0;
  height: 0;
  z-index: var(--feel-z, 60);
  /* Decoration must never intercept a tap meant for the control underneath. */
  pointer-events: none;
  /* Keep the chips' paint and layout out of the rest of the page's work. */
  contain: layout style;
}

.feel-confetti > i {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: var(--feel-w, 8px);
  height: var(--feel-h, 12px);
  background: var(--feel-fill, var(--feel-a));
  border-radius: var(--feel-round, 1px);
  will-change: transform, opacity;
  animation: feel-confetti-fly var(--feel-dur, 900ms) cubic-bezier(0.12, 0.62, 0.36, 1)
             var(--feel-delay, 0ms) both;
}

/* Up and out, then down: the arc is what separates confetti from a starburst.
   `--feel-dx` is the sideways drift, `--feel-up` the height of the arc, and
   `--feel-fall` how far below the origin the chip ends. */
@keyframes feel-confetti-fly {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.5) rotate(0deg);
  }
  8% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(calc(var(--feel-rot, 360deg) * 0.08));
  }
  45% {
    opacity: 1;
    transform: translate(calc(-50% + var(--feel-dx, 0px) * 0.55),
                         calc(-50% - var(--feel-up, 60px)))
               scale(1) rotate(calc(var(--feel-rot, 360deg) * 0.45));
  }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--feel-dx, 0px)),
                         calc(-50% + var(--feel-fall, 120px)))
               scale(0.7) rotate(var(--feel-rot, 360deg));
  }
}

/* ---------------------------------------------------------------------------
   6. Reduced motion. Not "smaller motion" — none.

   Every animation and transition in this file is cancelled, and confetti is
   removed from the layout rather than frozen mid-flight (a frozen burst would
   leave chips sitting on the screen forever, since JS removes the container on
   a timer but the chips would still be painted at their start position).

   The press state survives as an instant, non-animated change: `transform` is
   dropped and only `filter` remains, which is a state change, not motion.
--------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .feel-press {
    transition: none;
  }
  .feel-press:active:not(:disabled):not([aria-disabled="true"]) {
    transform: none;
    filter: brightness(0.94);
  }
  .feel-pop,
  .feel-shake,
  .feel-pulse,
  .feel-flash,
  .feel-enter,
  .feel-leave,
  .feel-confetti > i {
    animation: none;
  }
  /* The one class that keeps a visible, static meaning: "it is your turn"
     still needs to be identifiable without movement. */
  .feel-pulse {
    outline: 3px solid var(--feel-ring);
    outline-offset: 2px;
  }
  .feel-confetti {
    display: none;
  }
}
