/* ============================================================================
   FROOTCITY — VFX PRIMITIVES (the CSS half of src/core/vfx.js)
   Owned by WP-VFX. docs/vfx-master-spec.md §3.4.
   Tokens only: every duration is a --fc-m-* token, every easing a --fc-ease*
   token, every colour a semantic token. Motion is transform/opacity only; the
   two layout writes (sheet snap height, morph hand-over) happen once, at rest.
   ========================================================================= */

/* ------------------------------------------------------------- LAYERS */
/* Overlay canvases for confetti / sparkle / coins. Fixed at the anchor rect,
   never in the way of a finger, never in the accessibility tree. */
.vfx-layer {
  position: fixed;
  left: 0;
  top: 0;
  pointer-events: none;
  z-index: var(--fc-z-top);
}

/* ---------------------------------------------------------------- RING */
/* One expanding ring (once) or a slow breathing ring (live: "is it alive?"). */
.vfx-ring {
  position: fixed;
  display: block;
  pointer-events: none;
  border-radius: var(--fc-r-pill);
  border: 2px solid var(--vfx-ring, var(--accent));
  z-index: var(--fc-z-top);
  opacity: 0;
  will-change: transform, opacity;
}
.vfx-ring--live {
  animation: vfx-ring var(--fc-m-epic) var(--fc-ease) infinite;
}
@keyframes vfx-ring {
  0%   { transform: scale(.92); opacity: .7; }
  70%  { transform: scale(1.22); opacity: 0; }
  100% { transform: scale(1.22); opacity: 0; }
}

/* ------------------------------------------------------------ ETA PILL */
.vfx-eta {
  display: inline-flex;
  align-items: center;
  gap: var(--fc-s-1);
  padding: 0 var(--fc-s-3);
  min-height: var(--fc-s-7);
  border-radius: var(--fc-r-pill);
  background: var(--on-glass-chip);
  color: var(--txt-1);
  font: var(--fc-w-semi) var(--fc-t-sm)/1 var(--fc-font-sans);
  white-space: nowrap;
}
.vfx-eta-dot {
  width: var(--fc-s-2);
  height: var(--fc-s-2);
  border-radius: var(--fc-r-pill);
  background: var(--fc-success);
  animation: vfx-eta-pulse var(--fc-m-epic) var(--fc-ease) infinite;
}
.vfx-eta[data-band="warn"] .vfx-eta-dot { background: var(--fc-warn); }
.vfx-eta[data-band="late"] .vfx-eta-dot { background: var(--fc-danger); animation: none; }
.vfx-eta[data-band="late"] { color: var(--fc-danger); }
.vfx-eta-n { font-weight: var(--fc-w-bold); font-variant-numeric: tabular-nums; }
.vfx-eta-u { color: var(--txt-2); font-weight: var(--fc-w-medium); }
/* Opacity only: the dot is an ambient loop and must never report a non-identity
   transform to the geometry probes. */
@keyframes vfx-eta-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .4; }
}

/* ---------------------------------------------------------- RIDER PATH */
.vfx-path { display: block; width: 100%; height: 100%; overflow: visible; }
.vfx-path-trail { fill: none; stroke: var(--line); stroke-width: 3; stroke-dasharray: 4 6; stroke-linecap: round; }
.vfx-path-done  { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.vfx-rider circle { fill: var(--accent); stroke: var(--bg-base); stroke-width: 3; }

/* ---------------------------------------------------------------- CHECK */
.vfx-check { display: block; overflow: visible; }
.vfx-check-ring { fill: none; stroke: currentColor; stroke-width: 3; }
.vfx-check-tick { fill: none; stroke: currentColor; stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; }

/* ---------------------------------------------------------- SHEET DRAG */
.vfx-drag-handle { touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
.vfx-dragging .vfx-drag-handle { cursor: grabbing; }
/* The one layout write: height set at release, then the sheet is left alone. */
.sheet.vfx-snapped {
  height: calc(var(--vfx-snap, .5) * 100vh);
  height: calc(var(--vfx-snap, .5) * 100dvh);
  max-height: 92dvh;
}
/* While a finger holds the sheet above its resting height there is a gap under
   it. The apron that fills it is a SIBLING of the sheet (sheetDrag inserts it
   at drag start and removes it at release), not a pseudo-element: inside the
   sheet's backdrop root a ::after could only blur the sheet's own paint, so its
   band never matched the glass above it. As a sibling it shares the sheet's
   containing block, fill, stroke, blur and shadow, and rides on the same
   transform, so the drag reads as the sheet growing. */
.vfx-apron {
  position: fixed;
  left: 50%;
  top: 100%;
  width: 100%;
  max-width: var(--fc-shell-max);
  height: 100dvh;
  z-index: var(--fc-z-sheet);
  background: var(--glass-fill-hard);
  border: 1px solid var(--glass-stroke);
  border-top: 0;
  border-bottom: 0;
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  backdrop-filter: blur(28px) saturate(180%);
  box-shadow: var(--glass-shadow-lg);
  transform: translateX(-50%);
  pointer-events: none;
}
body.framed .vfx-apron { position: absolute; }
.sheet.vfx-dragging { animation: none; }

/* ------------------------------------------------------ PULL TO REFRESH */
.vfx-ptr-host { position: relative; }
.vfx-ptr-scroller { overscroll-behavior-y: contain; }
.vfx-ptr {
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--fc-s-8);
  height: var(--fc-s-8);
  margin-left: calc(-1 * var(--fc-s-5));
  margin-top: calc(-1 * var(--fc-s-8));
  display: grid;
  place-items: center;
  border-radius: var(--fc-r-pill);
  background: var(--glass-fill-hard);
  box-shadow: var(--glass-shadow);
  color: var(--accent);
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  will-change: transform, opacity;
}
.vfx-ptr > img, .vfx-ptr > svg, .vfx-ptr > canvas { width: var(--fc-s-6); height: var(--fc-s-6); object-fit: contain; }
.vfx-ptr.is-armed { color: var(--fc-success); }
.vfx-ptr.is-busy > * { animation: vfx-spin var(--fc-m-epic) linear infinite; }
@keyframes vfx-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------- STEP MORPH */
/* ADD ⇄ "− 1 +". The stepper sits over the button while it clips in from the
   right; once the button is hidden it takes the button's place in flow. */
.vfx-morph { position: relative; display: inline-block; vertical-align: top; }
.vfx-morph > .vfx-morph-b { position: absolute; right: 0; top: 0; }
.vfx-morph.is-stepper > .vfx-morph-b { position: static; }
/* Author `button { display }` rules outrank the UA [hidden] rule; the morph
   relies on `hidden` being final. */
.vfx-morph > [hidden] { display: none !important; }

/* ----------------------------------------------------------------- ROLL */
.vfx-roll { display: inline-grid; overflow: hidden; vertical-align: bottom; }
.vfx-roll > .vfx-roll-t { grid-area: 1 / 1; display: block; will-change: transform, opacity; }

/* ------------------------------------------------------------- CAROUSEL */
.vfx-carousel { position: relative; }
.vfx-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.vfx-track::-webkit-scrollbar { display: none; }
.vfx-slide { flex: 0 0 100%; width: 100%; scroll-snap-align: start; scroll-snap-stop: always; position: relative; overflow: hidden; }
.vfx-slide > .vfx-parallax { transform: translateX(calc(var(--vfx-px, 0) * -24px)); will-change: transform; }
.vfx-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--fc-s-2);
  display: flex;
  justify-content: center;
  gap: var(--fc-s-3);
  pointer-events: none;
}
.vfx-dots i {
  position: relative;
  display: block;
  width: var(--fc-s-2);
  height: var(--fc-s-1);
  border-radius: var(--fc-r-pill);
  background: var(--glass-stroke);
  overflow: hidden;
  transition: transform var(--fc-m-quick) var(--fc-ease-out), opacity var(--fc-m-quick) var(--fc-ease);
  opacity: .7;
}
.vfx-dots i.is-active { transform: scaleX(2.5); opacity: 1; }
.vfx-dots i b {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--accent);
  transform-origin: left center;
  transform: scaleX(0);
}

/* ----------------------------------------------------------------- HINT */
.vfx-hint-host { position: relative; }
.vfx-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  pointer-events: none;
  color: var(--txt-3);
  white-space: nowrap;
}
.vfx-hint > span { display: block; will-change: transform, opacity; }

/* ---------------------------------------------------------------- STAMP */
/* Ops verdicts: GRADE A, PAID, OK. The rotation lives on the static wrapper;
   the inner element animates and settles at identity. */
.vfx-stamp {
  position: absolute;
  top: var(--fc-s-2);
  right: var(--fc-s-2);
  transform: rotate(var(--vfx-rot, -8deg));
  pointer-events: none;
  z-index: 2;
}
.vfx-stamp > b {
  display: inline-block;
  padding: var(--fc-s-1) var(--fc-s-2);
  border: 2px solid currentColor;
  border-radius: var(--fc-r-xs);
  font: var(--fc-w-bold) var(--fc-t-sm)/1.2 var(--fc-font-sans);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fc-success);
  background: var(--glass-fill-hard);
  will-change: transform, opacity;
}
.vfx-stamp[data-tone="warn"] > b { color: var(--fc-warn); }
.vfx-stamp[data-tone="error"] > b, .vfx-stamp[data-tone="danger"] > b { color: var(--fc-danger); }
.vfx-stamp[data-tone="info"] > b { color: var(--fc-info); }

/* -------------------------------------------------------------- SHUTTER */
.vfx-shutter {
  position: fixed;
  pointer-events: none;
  background: var(--fc-ink-50);
  opacity: 0;
  z-index: var(--fc-z-top);
  border-radius: inherit;
}

/* ---------------------------------------------------------------- SHINE */
.vfx-shine { position: relative; overflow: hidden; }
.vfx-shine::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 35%, var(--glass-stroke) 50%, transparent 65%);
  transform: translateX(-100%);
  animation: vfx-shine var(--fc-m-slow) var(--fc-ease) forwards;
}
@keyframes vfx-shine { to { transform: translateX(100%); } }

/* ----------------------------------------------------------------- NEW */
/* The non-motion half of badgeBounce: a brief accent halo. */
.is-new { box-shadow: 0 0 0 2px var(--bg-base), 0 0 0 4px var(--accent); }

/* ------------------------------------------------------- REDUCED MOTION */
/* tokens.css already collapses every duration to 1ms. What is left is to hide
   the purely decorative layers and freeze the loops on their resting frame. */
@media (prefers-reduced-motion: reduce) {
  .vfx-layer { display: none; }
  .vfx-ring { animation: none; opacity: .35; transform: none; }
  .vfx-eta-dot, .vfx-ptr.is-busy > * { animation: none; }
  .vfx-shine::after { animation: none; transform: translateX(100%); }
  .vfx-dots i { transition: none; }
}
