/* ═══════════════════════════════════════════════════════
   GCDAY : Animations
   The 16-second 8-colour button cycle, part of the brand. Text stays white throughout. Hover
   transform stays independent so it stacks on the cycling.
═══════════════════════════════════════════════════════ */

/* Background colour: 8 stops, each holds for ~2 seconds with
   eased transitions between. The percent stops are spaced
   evenly across the 16-second cycle. */
@keyframes btn-color-cycle {
  0%   { background-color: #812166; }  /* Purple */
  12.5%{ background-color: #e21b79; }  /* Pink */
  25%  { background-color: #e13c30; }  /* Red */
  37.5%{ background-color: #e77724; }  /* Orange */
  50%  { background-color: #fddd00; }  /* Yellow */
  62.5%{ background-color: #43b049; }  /* Green */
  75%  { background-color: #0b85c8; }  /* Light Blue */
  87.5%{ background-color: #215baa; }  /* Dark Blue */
  100% { background-color: #812166; }  /* back to Purple */
}

/* Text stays white for the whole cycle. A deliberate choice, made with the
   contrast numbers in hand.

   An accessibility pass had cycled the text to near-black over the lighter
   backgrounds. It measured well, but the flip to black read as jarring and
   over-contrasted, so it is reverted and this is the final version.

   Recorded so nobody re-opens it blind: white on these backgrounds measures
   purple 8.96, pink 4.50, red 4.29, orange 2.97, yellow 1.35, green 2.78,
   light blue 4.04, dark blue 6.68. WCAG AA wants 4.5:1, so five of the
   eight fall short and yellow is the extreme case.

   If it is ever revisited, the option that keeps white text AND passes is
   to drop the light backgrounds from the cycle rather than to change the
   text colour. Do not simply re-add a colour swap. */
@keyframes btn-text-cycle {
  0%, 100% { color: #ffffff; }
}

/* Subtle floating motion for the decorative icon graphic */
@keyframes icon-float {
  0%, 100% { transform: translateY(-50%) rotate(0deg); }
  50%      { transform: translateY(-52%) rotate(-2deg); }
}

.icon-bleed__img {
  animation: icon-float 18s ease-in-out infinite;
}

/* Fade-in on first paint, for sections that come into view */
@keyframes fade-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

[data-anim="fade-up"] {
  animation: fade-up 600ms var(--ease-standard) both;
}

@media (prefers-reduced-motion: reduce) {
  .btn-primary {
    animation: none !important;
    background-color: var(--purple) !important;
    color: #ffffff !important;
  }
  .icon-bleed__img {
    animation: none !important;
    transform: translateY(-50%);
  }
  [data-anim="fade-up"] {
    animation: none !important;
  }
}

/* ═══ Hero entrance, line-by-line ripple ═══
   The three landing-copy lines slide in from the left and fade up on
   page load, staggered so they read like a ripple. The CTAs follow
   after the last line lands, fading up from below (centered content). */

@keyframes hero-line-rise {
  from { opacity: 0; transform: translateX(-44px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes hero-actions-rise {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.landing-copy {
  opacity: 0;
  animation: hero-line-rise 0.85s var(--ease-standard) both;
}

.landing-copy--line1 { animation-delay: 0.30s; }
.landing-copy--line2 { animation-delay: 0.50s; }
.landing-copy--line3 { animation-delay: 0.70s; }

.landing-actions {
  opacity: 0;
  animation: hero-actions-rise 0.8s var(--ease-standard) 1.00s both;
}

/* ═══ Scroll reveal utility classes ═══
   Add .reveal / .reveal-left / .reveal-right / .reveal-scale to any
   element. main.js attaches .is-visible when the element scrolls
   into view, which transitions it to its resting state. */

/* Reveal classes use pure CSS animations so they always run on page
   load, with no JS dependency. The JS observer can still add
   .is-visible for scroll-triggered effects later, but content is
   visible regardless. */

@keyframes reveal-up    { from { opacity: 0; transform: translateY(28px); }  to { opacity: 1; transform: none; } }
@keyframes reveal-left  { from { opacity: 0; transform: translateX(-32px); } to { opacity: 1; transform: none; } }
@keyframes reveal-right { from { opacity: 0; transform: translateX(32px); }  to { opacity: 1; transform: none; } }
@keyframes reveal-scale { from { opacity: 0; transform: scale(0.94); }       to { opacity: 1; transform: none; } }

.reveal,
.reveal-left,
.reveal-right,
.reveal-scale {
  animation-duration: 0.7s;
  animation-timing-function: var(--ease-standard);
  animation-delay: 0.2s;
  animation-fill-mode: both;
  /* An entrance runs once. Stating it here rather than relying on the
     default matters, because these rules only override animation-name
     and leave iteration-count to whatever else is on the element. A
     .btn-primary carries `infinite` in its shorthand, so any button
     that also had a reveal class looped its entrance forever. */
  animation-iteration-count: 1;
}

.reveal        { animation-name: reveal-up; }
.reveal-left   { animation-name: reveal-left; }
.reveal-right  { animation-name: reveal-right; }
.reveal-scale  { animation-name: reveal-scale; }

/* A button that also carries a reveal class has two jobs: run the
   one-shot entrance, and keep the standing 8-colour cycle. Both only
   survive if all three animations are listed explicitly, because a
   single animation-name silently drops whichever it doesn't name.
   That's why these buttons used to sit stuck on purple. */
.btn-primary.reveal,
.btn-primary.reveal-left,
.btn-primary.reveal-right,
.btn-primary.reveal-scale {
  animation-duration: 0.7s, 16s, 16s;
  animation-timing-function: var(--ease-standard), ease-in-out, steps(1, end);
  animation-iteration-count: 1, infinite, infinite;
  animation-fill-mode: both, none, none;
}

.btn-primary.reveal       { animation-name: reveal-up,    btn-color-cycle, btn-text-cycle; }
.btn-primary.reveal-left  { animation-name: reveal-left,  btn-color-cycle, btn-text-cycle; }
.btn-primary.reveal-right { animation-name: reveal-right, btn-color-cycle, btn-text-cycle; }
.btn-primary.reveal-scale { animation-name: reveal-scale, btn-color-cycle, btn-text-cycle; }

/* Stagger delay helpers, add to a reveal element for cascading entry. */
.delay-1 { animation-delay: 0.28s; }
.delay-2 { animation-delay: 0.36s; }
.delay-3 { animation-delay: 0.44s; }
.delay-4 { animation-delay: 0.52s; }
.delay-5 { animation-delay: 0.60s; }
.delay-6 { animation-delay: 0.68s; }

/* ═══ Scroll-triggered reveal ═══
   Different job to .reveal above. Those fire once on page load, which is
   right for content near the top. This one holds an element until it
   actually scrolls into view, which is what a long list like the Hayden
   timeline needs.

   It only ever hides anything when JS is confirmed to be running: an
   inline script in <head> puts .js on <html> before first paint, and
   main.js reveals everything after 4 seconds regardless. So a JS failure
   leaves the page fully readable instead of blank. */

.js .scroll-reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 640ms var(--ease-standard),
              transform 640ms var(--ease-standard);
  will-change: opacity, transform;
}

.js .scroll-reveal.is-visible {
  opacity: 1;
  transform: none;
}

.js .scroll-reveal--left  { transform: translateX(-30px); }
.js .scroll-reveal--right { transform: translateX(30px); }
.js .scroll-reveal--left.is-visible,
.js .scroll-reveal--right.is-visible { transform: none; }

/* Slide variant, used by the Hayden timeline.
   The entry itself stays exactly where it is and does nothing. Its inner
   slide is what moves, starting out to the left and travelling right into
   place. That split is the whole point: a clip on a moving element travels
   with it and hides nothing, so the clipping has to sit on something
   stationary. .timeline-item__mask is that stationary thing, and its left
   edge is parked just clear of the rail, which is what guarantees no
   content ever paints on the wrong side of the timeline. */

.js .scroll-reveal--slide {
  opacity: 1;
  transform: none;
  transition: none;
}

.js .scroll-reveal--slide .timeline-item__slide {
  opacity: 0;
  transform: translateX(-96px);
  transition: opacity 520ms var(--ease-standard),
              transform 780ms var(--ease-standard);
  will-change: opacity, transform;
}

.js .scroll-reveal--slide.is-visible .timeline-item__slide {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .scroll-reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .js .scroll-reveal--slide .timeline-item__slide {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* GSAP SplitText helpers (used when [data-split] is on a heading) */
.split-wrap { overflow: hidden; display: block; }
.split-char,
.split-word {
  display: inline-block;
  will-change: transform, opacity;
}

/* ═══ Page transition overlay ═══
   Sits above everything, hidden by default. main.js scales it up to
   cover the page when an internal link is clicked, then navigation
   completes underneath. */

.page-overlay {
  position: fixed;
  inset: 0;
  background: var(--dark-grey);
  z-index: 9000;
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 320ms var(--ease-standard);
  pointer-events: none;
}
.page-overlay.out {
  transform: scaleY(1);
  transform-origin: bottom;
  pointer-events: all;
}
.page-overlay.in {
  transform: scaleY(0);
  transform-origin: top;
}

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal-left, .reveal-right, .reveal-scale {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  .landing-copy, .landing-actions {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  .page-overlay { display: none !important; }
}
