/* ═══════════════════════════════════════════════════════════════════════
   RASTA Collective — motion
   Loaded LAST so the reduced-motion block below wins the cascade outright.
   Everything that moves is declared here; nothing else in the codebase
   defines a keyframe or a scroll-linked transform.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Hero entrance ──────────────────────────────────────────────────────── */

@keyframes rise {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
  to   { opacity: 1; transform: none; }
}

.hero__title,
.hero__cta {
  animation: rise .8s var(--ease-out) backwards;
}
.hero__title { animation-delay: .12s; }
.hero__cta   { animation-delay: .32s; }


/* ── Join RASTA arrives in red ──────────────────────────────────────────
   The button is painted as a red gradient over a dark ground rather than a
   red background-color, so the fill can be swept in — it lands as a dark
   red-ruled outline and then floods to solid, which is the one thing on the
   banner that moves after everything else has settled.

   `backwards` is what holds the empty state through the delay; without it
   the button would paint solid red first and then animate from nothing.
   The hover has to restate its own gradient, because the shorthand
   `background` in base.css can no longer reach the image layer. */
@keyframes cta-fill {
  from { background-size: 0% 100%; }
  to   { background-size: 100% 100%; }
}

.hero__cta .btn--solid {
  background-color: rgba(11, 11, 11, .55);
  background-image: linear-gradient(var(--red), var(--red));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 100% 100%;
  animation: cta-fill .72s cubic-bezier(.45, .05, .25, 1) 1.15s backwards;
}
.hero__cta .btn--solid:hover {
  background-image: linear-gradient(var(--yellow), var(--yellow));
}


/* ── Parallax ───────────────────────────────────────────────────────────
   Every layer reads the same --hero-p at a different rate. All of it is
   transform/opacity, so it stays on the compositor and never triggers
   layout. scroll.js writes the property and touches nothing else. */

/* Scaled about its own bottom edge, NOT translated. A vertical parallax on the
   figure lifted the heap of riot gear off the section's base line the moment
   you scrolled, leaving a strip of bare black under a collage that is supposed
   to be standing on the ground. Scaling from 50% 100% keeps that edge welded
   to the base line while the artwork still gains depth against the copy. */
.hero__figure {
  transform-origin: 50% 100%;
  transform: scale(calc(1 + var(--hero-p) * .05));
}
.hero__halo   { transform: translate3d(-50%, calc(-50% + var(--hero-p) * -140px), 0)
                           scale(calc(1 + var(--hero-p) * .18)); }
.hero__copy   { transform: translate3d(0, calc(var(--hero-p) * 40px), 0); }

/* Hinged on the same base line as the figure, and growing a little faster —
   which is where the parallax between the sheet and the figure now comes from.
   It used to come from a translate, and a translate is exactly what tore the
   artwork away from the bottom of the section. */
.hero__paper {
  transform-origin: 50% 100%;
  transform: scale(calc(1 + var(--hero-p) * .1));
}

.hero__figure,
.hero__halo,
.hero__paper { will-change: transform; }


/* ── Highlighter ────────────────────────────────────────────────────────
   A maroon marker sweeps across the key phrase of each chapter as it comes
   into view. Drawn as a background-size transition rather than a width or a
   pseudo-element, so it works on an inline run that wraps across lines and
   needs no wrapper element per line.

   The text flips to paper at the same time: #F4ECE4 on #A00E0E is ~7.4:1,
   whereas leaving the original ink under a solid maroon fill would be
   unreadable the moment the sweep passed. */

.hl {
  --hl-sweep: .78s;
  background-image: linear-gradient(var(--red), var(--red));
  background-repeat: no-repeat;
  background-size: 0% 100%;
  background-position: 0 0;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  padding: .06em .22em;
  margin-inline: -.22em;
  color: inherit;

  /* The two halves of this transition have to be sequenced, not overlapped.
     Run them together — as an eased colour fade starting a fraction into the
     sweep — and the whole phrase turns paper-cream while the marker is only a
     third of the way across, so the part it has not reached yet is cream on
     cream: the text DISAPPEARS mid-sweep and reappears behind the marker. That
     dropout is the flicker, not the sweep itself.

     So the colour is held until the stroke is essentially home (at .63s the
     curve below is ~97% across) and then flipped over .15s. At every instant
     the uncovered text is ink on paper and the covered text is cream on
     maroon; the only overlap is the last two or three characters.

     The curve matters as much as the sequence. An ease-out starts at full
     speed — it was 50% across in the first 100ms, so the stroke appeared to
     snap and then sit still waiting for the colour. This one starts slow,
     carries through the middle, and settles: a marker being pulled, not a
     bar being filled. */
  transition: background-size var(--hl-sweep) cubic-bezier(.45, .05, .25, 1),
              color .15s linear .63s;
}
.hl.is-lit {
  background-size: 100% 100%;
  color: var(--paper);
}
/* <mark> defaults to a yellow ground and black text in every UA sheet. */
mark.hl { background-color: transparent; }


/* ── Scroll-reveal for the stacked (non-pinned) About ────────────────────
   Alternating sides, set by scroll.js via IntersectionObserver. In pinned
   mode the panels already travel horizontally, so this is switched off. */

.about:not(.is-pinned) .panel {
  opacity: 0;
  transform: translate3d(var(--from, -28px), 0, 0);
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
.about:not(.is-pinned) .panel:nth-child(even) { --from: 28px; }
.about:not(.is-pinned) .panel.is-visible { opacity: 1; transform: none; }


/* ═══════════════════════════════════════════════════════════════════════
   REDUCED MOTION
   The JS gates match this exactly: the rAF loop never starts and the pin
   engine never initialises, so these rules are belt to that braces.
   ═══════════════════════════════════════════════════════════════════════ */

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

  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  /* Kill every scroll-linked transform outright rather than freezing it at
     whatever --hero-p happened to be. */
  .hero__figure,
  .hero__copy,
  .hero__paper { transform: none !important; transform-origin: 50% 50%; }
  .hero__halo  { transform: translate3d(-50%, -50%, 0) !important; }

  .hero__figure,
  .hero__halo,
  .hero__paper { will-change: auto; }

  /* The global .01ms duration above does not touch animation-delay, so the
     button would still sit unfilled for a beat and then snap. Drop the
     animation outright and let the declared 100% background-size stand. */
  .hero__cta .btn--solid { animation: none; }

  /* The highlight is information, not decoration — the phrase it marks is
     the point of the chapter. Show it outright rather than suppressing it. */
  .hl { background-size: 100% 100% !important; color: var(--paper) !important; }

  /* Panels must be visible without ever having been observed. */
  .about:not(.is-pinned) .panel { opacity: 1 !important; transform: none !important; }
}
