/* =============================================================================
   PIBITOS MEDIA — motion.css
   Stage (c). Everything that moves lives here and nowhere else.

   Two rules govern this file:

   1. style.css already renders the FINISHED state. Nothing here is required for
      the page to be correct — remove this file and you get a complete, static,
      fully readable site. That is also exactly what a reduced-motion visitor
      gets, which is why almost all of it sits inside one
      `prefers-reduced-motion: no-preference` block rather than being added and
      then reverted.

   2. Transform and opacity only, with the single documented exception of the
      timeline's clip-path (SPEC §6) — scaling a drawn curve would squash its
      wobble and the played half would stop matching the unplayed half.
   ============================================================================= */


/* --tl has to be registered before it can be animated: an unregistered custom
   property is an untyped token and interpolates by swapping, not tweening. */
@property --tl {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@keyframes tl-draw   { from { --tl: 0; }            to { --tl: 1; } }
@keyframes loader-failsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }

/* Safety net, deliberately OUTSIDE the reduced-motion block and not gated on
   anything. If loader.js throws, never loads, or the browser blocks modules,
   this clears the overlay after 4s so a JS failure can never leave the site
   permanently covered by its own splash screen. */
.loader { animation: loader-failsafe 1ms linear 4s forwards; }
@keyframes marquee   { from { transform: none; }    to { transform: translateX(-100%); } }
@keyframes scrub     { 0% { transform: scaleX(0); } 55%, 100% { transform: scaleX(1); } }
@keyframes player-in { from { opacity: 0; transform: translateY(10px) scale(.985); } }
@keyframes backdrop-in { from { opacity: 0; } }
@keyframes bob       { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }


@media (prefers-reduced-motion: no-preference) {

  /* ---------------------------------------------------------------------------
     1. THE TIMELINE

     Preferred path: no JS at all. The whole animation is one scroll-driven
     keyframe on :root, running on the compositor, and --tl inherits down to
     the progress layer. modules/timeline.js only exists for engines without
     scroll-driven animations, and it feature-detects this exact condition so
     the two can never both run.
     ------------------------------------------------------------------------ */

  @supports (animation-timeline: scroll()) {
    :root {
      animation-name: tl-draw;
      /* MUST be `auto`, and must be a longhand. The `animation` shorthand
         resets duration to its initial 0s, and a 0s scroll-driven animation
         never advances — --tl sat at its declared 1 and the rail was simply
         always full. `auto` is what binds progress to the timeline's range. */
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: scroll(root block);
    }
  }


  /* ---------------------------------------------------------------------------
     1b. LOADER — the figure drawing the curve
     ------------------------------------------------------------------------ */

  .loader { transition: transform .62s cubic-bezier(.76, 0, .24, 1); }
  .loader.is-done { transform: translateY(-100%); }

  .loader.is-running .loader__figure {
    animation: bob 1.15s cubic-bezier(.45, 0, .55, 1) 2;
    transform-origin: 420px 520px;
  }


  /* ---------------------------------------------------------------------------
     2. SECTION REVEALS

     Gated on .reveals, which reveal.js adds only once it has successfully set
     up — so a JS error can never leave content permanently invisible.
     Anything already on screen at load is marked is-in in the same frame and
     never animates, which protects LCP and avoids a flash.
     ------------------------------------------------------------------------ */

  .reveals [data-reveal] {
    opacity: 0;
    transform: translateY(16px);
  }
  .reveals [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: opacity .55s ease, transform .55s cubic-bezier(.2, .7, .3, 1);
    transition-delay: var(--d, 0ms);
  }
  /* Already visible at first paint: final state, no transition, no delay. */
  .reveals [data-reveal].is-instant {
    opacity: 1;
    transform: none;
    transition: none;
  }


  /* ---------------------------------------------------------------------------
     3. CLIENT MARQUEE

     style.css hides the duplicate row and lets the names wrap. Here the row
     becomes a single non-wrapping line and the duplicate is unhidden, which is
     the only reason it exists — two identical rows translating -100% in step
     hand off seamlessly.
     ------------------------------------------------------------------------ */

  /* The pill lifts and its ring warms on hover — same language as the cards. */
  .client__link { transition: border-color .25s ease, background-color .25s ease, transform .25s cubic-bezier(.2,.7,.3,1); }
  .client__link:hover,
  .client__link:focus-visible { border-color: var(--amber); transform: translateY(-2px); }

  .marquee { display: flex; flex-wrap: nowrap; }
  .marquee__row {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    padding-right: var(--sp-xl);
    animation: marquee 45s linear infinite;
  }
  .marquee__row[aria-hidden="true"] { display: flex; }

  /* Pauses on focus as well as hover — a keyboard user tabbing a name inside
     a moving band would otherwise be chasing it. */
  .marquee:hover  .marquee__row,
  .marquee:focus-within .marquee__row { animation-play-state: paused; }


  /* ---------------------------------------------------------------------------
     4. REEL CARDS
     ------------------------------------------------------------------------ */

  .reel__poster { transition: transform .5s cubic-bezier(.2, .7, .3, 1); }
  .reel:hover .reel__poster,
  .reel:focus-within .reel__poster { transform: scale(1.04); }

  /* ::before, not ::after -- .reel__link::after is the whole-card hit area. */
  .reel__media::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    height: .5em;
    background: var(--curve-x) repeat-x;
    background-size: 3em .5em;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .45s cubic-bezier(.2, .7, .3, 1);
  }
  .reel:hover .reel__media::before,
  .reel:focus-within .reel__media::before { transform: scaleX(1); }

  /* The wait, drawn. Same tile, swept once per cycle. */
  .player.is-loading .player__status[data-status="loading"]::after {
    animation: scrub 1.4s cubic-bezier(.45, 0, .55, 1) infinite;
    transform-origin: left center;
  }


  /* ---------------------------------------------------------------------------
     5. LINK UNDERLINES — the logo's curve as the hover language
     ------------------------------------------------------------------------ */

  .site-nav a,
  .lang-switch a,
  .contact__alt a,
  .site-footer__social a,
  .site-footer__contact a,
  .showreel__play { position: relative; }

  .site-nav a::after,
  .lang-switch a::after,
  .contact__alt a::after,
  .site-footer__social a::after,
  .site-footer__contact a::after,
  .showreel__play::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -.35em;
    height: .5em;
    background: var(--curve-x) repeat-x;
    background-size: 3em .5em;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .35s cubic-bezier(.2, .7, .3, 1);
  }
  .site-nav a:hover::after,          .site-nav a:focus-visible::after,
  .lang-switch a:hover::after,       .lang-switch a:focus-visible::after,
  .contact__alt a:hover::after,      .contact__alt a:focus-visible::after,
  .site-footer__social a:hover::after, .site-footer__social a:focus-visible::after,
  .site-footer__contact a:hover::after, .site-footer__contact a:focus-visible::after,
  .showreel__play:hover::after,      .showreel__play:focus-visible::after {
    transform: scaleX(1);
  }

  /* The underline replaces the static one on .contact__alt links, which would
     otherwise sit on top of the drawn curve. */
  .contact__alt a { text-decoration: none; }


  /* ---------------------------------------------------------------------------
     6. HEADER CONDENSE

     Deliberately not a padding transition — that animates layout every frame.
     The bar gains a translucent, blurred ground and the wordmark scales down;
     both are compositor work.
     ------------------------------------------------------------------------ */

  .site-header { transition: background-color .3s ease, box-shadow .3s ease; }
  .brand img   { transition: transform .3s cubic-bezier(.2, .7, .3, 1); transform-origin: left center; }

  /* A hairline under a full-width bar made sense; a floating one needs to cast
     instead. Opacity firms up and a real shadow lifts it off the page. */
  .site-header.is-condensed {
    background-color: rgb(15 15 14 / .8);
    background-color: rgb(from var(--ink) r g b / .8);
    box-shadow: 0 10px 30px -12px rgb(0 0 0 / .7);
  }
  .site-header.is-condensed .brand img { transform: scale(.82); }


  /* ---------------------------------------------------------------------------
     6b. PLAYER — continuity

     The dialog appeared instantly on top of the page with no relationship to
     the card that opened it. A short rise settles it; the backdrop fades under
     it so the page reads as receding rather than being replaced.
     ------------------------------------------------------------------------ */

  .player[open]            { animation: player-in .32s cubic-bezier(.16, 1, .3, 1) both; }
  .player[open]::backdrop  { animation: backdrop-in .32s ease both; }


  /* ---------------------------------------------------------------------------
     7. BUTTONS + FORM
     ------------------------------------------------------------------------ */

  .btn { transition: transform .2s cubic-bezier(.2, .7, .3, 1), background-color .2s ease, border-color .2s ease; }
  .btn:hover { transform: translateY(-2px); }
  .btn:active { transform: translateY(0); }
  .btn--outline:hover { background-color: var(--amber-soft); }
  .btn--ghost:hover   { border-color: var(--bone-dim); }

  .field input,
  .field textarea { transition: border-color .2s ease, background-color .2s ease; }
  .field input:hover,
  .field textarea:hover { border-color: var(--bone-dim); }

  .enquiry__status { transition: opacity .3s ease; }


  /* ---------------------------------------------------------------------------
     8. CUSTOM CURSOR

     Fine pointers only. `cursor: none` is applied via .has-cursor, which
     cursor.js adds only after the element exists and has been positioned — if
     anything fails, the native cursor is never hidden.
     ------------------------------------------------------------------------ */

  @media (pointer: fine) {
    .cursor {
      position: fixed;
      top: 0;
      left: 0;
      z-index: 200;
      width: 62px;
      height: 62px;
      margin: -31px 0 0 -31px;
      pointer-events: none;
      will-change: transform;
      transition: opacity .2s ease;
    }
    .cursor::before {
      content: "";
      position: absolute;
      inset: 0;
      border-radius: 50%;
      background: var(--amber);
      transform: scale(.2258);          /* 14 / 62 */
      transition: transform .2s cubic-bezier(.2, .7, .3, 1), opacity .2s ease;
    }
    .cursor.is-lg::before   { transform: scale(.6774); opacity: .55; }   /* 42 / 62 */
    .cursor.is-play::before { transform: scale(1);     opacity: .92; }
    .cursor.is-out   { opacity: 0; }

    .cursor__label {
      position: absolute;
      inset: 0;
      display: grid;
      place-items: center;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: .06em;
      color: var(--ink);
      opacity: 0;
    }
    .cursor.is-play .cursor__label { opacity: 1; }

    .has-cursor,
    .has-cursor a,
    .has-cursor button,
    .has-cursor .reel__link { cursor: none; }

    /* A modal <dialog> is in the top layer, above every z-index, so the drawn
       cursor cannot be raised over it -- and `cursor: none` was leaving the
       player with no pointer at all. Give the real one back for the duration
       and take the drawn one off screen. */
    .has-cursor.is-modal,
    .has-cursor.is-modal a,
    .has-cursor.is-modal button,
    .has-cursor.is-modal .reel__link { cursor: auto; }
    .is-modal .cursor { opacity: 0; }
  }
}


/* -----------------------------------------------------------------------------
   REDUCED MOTION

   Nothing above applies. These few rules exist because the *static* stylesheet
   assumes smooth scrolling.
   The timeline still reads at full length: style.css sets --tl to 1 and no
   animation ever overrides it here.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
