/* ==========================================================================
   FORCE PR — 06 MOTION

   The legacy site ships 101 distinct keyframe animations, 47 of them
   bounce* variants, plus GSAP/TweenMax and jquery.appear on every page.
   This file is ~150 lines of CSS, one 1 KB IntersectionObserver, and no
   bounce of any kind.

   PRINCIPLES
   1. Motion clarifies, it does not perform. Reveals are 12px and 400ms,
      not 80px and 1200ms.
   2. Only opacity and transform are animated on reveal — both composited,
      neither triggers layout.
   3. Content is visible by default. The observer *adds* the animation.
      If JavaScript fails, everything is simply already there. The legacy
      approach — hide with CSS, reveal with JS — blanks the page when the
      script does not run.
   4. prefers-reduced-motion is honoured at the end of this file, and it is
      a real off switch, not a speed reduction.
   ========================================================================== */

@layer force.components {

  /* ======================================================================
     REVEAL
     Applied by assets/js/reveal.js. Markup carries `data-force-reveal`;
     the observer adds `.is-inview` once, then stops observing.
     ====================================================================== */

  /* Only elements the script has actually claimed get hidden. The script
     sets `data-force-reveal-ready` on <html> at parse time, so there is no
     window in which content is hidden with no observer running. */
  [data-force-reveal-ready] [data-force-reveal] {
    opacity: 0;
    transform: translateY(var(--force-reveal-distance));
    transition: opacity   var(--force-duration-slow) var(--force-ease-out),
                transform var(--force-duration-slow) var(--force-ease-out);
    transition-delay: var(--force-reveal-delay, 0ms);
    will-change: opacity, transform;
  }

  [data-force-reveal].is-inview {
    opacity: 1;
    transform: none;
    will-change: auto;
  }

  /* Directional variants */
  [data-force-reveal-ready] [data-force-reveal="left"]  { transform: translateX(calc(var(--force-reveal-distance) * -1)); }
  [data-force-reveal-ready] [data-force-reveal="right"] { transform: translateX(var(--force-reveal-distance)); }
  [data-force-reveal-ready] [data-force-reveal="fade"]  { transform: none; }

  [data-force-reveal-ready] [data-force-reveal="scale"] {
    transform: scale(0.97);
    transform-origin: center;
  }

  /* Blur-in — reserved for hero media. Expensive; use once per page. */
  [data-force-reveal-ready] [data-force-reveal="blur"] {
    transform: none;
    filter: blur(8px);
    transition: opacity var(--force-duration-slower) var(--force-ease-out),
                filter  var(--force-duration-slower) var(--force-ease-out);
  }

  [data-force-reveal="blur"].is-inview { filter: blur(0); }


  /* ======================================================================
     STAGGER
     Put `.force-stagger` on a parent; children reveal in sequence.
     Capped at 10 so a 40-item archive does not take four seconds to appear.
     ====================================================================== */

  .force-stagger > *:nth-child(1)  { --force-reveal-delay: calc(var(--force-stagger) * 0); }
  .force-stagger > *:nth-child(2)  { --force-reveal-delay: calc(var(--force-stagger) * 1); }
  .force-stagger > *:nth-child(3)  { --force-reveal-delay: calc(var(--force-stagger) * 2); }
  .force-stagger > *:nth-child(4)  { --force-reveal-delay: calc(var(--force-stagger) * 3); }
  .force-stagger > *:nth-child(5)  { --force-reveal-delay: calc(var(--force-stagger) * 4); }
  .force-stagger > *:nth-child(6)  { --force-reveal-delay: calc(var(--force-stagger) * 5); }
  .force-stagger > *:nth-child(7)  { --force-reveal-delay: calc(var(--force-stagger) * 6); }
  .force-stagger > *:nth-child(8)  { --force-reveal-delay: calc(var(--force-stagger) * 7); }
  .force-stagger > *:nth-child(9)  { --force-reveal-delay: calc(var(--force-stagger) * 8); }
  .force-stagger > *:nth-child(n+10) { --force-reveal-delay: calc(var(--force-stagger) * 9); }


  /* ======================================================================
     AMBIENT DRIFT
     The slow gradient movement behind heroes. 24s, tiny amplitude — it
     should be felt rather than noticed.
     ====================================================================== */

  .force-ambient--drift {
    animation: force-drift 24s var(--force-ease-in-out) infinite alternate;
  }

  @keyframes force-drift {
    from { transform: translate3d(0, 0, 0)       scale(1); }
    to   { transform: translate3d(4%, -3%, 0)    scale(1.08); }
  }

  /* Gradient sweep across a rule or a text fill. */
  .force-sweep {
    background-size: 200% 100%;
    animation: force-sweep 8s var(--force-ease-in-out) infinite alternate;
  }

  @keyframes force-sweep {
    from { background-position:   0% 50%; }
    to   { background-position: 100% 50%; }
  }


  /* ======================================================================
     PULSE
     For a single live/status indicator. Not for CTAs — a pulsing button is
     a nag, and it competes with the signature cursor.
     ====================================================================== */

  .force-pulse { animation: force-pulse 2.4s var(--force-ease-in-out) infinite; }

  @keyframes force-pulse {
    0%, 100% { box-shadow: var(--force-glow-accent-soft); }
    50%      { box-shadow: var(--force-glow-accent); }
  }


  /* ======================================================================
     LOADING
     ====================================================================== */

  .force-spinner {
    inline-size: 1.25rem;
    block-size: 1.25rem;
    border: 2px solid rgb(255 255 255 / 0.18);
    border-block-start-color: var(--force-accent);
    border-radius: var(--force-radius-pill);
    animation: force-spin 700ms linear infinite;
  }

  @keyframes force-spin { to { transform: rotate(360deg); } }
}


/* ==========================================================================
   REDUCED MOTION
   Deliberately OUTSIDE @layer, so it wins over every layered rule above
   without a single !important.

   What is switched off: the signature cursor blink, ambient drift, gradient
   sweep, pulse, hover zoom, and the reveal translate.
   What is kept: content visibility, focus rings, colour transitions on
   hover and focus (these communicate state and removing them would make the
   interface harder to use, not calmer), and the spinner, which indicates
   that work is in progress.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  html { scroll-behavior: auto; }

  /* Content must never be left hidden by a disabled animation. */
  [data-force-reveal-ready] [data-force-reveal] {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }

  .force-cursor::after {
    animation: none;
    opacity: 1;
  }

  .force-ambient--drift,
  .force-sweep,
  .force-pulse {
    animation: none;
  }

  .force-media--zoom > img,
  .force-card--link,
  .force-btn,
  .force-media__reveal {
    transform: none;
    transition-property: color, background-color, border-color, box-shadow, opacity;
    transition-duration: var(--force-duration-fast);
  }

  .force-card--link:hover,
  .force-card--link:focus-within,
  .force-btn--primary:hover,
  .force-btn--secondary:hover {
    transform: none;
  }

  .force-media--zoom:hover > img,
  .force-card--link:hover .force-media--zoom > img {
    transform: none;
  }

  /* The hover-reveal caption still appears — it carries content — it just
     does not slide. */
  .force-media__reveal { transform: none; }
}
