/* ============================================================================
   Motion — Framesite.ai
   ----------------------------------------------------------------------------
   The brand's motion language is built on one idea: the PLAYHEAD. Things scrub
   in, scan, and settle, the way a video timeline does. One easing curve
   (--ease, from spacing.css), short durations, no bounce, no decorative loops
   on content. Everything degrades to a clean static end-state under
   prefers-reduced-motion (so print, PDF, and reduced-motion users see content,
   never a pre-animation blank).

   Usage:
   - Add class `fs-rise` / `fs-fade` / `fs-pop` to animate an element in.
   - For a staggered group, set `style="--i:0"`, `--i:1`, ... on children and
     give them `fs-rise fs-stagger`.
   - Gate entrance animations behind a container with `[data-animate]` if you
     want to trigger on view; the bare classes animate on load.
   ============================================================================ */

@keyframes fs-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes fs-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes fs-pop {
  0%   { opacity: 0; transform: scale(0.94); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes fs-rise-left {
  from { opacity: 0; transform: translateX(-12px); }
  to   { opacity: 1; transform: translateX(0); }
}
/* The signature: a playhead sweeping a timeline left to right. */
@keyframes fs-scrub {
  0%   { transform: translateX(-2%); }
  100% { transform: translateX(102%); }
}
/* A scanning line reading a frame top to bottom (loaders, thumbnails). */
@keyframes fs-scan {
  0%   { transform: translateY(-100%); opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateY(900%); opacity: 0; }
}
/* Skeleton shimmer for content that is still rendering. */
@keyframes fs-shimmer {
  0%   { background-position: -150% 0; }
  100% { background-position: 250% 0; }
}
/* Expanding ring around an active processing dot. */
@keyframes fs-ripple {
  0%   { transform: scale(1); opacity: 0.55; }
  100% { transform: scale(2.2); opacity: 0; }
}
/* A blinking caret / record dot. */
@keyframes fs-blink {
  0%, 55% { opacity: 1; }
  60%, 100% { opacity: 0.25; }
}
/* Tape-reel / processing spin (used sparingly, on loaders only). */
@keyframes fs-spin {
  to { transform: rotate(360deg); }
}
/* Slide-over panel entrance (detail panels — conditionally rendered, never in a card thumbnail).
   Transform-only so a paused/captured frame still shows the panel (never blank). */
@keyframes fs-panel-in {
  from { transform: translateX(26px); }
  to   { transform: none; }
}

/* ---- entrance utility classes ---- */
.fs-rise  { animation: fs-rise var(--dur-slow, 0.34s) var(--ease, cubic-bezier(0.22,1,0.36,1)) both; }
.fs-fade  { animation: fs-fade var(--dur-slow, 0.34s) var(--ease) both; }
.fs-pop   { animation: fs-pop var(--dur-med, 0.22s) var(--ease) both; }
.fs-rise-left { animation: fs-rise-left var(--dur-slow, 0.34s) var(--ease) both; }

/* staggered children: set --i on each child */
.fs-stagger { animation-delay: calc(var(--i, 0) * 70ms); }

/* recurring brand device: a hairline timeline with a sweeping playhead */
.fs-scrubline {
  position: relative;
  height: 2px;
  border-radius: 999px;
  background: var(--app-line, rgba(0,0,0,.12));
  overflow: hidden;
}
.fs-scrubline::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 34%;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--app-crimson, #c42a43), transparent);
  animation: fs-scrub 1.4s var(--ease) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .fs-rise, .fs-fade, .fs-pop, .fs-rise-left { animation: none; opacity: 1; transform: none; }
  .fs-scrubline::after { animation: none; }
}
