/* fx.css · what fx.js needs on screen
   =========================================================================
   Loaded after brand.css and depth.css, alongside play.css where a page
   already has it:

     <link rel="stylesheet" href="brand.css">
     <link rel="stylesheet" href="depth.css">
     <link rel="stylesheet" href="fx.css">
     ... and, at the end of the body, <script src="fx.js"></script>

   Every selector here is new. Checked against brand.css, depth.css and
   play.css first: none of .fx-ripple, .fx-pop, .fx-shake, .fx-progress,
   .fx-fill, .fx-count, .fx-tap or .fx-press exist anywhere else in the lab,
   so nothing here inherits a rule it did not write and nothing already on a
   page inherits one of these by accident.

   Same two rules as depth.css and play.css: no gradients, flat color only,
   drawn from the same variables brand.css already defines (--red, --burg,
   --beige, --tint, --g800, --g600, --green). Nothing here may hide content:
   .fx-ripple is the only element this file ever creates, it is decoration
   fx.js owns start to finish, and it is always removed outright, never left
   behind at opacity 0.
   ========================================================================= */

/* ---- the press ripple -----------------------------------------------------
   A small circle fixed to the viewport at the point of contact, the same
   pattern play.css uses for its score pop: living on the body means it never
   depends on the element under it having a positioning context, and it never
   gets clipped by an overflow:hidden ancestor. Pointer events pass through
   it, so it can never sit in the way of the next click. */
.fx-ripple{
  position:fixed;top:0;left:0;z-index:9999;pointer-events:none;
  width:16px;height:16px;border-radius:50%;background:rgba(230,0,0,.4);
  transform:translate(-50%,-50%) scale(.2);opacity:.4;
  animation:fx-ripple-anim .55s ease-out forwards}
@keyframes fx-ripple-anim{
  0%{transform:translate(-50%,-50%) scale(.2);opacity:.4}
  100%{transform:translate(-50%,-50%) scale(3.2);opacity:0}}

/* ---- the pop ---------------------------------------------------------------
   A state change confirming itself: a number that just changed, a card that
   just got picked. The keyframe returns to scale(1) on its own, so the class
   leaves nothing behind whether fx.js removes it a moment later or not. */
.fx-pop{animation:fx-pop-anim .34s cubic-bezier(.34,1.4,.4,1)}
@keyframes fx-pop-anim{0%{transform:scale(1)}52%{transform:scale(1.14)}100%{transform:scale(1)}}

/* ---- the shake ---------------------------------------------------------- */
/* A wrong answer moves the element, briefly and a small distance, the same
   feel as play.css's own shake at a fifth of the travel: this sits next to a
   paragraph somebody is reading, not a board somebody is playing on. */
.fx-shake{animation:fx-shake-anim .36s ease-in-out}
@keyframes fx-shake-anim{
  0%,100%{transform:translateX(0)}
  20%{transform:translateX(-5px)}45%{transform:translateX(4px)}
  70%{transform:translateX(-3px)}88%{transform:translateX(2px)}}

/* ---- the progress fill --------------------------------------------------- */
/* The track is plain markup, any element. `.fx-fill` is the bar inside it and
   is the element fx.js transforms; it never gets a width set on it, only a
   transform, so filling it never triggers layout. It ships with the correct
   final transform already inline, so a page with fx.js turned off, or a
   learner on reduced motion, sees the right amount filled from first paint. */
.fx-progress{height:8px;border-radius:99px;background:var(--tint,#FFF3F3);
  border:1px solid #F3DADA;overflow:hidden}
.fx-progress.deep{background:rgba(255,255,255,.1);border-color:var(--burg-line,rgba(255,255,255,.13))}
.fx-fill{display:block;height:100%;width:100%;transform-origin:0 50%;
  background:var(--red,#E60000);
  transition:transform .6s cubic-bezier(.2,.8,.2,1)}
.fx-fill.ok{background:var(--green,#1E7D44)}

/* ---- the tap wire --------------------------------------------------------
   `.fx-tap` marks an element fx.js wires for a click-triggered ripple, and
   an optional tick when it also carries data-fx-sound="tick". It draws
   nothing on its own; the element keeps whatever `.btn`, `.tap` or other
   class already styles it. */
.fx-tap{cursor:pointer}
.fx-tap:focus-visible{outline:3px solid var(--red,#E60000);outline-offset:3px;border-radius:6px}
.deep .fx-tap:focus-visible{outline-color:#fff}

/* ---- the press state -------------------------------------------------------
   A general purpose squash for anything that should feel pressed under a
   pointer, independent of ripple or sound. Add it to a button alongside
   whatever else already styles it. */
.fx-press{transition:transform .12s cubic-bezier(.3,.7,.4,1)}
.fx-press:active:not(:disabled){transform:scale(.965)}

/* ---- reduced motion ------------------------------------------------------
   fx.js already gates every helper on prefers-reduced-motion before it ever
   adds one of these classes or starts a transition, so in ordinary use this
   block never has anything to do. It stays here as the same defense in
   depth depth.css and play.css both keep in their own files, in case a page
   ever calls into this stylesheet without the script behind it. */
@media (prefers-reduced-motion: reduce){
  .fx-ripple,.fx-pop,.fx-shake{animation:none}
  .fx-fill{transition:none}
  .fx-press:active{transform:none}
}

/* ==========================================================================
   PHONE
   --------------------------------------------------------------------------
   At the bottom on purpose. A media query adds no specificity, so a phone
   rule written above the base rule it overrides loses silently and still
   looks correct on read.
   ========================================================================== */
@media(max-width:560px){
  .fx-ripple{width:13px;height:13px}
  .fx-progress{height:7px}
}
