/* ============================================================================
   SulyEye - motion
   ----------------------------------------------------------------------------
   Two rules govern everything here:

   1. Only `transform`, `opacity`, `filter` and `clip-path` animate. Anything
      that triggers layout is a bug, not a design decision.
   2. Every effect has an off switch under `prefers-reduced-motion`, and the
      off switch leaves the page *usable* - content that reveals on scroll must
      be visible, not stuck at opacity 0.
   ========================================================================== */

@layer motion {

  /* ============================================ scroll-driven reveals ==== */

  /* The initial state is applied by JS (adding `js-motion` to <html>) so the
     page is fully readable if the script never runs. */
  .js-motion [data-reveal] {
    opacity: 0;
    will-change: transform, opacity;
    transition:
      opacity var(--dur-reveal) var(--ease-out-quart),
      transform var(--dur-reveal) var(--ease-out-quart),
      filter var(--dur-reveal) var(--ease-out-quart);
    transition-delay: var(--reveal-delay, 0ms);
  }

  .js-motion [data-reveal="up"]    { transform: translate3d(0, 26px, 0); }
  .js-motion [data-reveal="down"]  { transform: translate3d(0, -20px, 0); }
  .js-motion [data-reveal="in"]    { transform: translate3d(0, 0, 0) scale(0.965); }
  .js-motion [data-reveal="blur"]  { transform: translate3d(0, 16px, 0); filter: blur(10px); }

  /* "start" and "end" are the logical edges, so a card that slides in from the
     margin slides from the correct side in Kurdish without a second rule. */
  .js-motion [data-reveal="start"] { transform: translate3d(calc(-34px * var(--dir, 1)), 0, 0); }
  .js-motion [data-reveal="end"]   { transform: translate3d(calc(34px * var(--dir, 1)), 0, 0); }

  .js-motion [data-reveal].is-revealed {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
    filter: blur(0);
  }

  /* --------------------------------------------------- headline reveal --- */

  /* Words are wrapped in spans by app.js and lifted from behind a mask, which
     reads as the sentence rising into place rather than fading on. */
  /* The mask has to clear descenders and the swash on an italic Fraunces "f",
     or the reveal shaves the bottom off the headline. The padding is added and
     then subtracted again so the extra space costs no layout. */
  .split-line {
    display: inline-block;
    overflow: hidden;
    vertical-align: bottom;
    padding-block: 0.02em 0.22em;
    margin-block: -0.02em -0.22em;
  }

  .split-word {
    display: inline-block;
    transform: translateY(110%);
    transition: transform 1s var(--ease-out-quart);
    transition-delay: var(--word-delay, 0ms);
  }

  .is-revealed .split-word,
  .split-ready .split-word { transform: translateY(0); }

  /* ------------------------------------------------------- line sweep --- */

  .rule-sweep {
    block-size: 1px;
    background: var(--line);
    transform: scaleX(0);
    transform-origin: inline-start;
    transition: transform 1.1s var(--ease-out-quart);
  }

  .rule-sweep.is-revealed { transform: scaleX(1); }

  /* ========================================================= parallax ==== */

  /* app.js writes --shift (px) on each frame; the element only ever translates. */
  [data-parallax] {
    transform: translate3d(0, var(--shift, 0px), 0);
    will-change: transform;
  }

  /* ======================================================== magnetism ==== */

  [data-magnetic] {
    transition: transform var(--dur-slow) var(--ease-out-quart);
  }

  [data-magnetic][data-pulling="true"] {
    transition: transform 120ms linear;
  }

  /* ==================================================== page transition == */

  /* A thin curtain wipes across on navigation. Progressive enhancement: where
     the View Transitions API exists we use it, otherwise nothing happens and
     the browser navigates normally. */
  @view-transition { navigation: auto; }

  ::view-transition-old(root) {
    animation: vt-out 260ms var(--ease-in-out-soft) both;
  }

  ::view-transition-new(root) {
    animation: vt-in 420ms var(--ease-out-quart) both;
  }

  @keyframes vt-out {
    to { opacity: 0; transform: translateY(-8px); }
  }

  @keyframes vt-in {
    from { opacity: 0; transform: translateY(10px); }
  }

  /* Deliberately no per-element `view-transition-name`. Naming the header or
     footer would opt them out of the root snapshot and force containment on
     them, which is a lot of subtlety to take on for a crossfade the root
     transition above already delivers. Add names only if a specific element
     needs to morph between two pages. */

  /* ========================================================== counters === */

  .counting { font-variant-numeric: tabular-nums; }

  /* ==================================================== reduced motion === */

  @media (prefers-reduced-motion: reduce) {
    /* Blanket kill. Anything that slipped through a hand-written rule below
       still ends up effectively static. */
    *,
    *::before,
    *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
      scroll-behavior: auto !important;
    }

    /* ...but "static" must not mean "invisible". */
    .js-motion [data-reveal],
    .js-motion [data-reveal].is-revealed {
      opacity: 1 !important;
      transform: none !important;
      filter: none !important;
    }

    .split-word { transform: none !important; }
    .rule-sweep { transform: scaleX(1) !important; }
    [data-parallax] { transform: none !important; }

    .aurora { animation: none; }
    .aurora__blob { animation: none !important; }
    .iris__scan { opacity: 0 !important; }
    .cursor-halo { display: none !important; }
    .marquee__track { animation: none !important; }
    .marquee { mask-image: none; overflow-x: auto; }

    @view-transition { navigation: none; }
  }
}
