/* ==========================================================================
   Load and scroll animations.

   Two rules govern everything here:

   1. Nothing is hidden unless JavaScript has confirmed it can un-hide it.
      Every initial-hidden state is scoped under `html.gt-anim`, a class only
      enhance.js adds. If the script fails, 404s, or is blocked, the page renders
      fully visible, an animation layer must never be able to hide content.

   2. prefers-reduced-motion is honoured by not opting in at all: enhance.js
      skips the class, so these rules never apply. The @media block at the bottom
      is a second belt for anything that slips through.

   The brand curtain is drawn with pseudo-elements on <html> rather than a real
   element, so React has nothing to reconcile and cannot tear it out mid-animation.
   ========================================================================== */

/* --- Brand curtain -------------------------------------------------------
   Set by a tiny inline script in <head> so it covers the page before first
   paint, rather than flashing content and then hiding it. That script always
   schedules its own removal, including a hard timeout, so the curtain cannot
   get stuck even if `load` never fires. */
html.gt-loading::before,
html.gt-loaded::before {
  content: "";
  position: fixed;
  inset: 0;
  background: #09090b;
  z-index: 9998;
  /* Never intercept clicks: if anything goes wrong the page stays usable. */
  pointer-events: none;
}

/* The mark is the site's own header logo, drawn as a background image on a single
   pseudo-element. background-image rather than content:url() so it can be sized
   and centred with ordinary properties instead of relying on replaced-element
   sizing, and one element rather than two because an image already carries its
   own colours, no need to split words across pseudo-elements to get two.

   The file is the same 8KB PNG the nav uses on every page, so it is either
   already in cache or a trivial same-origin fetch.

   --gt-logo is the logo's rendered width: the art is 500x200, so height follows
   at 2.5:1. Tune this one value to resize the mark. */
html.gt-loading,
html.gt-loaded {
  --gt-logo: min(58vw, 30rem);
}

html.gt-loading::after,
html.gt-loaded::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  background-image: url("/images/GHOST_WEBSITE_HEADER_TEXT_LOGO-removebg-preview.webp");
  background-repeat: no-repeat;
  background-position: center;
  background-size: var(--gt-logo) auto;
  pointer-events: none;
  animation: gt-mark-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Mark leaves first, then the curtain wipes up off the top. */
html.gt-loaded::after {
  animation: gt-mark-out 0.32s cubic-bezier(0.7, 0, 0.84, 0) forwards;
}
html.gt-loaded::before {
  animation: gt-curtain 0.62s cubic-bezier(0.76, 0, 0.24, 1) 0.14s forwards;
}

@keyframes gt-mark-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}
@keyframes gt-mark-out {
  to { opacity: 0; transform: scale(1.06); }
}
@keyframes gt-curtain {
  to { transform: translateY(-101%); }
}

/* --- Scroll / load reveals ----------------------------------------------
   One mechanism for both: elements start offset, and an IntersectionObserver
   adds .is-in. Anything already in view when the page loads therefore animates
   immediately, which is what makes the hero feel like a load animation without
   needing separate code. --gt-d is the per-element stagger delay. */
html.gt-anim [data-gt-reveal] {
  opacity: 0;
  transform: translateY(24px);
}

html.gt-anim [data-gt-reveal].is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.62s cubic-bezier(0.16, 1, 0.3, 1) var(--gt-d, 0s),
    transform 0.72s cubic-bezier(0.16, 1, 0.3, 1) var(--gt-d, 0s);
}

/* Headlines get a slightly heavier, slower rise, they carry the page. */
html.gt-anim h1[data-gt-reveal],
html.gt-anim h2[data-gt-reveal] {
  transform: translateY(34px);
}

/* Cards scale up a touch as they arrive, which reads as more deliberate than
   a plain fade on a grid of photographs. */
html.gt-anim [data-gt-reveal][data-gt-card] {
  transform: translateY(30px) scale(0.975);
}
html.gt-anim [data-gt-reveal][data-gt-card].is-in {
  transform: none;
}

/* --- Headline mask reveal -----------------------------------------------
   The hero headline rises out from behind a mask instead of fading in place.

   Two nested spans are injected inside the h1 rather than masking the h1 itself,
   because the h1 carries Tailwind's mb-8, clipping it would mean overriding its
   own margins and shifting the layout. The outer span is the mask; the inner one
   moves.

   The padding/negative-margin pair matters: these headlines use leading-[0.9], a
   line box shorter than the glyphs, so a plain overflow:hidden shears the tops
   and tails off the letters. The padding gives the glyphs room inside the clip
   and the negative margin takes the space back out of the layout.

   The h1 keeps data-gt-reveal so the existing observer still drives it, but its
   own fade is neutralised here, since the mask is doing the work. */
html.gt-anim [data-gt-mask] {
  display: block;
  overflow: hidden;
  /* 0.2em, not 0.12: the homepage headline scales "ELITE" to 1.22em, whose glyphs
     reach further out of the line box than the rest of the line. The negative
     margin cancels the padding, so this grows the clip box without moving layout. */
  padding-block: 0.2em;
  margin-block: -0.2em;
}

html.gt-anim [data-gt-mask-inner] {
  display: block;
  transform: translateY(112%);
}

html.gt-anim h1[data-gt-masked].is-in [data-gt-mask-inner] {
  transform: none;
  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--gt-d, 0s);
}

/* Must come after the generic and h1-specific reveal rules to win on order. */
html.gt-anim h1[data-gt-masked] {
  opacity: 1;
  transform: none;
}

/* --- Hero background: slow zoom + parallax ------------------------------
   Ken Burns drift on the hero photo, plus a slower-than-scroll parallax.

   The zoom animates `transform` while the parallax is written to `translate` by
   enhance.js. Two separate properties on purpose, sharing one would mean each
   effect clobbering the other every frame. They compose instead. */
html.gt-anim [data-gt-hero-bg] {
  animation: gt-kenburns 22s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes gt-kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.09); }
}

/* --- Film grain ----------------------------------------------------------
   A little texture over the flat blacks. Static rather than animated: a moving
   grain repaints the whole viewport every frame for very little visual gain.
   The noise is an inline SVG turbulence filter, so there is no extra request. */
html.gt-anim body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9985;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- Card cursor spotlight ----------------------------------------------
   A soft brand-red glow that tracks the pointer across a card. Coordinates come
   from enhance.js as percentages; the gradient itself is pure CSS.
   Only on real pointer devices, on touch it would just sit wherever the last
   tap landed.

   Scoped to [data-gt-tap], the cards that are or contain a link. A glow that
   follows the cursor is a hover state, and a hover state on a card that does
   nothing when clicked is the same false promise as the lift below. Athlete and
   process cards get the reveal and nothing else. */
@media (hover: hover) {
  html.gt-anim [data-gt-card][data-gt-tap] {
    position: relative;
    overflow: hidden;
  }
  html.gt-anim [data-gt-card][data-gt-tap]::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.35s ease;
    background: radial-gradient(
      18rem circle at var(--gt-mx, 50%) var(--gt-my, 50%),
      rgb(255 62 62 / 16%),
      transparent 62%
    );
  }
  html.gt-anim [data-gt-card][data-gt-tap]:hover::after {
    opacity: 1;
  }
}

/* --- Stat counters ------------------------------------------------------
   Tabular figures stop the number jittering horizontally as it counts. */
[data-gt-count] {
  font-variant-numeric: tabular-nums;
}

/* A brief flare as the figure lands, so the count resolves on a beat. */
[data-gt-count].gt-counted {
  animation: gt-count-land 0.75s ease-out;
}

@keyframes gt-count-land {
  0%   { text-shadow: 0 0 0 rgb(255 62 62 / 0%); }
  30%  { text-shadow: 0 0 24px rgb(255 62 62 / 65%); }
  100% { text-shadow: 0 0 0 rgb(255 62 62 / 0%); }
}

/* --- Nav condense on scroll ---------------------------------------------
   The blur is on the bar's `::before` (see overrides.css) rather than on the
   bar itself: `backdrop-filter` makes an element the containing block for every
   fixed descendant, and the mobile drawer is one, so on the bar it pulled the
   full-screen overlay in to the bar's own box. Strength still changes here on
   scroll, it just changes on the pseudo-element. */
nav[data-gt-nav] {
  transition: background-color 0.3s ease, border-color 0.3s ease;
}
nav[data-gt-nav]::before {
  transition: backdrop-filter 0.3s ease, -webkit-backdrop-filter 0.3s ease;
}
nav[data-gt-nav].gt-scrolled {
  background-color: rgb(0 0 0 / 72%);
  border-color: rgb(255 255 255 / 14%);
}
nav[data-gt-nav].gt-scrolled::before {
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  backdrop-filter: blur(20px) saturate(140%);
}
/* The bar itself tightens up, and the logo scales with it. */
nav[data-gt-nav] .h-20 {
  transition: height 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
nav[data-gt-nav].gt-scrolled .h-20 {
  height: 4rem;
}
nav[data-gt-nav] a.h-12 {
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  transform-origin: left center;
}
nav[data-gt-nav].gt-scrolled a.h-12 {
  transform: scale(0.88);
}

/* --- Hover polish on cards ----------------------------------------------
   Deliberately no blanket `transition: transform` on [data-gt-card]. That would
   also transition the element INTO its hidden offset the moment the attribute is
   set, so the card visibly slides down before it reveals. The reveal transition
   on .is-in is the only one needed; hover reuses it.

   The specificity here has to beat `html.gt-anim [data-gt-reveal][data-gt-card].is-in`
   (0,4,1), which sets transform:none, hence the full selector rather than a
   bare [data-gt-card]:hover. */
/* Only cards that actually respond to a click. Movement on hover reads as "this
   is a button", of the 11 cards on the homepage only 2 are links, so the other 9
   were advertising an interaction that does not exist. enhance.js adds
   data-gt-tap to the cards that are, or contain, a link or button. */
@media (hover: hover) {
  html.gt-anim [data-gt-reveal][data-gt-card][data-gt-tap].is-in:hover {
    transform: translateY(-6px);
  }
  /* Non-interactive cards still acknowledge the pointer, just without moving. */
  html.gt-anim [data-gt-reveal][data-gt-card]:not([data-gt-tap]).is-in {
    transition: border-color 0.3s ease, background-color 0.3s ease;
  }
  /* And they keep the ordinary arrow, even where a stray `cursor: pointer` from
     the utility bundle says otherwise. */
  [data-gt-card]:not([data-gt-tap]) {
    cursor: default;
  }
}

/* --- Reduced motion -----------------------------------------------------
   enhance.js already declines to opt in, so this is a second line of defence
   for any state that was set before the preference was read. */
@media (prefers-reduced-motion: reduce) {
  html.gt-anim [data-gt-reveal],
  html.gt-anim [data-gt-reveal].is-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  /* Curtain and logo both, so a reduced-motion visitor never sees either. */
  html.gt-loading::before,
  html.gt-loading::after,
  html.gt-loaded::before,
  html.gt-loaded::after {
    display: none !important;
  }
  [data-gt-card],
  nav[data-gt-nav],
  nav[data-gt-nav] .h-20,
  nav[data-gt-nav] a.h-12 {
    transition: none !important;
  }
  /* The headline must still be readable with the mask inert. */
  html.gt-anim [data-gt-mask-inner] {
    transform: none !important;
    transition: none !important;
  }
  html.gt-anim [data-gt-mask] {
    overflow: visible !important;
  }
  /* No drifting photo, no flare on the figures. */
  html.gt-anim [data-gt-hero-bg] {
    animation: none !important;
    translate: none !important;
  }
  [data-gt-count].gt-counted {
    animation: none !important;
  }
  /* Grain is static decoration, but drop it too, it is texture the visitor did
     not ask for. */
  html.gt-anim body::before {
    display: none !important;
  }
}
