/* Obscura · motion layer
 * Preloader, magic cursor and Lenis smooth-scroll scaffolding.
 * Structure borrowed from Boldz V1.0; every value retuned to Obscura tokens.
 * Loaded after main.css. Nothing here changes palette, type scale or layout.
 */

/* ---------- Lenis smooth scroll ----------
   Lenis eases the NATIVE scroll position — no fixed wrapper, no transformed
   content — so unlike the old ScrollSmoother setup nothing here is required for
   the page to scroll: with JS off, or before motion.js runs, the page scrolls
   normally and Lenis layers on top. Lenis adds the `lenis`/`lenis-smooth`
   classes to <html> itself; these rules are its recommended baseline.
   #smooth-wrapper / #smooth-content are now inert nesting divs (kept only so no
   template has to change); they carry no positioning or transform. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
/* A scrolling iframe would otherwise capture the wheel and desync Lenis. */
.lenis.lenis-smooth iframe { pointer-events: none; }

/* The ken-burns cover images (hero + cta) and the stats background keep their
   own compositor layer so their CSS zoom animation composites cleanly. Harmless
   under Lenis — there is no wrapper transform to fight — and it keeps the zoom
   crisp. */
body.motion-on .hero__media img,
body.motion-on .cta__media img,
body.motion-on .stats__bg img {
  will-change: transform;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}

/* ---------- Preloader ---------- */
.preloader-wrap {
  position: fixed;
  inset: 0;
  z-index: 1024;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: var(--color-paper);
  /* Failsafe. The curtain is a full-screen opaque layer at z-index 1024; if
     the JS that dismisses it never runs, the site is a blank page with no
     recovery. This lifts it unconditionally after 6s — long past the ~1.2s
     the intro actually needs, so it never fires on a healthy load. */
  animation: preloaderFailsafe var(--dur-slow, 600ms) linear 6s forwards;
}
@keyframes preloaderFailsafe {
  to { opacity: 0; visibility: hidden; pointer-events: none; }
}
/* The two durations must stay equal: visibility flips at the end of the fade,
   so a shorter delay snaps the curtain away mid-dissolve. */
.preloader-wrap.is-done {
  opacity: 0;
  visibility: hidden;
  /* pointer-events drops immediately, not at the end of the fade. The curtain
     stays hit-testable for the whole 1.2s dissolve otherwise, so the page looks
     ready while silently swallowing every click and tap. */
  pointer-events: none;
  transition: opacity 1.2s var(--ease-out), visibility 0s linear 1.2s;
}

/* Already seen this session — page-to-page navigation shows no curtain at all.
   display:none rather than a fast fade: any visible curtain between two pages
   of the same site reads as a stall, not as polish. */
.intro-seen .preloader-wrap { display: none; }

.preloader-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xl);
  padding-inline: var(--gutter);
}

.preloader-content .logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 9vw, var(--text-display-s));
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  text-transform: uppercase;
  color: var(--color-ink);
}
/* Capped in rem as well as em: the wordmark reaches 5rem on a wide screen and
   a mark scaled from that would fill half the curtain. */
.preloader-content .logo__mark { width: auto; height: min(1em, 5rem); }
/* The tracking above is set for the uppercase display face; on the last letter
   it adds a trailing gap that pushes the centred word visibly off-centre. */
.preloader-content .logo__word { margin-right: calc(var(--tracking-display) * -1); }
.preloader-content .logo__word span { color: var(--color-accent); }

.preloader-content .preloader-sub {
  font-family: var(--font-script);
  font-size: var(--text-xl);
  color: var(--color-script);
  letter-spacing: 0;
  text-transform: none;
}

.preloader-bar-wrap {
  position: relative;
  overflow: hidden;
  width: 100%;
  max-width: 618px;
  height: 2px;
  background: var(--color-line);
}
.preloader-bar-wrap .preloader-bar {
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    var(--color-accent)
  );
  animation: preLoader 2.4s infinite linear;
}

@keyframes preLoader {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(0%); }
}

/* ---------- Magic cursor ---------- */
/* Pointer-guarded: a follow-cursor is meaningless on touch. */
#magic-cursor { display: none; }

@media (hover: hover) and (pointer: fine) {
  #magic-cursor {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    width: 30px;
    height: 30px;
    pointer-events: none;
    z-index: 10000;
    transition: opacity var(--dur-fast) var(--ease-out) 0.5s;
  }
  #ball {
    position: fixed;
    top: 0;
    left: 0;
    width: 25px;
    height: 25px;
    /* Centre the ring on the pointer: JS parks #ball's top-left on (clientX,
       clientY), so pull it back half its own size (25/2) on each axis. */
    margin: -12.5px 0 0 -12.5px;
    box-sizing: border-box;
    border: var(--rule-hair) solid var(--color-accent);
    border-radius: 50%;
    opacity: 0;
    pointer-events: none;
    mix-blend-mode: difference;
  }
  #ball.hovered {
    opacity: 0 !important;
    transition: opacity var(--dur-mid) var(--ease-out);
  }
}

/* ---------- Scroll animation safety net ---------- */
/* If GSAP never boots, animated blocks must still be visible. */
.no-js .scroll-animation,
body.motion-off .scroll-animation {
  opacity: 1 !important;
  transform: none !important;
}

/* Same safety net for the curtain, which was missing it.
   .preloader-wrap is position:fixed over the whole viewport and is dismissed
   only by motion.js on window load. With JavaScript unavailable that never
   happens, so the curtain stayed up permanently and swallowed every click on
   the site underneath — the page looked loaded but nothing could be pressed.
   The inline script in includes/header.php strips .no-js before first paint,
   so this costs the normal path nothing. */
.no-js .preloader-wrap { display: none; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .preloader-bar-wrap .preloader-bar { animation: none; transform: none; }
  #magic-cursor { display: none; }
  .scroll-animation { opacity: 1 !important; transform: none !important; }
}
