/* ===========================================================================
   Academyship preloader — standalone, dependency-free.

   Visual match for the about-page preloader, but driven entirely by CSS
   animations so the homepage does not have to load jQuery or GSAP for it.
   Eight near-black bars sweep left to right across a white ground while the
   wordmark sits above them, inverting itself via difference blending, then a
   the whole curtain slides off to the right to reveal the page.

   The element removes itself when the wipe finishes (see script/preloader.js),
   so nothing here survives past the intro — no lingering layers, no animations
   left running.
   ========================================================================= */

/* The wordmark face is declared HERE, not left to the host page. This file is
   now linked from pages that never loaded Welfare (the docs shell, 404,
   tour-video, help-centre-docs …), and without its own @font-face the intro
   would silently fall back to a different typeface on each of them — the exact
   inconsistency this shared component exists to remove.

   Note the capital F in ../Fonts/: that is the directory that actually exists.
   style/about/academyship-about-base.css asks for ../../fonts/welfare.woff2,
   which resolves on Windows but 404s on a case-sensitive host. Declaring the
   face here means the preloader no longer depends on that.

   url() is resolved against this stylesheet, not the page, so the one path is
   correct from every directory depth. */
@font-face {
  font-family: "Welfare";
  src: url("../Fonts/welfare.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.ax-pre {
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: #ffffff;
  /* The exit is a transform, not a clip-path. clip-path is a paint-driven
     property: the browser re-rasterises the whole curtain every frame on the
     main thread, and the main thread is at its busiest exactly then (images
     decoding, GSAP/Lenis starting, fonts swapping as the page appears). That
     contention was the stutter. A translate runs on the compositor and is
     unaffected by main-thread work. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
}

/* will-change is armed only while the intro is actually running, never at rest.
   A permanent will-change holds a compositor layer for the life of the page,
   and build/check-site.mjs budgets exactly that. .is-running is added by
   script/preloader.js when the intro starts and the element is removed when it
   ends, so the hint exists for precisely the ~3s it is useful. */
.ax-pre.is-running { will-change: transform; }
.ax-pre.is-running .ax-pre__bar::before { will-change: transform; }
.ax-pre.is-running .ax-pre__name { will-change: transform, opacity; }

/* --- the bars ----------------------------------------------------------- */

.ax-pre__bars {
  position: absolute;
  inset: 0;
  display: flex;
  z-index: 2;
}

.ax-pre__bar {
  flex: 1 1 12.5%;
  position: relative;
}

/* The stagger index lives here rather than in a style="--i:N" attribute on
   every bar. build/check-contact.mjs enforces "no inline style attribute
   remains" on the contact page (its build lifts inline styles into classes),
   and hard-coding eight indices in the markup would have to be repeated,
   correctly, on every page that adopts the preloader. One rule, eight
   children, nothing to keep in sync. */
.ax-pre__bar:nth-child(1) { --i: 0; }
.ax-pre__bar:nth-child(2) { --i: 1; }
.ax-pre__bar:nth-child(3) { --i: 2; }
.ax-pre__bar:nth-child(4) { --i: 3; }
.ax-pre__bar:nth-child(5) { --i: 4; }
.ax-pre__bar:nth-child(6) { --i: 5; }
.ax-pre__bar:nth-child(7) { --i: 6; }
.ax-pre__bar:nth-child(8) { --i: 7; }

/* Filled by a pseudo-element so the bar itself never changes size — the fill
   is a pure transform, composited on the GPU, no per-frame layout.

   The fill runs 1px past its own right edge. Eight bars across a viewport
   whose width is not a multiple of 8 (1366, 1440, 1536 …) put every bar
   boundary on a fractional device pixel, and two antialiased edges meeting
   there do not quite cover it — that is the pair of hairline vertical seams
   that showed on the black screen. Overlapping the neighbour removes the
   boundary entirely. The last bar's overflow is clipped by .ax-pre. */
.ax-pre__bar::before {
  content: "";
  position: absolute;
  inset: 0 -1px 0 0;
  background: #0e1116;
  transform: scaleX(0);
  transform-origin: left center;
  backface-visibility: hidden;
}

.ax-pre.is-running .ax-pre__bar::before {
  animation: axPreFill 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  /* Staggered rather than sequential, so the sweep reads as one continuous
     motion instead of separate blocks. */
  animation-delay: calc(var(--i) * 0.13s);
}

@keyframes axPreFill {
  0%   { transform: scaleX(0); }
  55%  { transform: scaleX(0.6); }
  100% { transform: scaleX(1); }
}

/* --- the wordmark ------------------------------------------------------- */

.ax-pre__name {
  position: relative;
  z-index: 3; /* above the bars, so they can never cover it */
  font-family: "Welfare", "AnekBangla", system-ui, sans-serif;
  font-size: clamp(34px, 7.5vw, 84px);
  font-weight: 400;
  font-synthesis: none;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  text-indent: 0.16em; /* balances the trailing letter-space so it centres */

  /* Difference blending: white glyphs over the white ground resolve to black,
     and over a near-black bar they resolve to white. The wordmark therefore
     inverts letter by letter as the bars sweep in behind it and stays legible
     for the entire intro — no colour switching, no fade. */
  color: #ffffff;
  -webkit-text-fill-color: #ffffff;
  mix-blend-mode: difference;

  /* Hidden until the webfont resolves, so the fallback-to-webfont swap happens
     behind the curtain rather than on screen. JS adds .is-ready. */
  opacity: 0;
  transform: scale(0.97);
  transition: opacity 0.35s ease;
}

.ax-pre.is-ready .ax-pre__name { opacity: 1; }

/* The breath now FINISHES at 2.23s, the instant the wipe begins — it used to
   run to 3.03s and overlap it. That overlap was the second half of the shake:
   this element carries mix-blend-mode: difference, so every frame of the scale
   forced the browser to re-blend the wordmark against the bars behind it, and
   a blended layer that is still changing cannot be cached and handed to the
   compositor. Ending it first leaves a static layer that the exit transform
   simply carries off. */
.ax-pre.is-running .ax-pre__name {
  animation: axPreBreath 2.23s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

@keyframes axPreBreath {
  0%   { transform: scale(0.97); }
  68%  { transform: scale(1); }
  84%  { transform: scale(1); }
  100% { transform: scale(1.03); }
}

/* Without blend support the white glyphs would be invisible on the white
   ground, so fall back to flat black and lose only the inversion. */
@supports not (mix-blend-mode: difference) {
  .ax-pre__name {
    color: #0e1116;
    -webkit-text-fill-color: #0e1116;
    mix-blend-mode: normal;
  }
}

/* --- the wipe ----------------------------------------------------------- */

/* Bars finish at ~1.53s (last starts 0.91s + 0.62s), then a 0.7s hold, so the
   wipe begins at 2.23s. The wordmark is still at full opacity when it starts
   and is carried off by the curtain itself.

   The easing is a long, shallow curve rather than the old easeInOutCubic: it
   reaches cruising speed early and holds it, so the slide reads as one steady
   travel instead of accelerating away. A position:fixed element cannot extend
   the document's scrollable area, so sliding it past the right edge never
   produces a scrollbar. */
.ax-pre.is-running {
  animation: axPreWipe 0.9s cubic-bezier(0.5, 0.02, 0.28, 1) 2.23s forwards;
}

@keyframes axPreWipe {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(100%, 0, 0); }
}

/* --- reduced motion ----------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .ax-pre__name {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* These selectors deliberately MATCH the specificity of the three rules they
     switch off (.ax-pre.is-running, .ax-pre.is-running .ax-pre__name and
     .ax-pre.is-running .ax-pre__bar::before) and sit later in the file, so they
     win on order alone. They used to carry !important, which cost two points of
     cascade debt — build/check-school.mjs budgets that for the generated bundle
     and fails the build if it grows. Same behaviour, nothing borrowed. */
  .ax-pre.is-running,
  .ax-pre.is-running .ax-pre__name,
  .ax-pre.is-running .ax-pre__bar::before {
    animation: none;
  }
}
