/* Project X — shared motion layer.
   Tokens + patterns from MOTION_SPEC.md. Include this BEFORE any page-specific css.
   ADDITIVE ONLY: nothing in here changes layout, color, or size at rest.
   Transform + opacity only — never width/height/top/left/margin/padding. */

:root{
  --px-t-instant: 90ms;
  --px-t-quick:   180ms;
  --px-t-base:    260ms;
  --px-t-slow:    420ms;
  --px-t-hero:    620ms;

  --px-e-out:    cubic-bezier(.22,1,.36,1);
  --px-e-in:     cubic-bezier(.55,0,1,.45);
  --px-e-spring: cubic-bezier(.34,1.56,.64,1);
  --px-e-soft:   cubic-bezier(.4,0,.2,1);

  --px-d-hair:  4px;
  --px-d-near: 10px;
  --px-d-far:  24px;
}

/* ---- press: the single biggest "premium" tell. opt in with .px-press ---- */
.px-press{
  transition: transform var(--px-t-quick) var(--px-e-spring);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.px-press:active{
  transform: scale(.97);
  transition: transform var(--px-t-instant) var(--px-e-out);
}

/* ---- entrance: stagger via --i (capped at 8 in JS, see spec) ---- */
@keyframes px-rise{
  from{ opacity:0; transform: translate3d(0,var(--px-d-near),0); }
  to  { opacity:1; transform: translate3d(0,0,0); }
}
.px-rise{
  animation: px-rise var(--px-t-base) var(--px-e-out) both;
  animation-delay: calc(var(--i, 0) * 40ms);
}

@keyframes px-fade{ from{opacity:0} to{opacity:1} }
.px-fade{ animation: px-fade var(--px-t-base) var(--px-e-out) both; }

/* ---- sheets / modals ---- */
@keyframes px-sheet-in{
  from{ opacity:0; transform: translate3d(0,var(--px-d-far),0); }
  to  { opacity:1; transform: translate3d(0,0,0); }
}
@keyframes px-sheet-out{
  from{ opacity:1; transform: translate3d(0,0,0); }
  to  { opacity:0; transform: translate3d(0,var(--px-d-near),0); }
}
.px-sheet-in { animation: px-sheet-in  var(--px-t-base)  var(--px-e-out) both; }
.px-sheet-out{ animation: px-sheet-out var(--px-t-quick) var(--px-e-in)  both; }
.px-backdrop { transition: opacity var(--px-t-quick) var(--px-e-soft); }

/* ---- page transition. runs constantly, keep it cheap ---- */
@keyframes px-page-in{
  from{ opacity:0; transform: translate3d(0,6px,0); }
  to  { opacity:1; transform: translate3d(0,0,0); }
}
.px-page-in{ animation: px-page-in var(--px-t-base) var(--px-e-out) both; }

/* ---- reward. the ONLY place spring belongs ---- */
@keyframes px-pop{
  0%  { transform: scale(1); }
  18% { transform: scale(.94); }
  55% { transform: scale(1.06); }
  100%{ transform: scale(1); }
}
.px-pop{ animation: px-pop var(--px-t-hero) var(--px-e-spring) both; }

/* ---- counting numbers: digits must not jitter width ---- */
.px-count{ font-variant-numeric: tabular-nums; }

/* ---- skeleton. must match the real box exactly so nothing shifts on swap ---- */
@keyframes px-shimmer{ from{background-position:-160% 0} to{background-position:260% 0} }
.px-skeleton{
  background-image: linear-gradient(90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.06) 50%,
    rgba(255,255,255,0) 100%);
  background-size: 200% 100%;
  animation: px-shimmer 1.4s linear infinite;
}

/* ---- error: colour only. NO shake — users are 13-18, shake reads as punishment ---- */
.px-error{ transition: border-color var(--px-t-quick) var(--px-e-soft),
                       color        var(--px-t-quick) var(--px-e-soft); }

/* ══════════════════════════════════════════════════════════════════════════
   CROSS-DOCUMENT VIEW TRANSITIONS — "one app", not 26 separate pages
   --------------------------------------------------------------------------
   THE PROBLEM this solves (founder, 2026-08-06): "sometimes you click or switch
   between modes it's still kind of glitchy... I wanted it to feel like it flows
   like water." Every tab tap in this app is a REAL document navigation. The
   browser tears the old screen down, re-parses, and rebuilds — and in between
   there is a frame where nothing exists to animate. styles.css already fakes a
   transition around that gap (.px-enter in, html.px-leaving out) but a fake
   cannot cover the teardown itself, so the seam still shows.

   @view-transition opts a multi-page app into the browser's NATIVE cross-document
   transition: the outgoing page's last painted frame is held on screen while the
   next document loads and reaches its first paint, then the two are cross-faded.
   The empty frame, the flash, and the chrome pop stop existing — they are covered
   by a real snapshot instead of papered over by an animation.

   WHY THIS IS SAFE / ADDITIVE:
   - Unsupported browsers ignore every rule here and keep today's behaviour byte
     for byte. Nothing to feature-detect, nothing to branch on.
   - It never delays navigation. The browser starts loading immediately; the
     transition plays inside latency that already existed.
   - Nothing moves at rest. There is no layout, colour, spacing or copy change.

   Support: Safari/iOS 18.2+, Chrome 126+. The Capacitor WKWebView is Safari, so
   the shipped iOS app gets this; everyone else silently keeps the old path.
   ══════════════════════════════════════════════════════════════════════════ */

@view-transition { navigation: auto; }

/* --- 1. Lift the persistent chrome OUT of the page snapshot ----------------
   Without this, the topbar and bottom nav are part of the full-page image that
   cross-fades — so the one thing that should feel nailed down is the thing that
   dissolves. Naming them gives each its own snapshot pair. Their box is byte
   identical on every page, so the group morph is a no-op and the bar visually
   NEVER MOVES across a navigation. Only the active-tab highlight changes, and
   it cross-fades in place.

   Deliberately only TWO names. Naming more elements makes the compositor morph
   half the DOM every tap, which is exactly the jank we are removing. If one side
   of a navigation lacks the element (tracker/drill-runner/onboard ship without
   chrome) the snapshot simply fades — graceful, never a stuck ghost. */
.topbar    { view-transition-name: px-topbar; }
.bottomnav { view-transition-name: px-nav; }

/* --- 2. Timing -------------------------------------------------------------
   This runs on EVERY tap, so fast beats pretty: a slow page transition puts a
   delay between the tap and the destination and reads as lag, not polish. The
   browser's default is 250ms ease; we pin it to the app's own tokens so a
   navigation feels like the same material as everything else in the app.

   The UA's own cross-fade keyframes are kept on purpose. They blend the two
   snapshots with mix-blend-mode: plus-lighter, which is the only way a cross-fade
   neither dips to the background colour mid-way nor double-exposes. Re-authoring
   them with a slide would re-introduce both artefacts. Duration and easing are
   the only things worth changing here. */
::view-transition-group(root),
::view-transition-old(root),
::view-transition-new(root){
  animation-duration: var(--px-t-base);
  animation-timing-function: var(--px-e-soft);
}
/* Chrome settles faster than content — it is the fixed point of the whole move. */
::view-transition-group(px-topbar),
::view-transition-old(px-topbar),
::view-transition-new(px-topbar),
::view-transition-group(px-nav),
::view-transition-old(px-nav),
::view-transition-new(px-nav){
  animation-duration: var(--px-t-quick);
  animation-timing-function: var(--px-e-soft);
}

/* --- 3. Motion never blocks input (MOTION_SPEC §2 rule 3) ------------------
   The transition overlay sits in the top layer. Letting taps through means the
   athlete can hit the next tab mid-transition instead of waiting out 260ms. The
   nav is in the identical position on both screens, so a tap always lands where
   it looks like it should. */
::view-transition{ pointer-events: none; }

/* --- 4. Exactly ONE animation per navigation -------------------------------
   styles.css simulates the route change with .px-enter (in) and html.px-leaving
   (out). When the browser is running the real thing, the simulation must stand
   down or the two stack: the content fades inside a snapshot that is itself
   fading, which is mushy, and the outgoing half can be captured mid-fade — a
   half-transparent, 2%-shrunk screen frozen into the snapshot. That artefact is
   very likely part of what reads as "glitchy" today.

   The outgoing half is disabled in shell.js (it is stamped from JS on tap, before
   any transition exists to detect). This handles the incoming half. Every
   selector here uses :active-view-transition, so on a browser without it the
   whole rule is dropped and styles.css keeps full control — which is correct,
   because that browser is not running a view transition either. */
html:active-view-transition .px-enter,
html:active-view-transition.px-shell .container,
html:active-view-transition .px-page-in,
html:active-view-transition .px-rise{
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}

/* ---- in-page mode switching: .px-swap ------------------------------------
   The other half of "switching between modes feels glitchy". Several pages swap
   whole screens inside ONE document by toggling a class — tracker.html's
   #screen-intro / #screen-live / #screen-summary via `.on`, and the same shape
   elsewhere. Those are hard cuts: one screen vanishes on the frame the next
   appears, which is a bigger visual discontinuity than an actual page load.

   This is the shared vocabulary for that swap. Opt in by adding .px-swap-in to
   the screen being shown (and optionally .px-swap-out to the one being hidden
   for one --px-t-quick before it is removed), or call PXMotion.swap() from
   shell.js which sequences both. Same tokens as everything else, so an in-page
   mode change and a real navigation feel like the same gesture.
   See MOTION_RESEARCH.md for the per-page adoption list. */
@keyframes px-swap-in{
  from{ opacity:0; transform: translate3d(0,var(--px-d-near),0); }
  to  { opacity:1; transform: translate3d(0,0,0); }
}
@keyframes px-swap-out{
  from{ opacity:1; transform: translate3d(0,0,0); }
  to  { opacity:0; transform: translate3d(0,calc(var(--px-d-hair) * -1),0); }
}
.px-swap-in { animation: px-swap-in  var(--px-t-base)  var(--px-e-out) both; }
/* The outgoing screen is on its way out — it must not eat the tap that replaced it. */
.px-swap-out{ animation: px-swap-out var(--px-t-quick) var(--px-e-in) both; pointer-events: none; }

/* ---- accessibility: every flow stays completable with zero motion ---- */
@media (prefers-reduced-motion: reduce){
  .px-rise, .px-fade, .px-sheet-in, .px-sheet-out,
  .px-page-in, .px-pop, .px-skeleton,
  .px-swap-in, .px-swap-out{
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .px-press{ transition: none !important; }
  .px-press:active{ transform: none !important; }

  /* Navigation goes back to a plain instant load. Two belts: switch the opt-in
     off, and zero out the pseudo-element animations for any engine that resolves
     @view-transition before the media query. */
  @view-transition { navigation: none; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*){ animation: none !important; }
}
