/* ==========================================================================
   Preloader
   A spin up gate on the home page. Loaded last so it can sit above every
   other layer without fighting specificity.

   Contract with the rest of the site
   ----------------------------------
   - Nothing here applies until <html> carries `is-preloading`. The inline
     script in head.html adds that class before first paint, and preloader.js
     removes it on exit. No class, no overlay, so a JavaScript failure cannot
     leave a visitor staring at a locked page.
   - `.no-js` never shows it at all.

   Two custom properties drive almost everything, both written by preloader.js:

     --d   demand, 0 to 1. What the visitor has dialled in. Updated the instant
           they move, so the knob and the fill track the finger with no lag.
     --p   actual, 0 to 1. What the motor has actually reached. Lags --d, which
           is what makes the readout and the blur feel like a machine spooling
           up rather than a value being set.
   ========================================================================== */

/* --- Page level state ----------------------------------------------------- */

/* Scroll lock while the gate is up. Both elements, because iOS honours one
   and not the other depending on version. */
html.is-preloading,
html.is-preloading body {
  overflow: hidden;
  overscroll-behavior: none;
}

/* While the gate is armed, nothing but the overlay may show.

   This matters most in the offline single file, where <main> starts empty and
   is filled by the hash router a moment after the boot script has already
   locked scrolling. Without this the header, footer and floating button would
   paint for a frame before the overlay mounted over them. The overlay itself
   lives inside <main>, so it opts back in: visibility inherits and can be
   turned on again by a descendant, which display cannot. */
html.is-preloading body { background: var(--ink-1000); }
html.is-preloading body > * { visibility: hidden; }
html.is-preloading .preloader { visibility: visible; }

/* The hero runs its own entrance animation on load. Hold it at frame zero
   while the preloader is up so it plays properly once the gate lifts, rather
   than finishing unseen behind the overlay. */
html.is-preloading .hero *,
html.is-preloading .hero {
  animation-play-state: paused !important;
}

/* The zoom through. The preloader scales up and fades while the hero settles
   back from a slight overscale, so the two moves read as one camera push. */
html.is-preloading-out .hero {
  animation: preloader-hero-in 900ms var(--ease-out) both;
}

@keyframes preloader-hero-in {
  from { opacity: 0; transform: scale(1.055); }
  to   { opacity: 1; transform: none; }
}

/* --- Shell ---------------------------------------------------------------- */

.preloader {
  --fan-size: clamp(12rem, 8rem + 20vmin, 20rem);
  --meter-size: clamp(5rem, 4rem + 4vmin, 7rem);
  --d: 0;
  --p: 0;
  --blur: 0;
  --buffered: 0;
  --arc: #b8894a;

  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  place-items: center;
  padding: clamp(1rem, 0.5rem + 2vw, 2.5rem);
  color: var(--text-on-dark);
  background: var(--ink-1000);
  /* Fence off layout and paint so the spinning fan cannot invalidate anything
     in the page underneath. Size containment is deliberately left out: the
     grid still needs to measure its own content. */
  contain: layout paint style;
  transform-origin: 50% 46%;
}

html.is-preloading .preloader { display: grid; }
html.no-js .preloader { display: none !important; }

/* The overlay takes focus on open so the trap has somewhere to start, but it
   is a container rather than a control and should not draw a focus ring. */
.preloader:focus { outline: none; }

/* Exit: push through the fan and hand over to the hero. */
html.is-preloading-out .preloader {
  animation: preloader-out 900ms var(--ease-out) both;
  pointer-events: none;
}

@keyframes preloader-out {
  from { opacity: 1; transform: scale(1); }
  60%  { opacity: 0.42; }
  to   { opacity: 0; transform: scale(1.5); }
}

/* --- Backdrop ------------------------------------------------------------- */

.preloader__bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.preloader__jaali {
  position: absolute;
  inset: 0;
  background-image: var(--pattern-jaali-dark);
  background-size: 72px 72px;
  opacity: 0.55;
  /* Fade the lattice away from the middle so nothing sits behind the fan. */
  mask-image: radial-gradient(circle at 50% 46%, transparent 0%, transparent 24%, #000 66%);
}

/* Warms and brightens as the fan spins up. */
.preloader__glow {
  position: absolute;
  left: 50%;
  top: 46%;
  width: min(120vmin, 60rem);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(193, 37, 32, 0.22) 0%,
    rgba(156, 28, 24, 0.10) 36%,
    transparent 68%
  );
  opacity: calc(0.32 + var(--blur) * 0.68);
  transition: opacity 180ms linear;
}

.preloader__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 46%, transparent 40%, rgba(8, 10, 13, 0.85) 100%);
}

/* --- Layout --------------------------------------------------------------- */

.preloader__inner {
  position: relative;
  display: grid;
  justify-items: center;
  gap: clamp(0.85rem, 0.5rem + 1.5vmin, 1.7rem);
  width: min(100%, 34rem);
  text-align: center;
}

.preloader__eyebrow {
  font-family: var(--font-technical);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--brass-400);
}

.preloader__title {
  margin-top: 0.5rem;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.15rem, 0.95rem + 1.1vmin, 1.6rem);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
}

/* --- Fan stage ------------------------------------------------------------ */

.preloader__stage {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--fan-size);
  height: var(--fan-size);
}

.preloader__particles {
  position: absolute;
  inset: -18%;
  width: 136%;
  height: 136%;
  pointer-events: none;
}

/* Viewed face on, matching the brand's coming soon artwork. No tilt, no
   downrod: this reads as a fan symbol rather than a ceiling fixture. */
.fan {
  position: relative;
  width: 100%;
  height: 100%;
  /* A slow camera push that tracks speed, so the stage tightens as it spools. */
  scale: calc(1 + var(--blur) * 0.04);
}

/* The glowing rim the blades turn inside. */
.fan__ring {
  position: absolute;
  inset: 2%;
  border-radius: 50%;
  border: 1px solid rgba(238, 28, 37, calc(0.28 + var(--blur) * 0.45));
  box-shadow:
    0 0 calc(1.5rem + var(--blur) * 2.5rem) rgba(238, 28, 37, calc(0.18 + var(--blur) * 0.3)),
    inset 0 0 3rem rgba(238, 28, 37, calc(0.06 + var(--blur) * 0.16));
}

/* --- Rotor and blades ----------------------------------------------------- */

/* Rotation is applied by the Web Animations API, not by a per frame style
   write, so it runs on the compositor and cannot be stuttered by a busy main
   thread. `will-change` keeps the layer promoted for the whole run. */
.fan__rotor {
  position: absolute;
  inset: 0;
  will-change: transform;
}

/* Ghost rotors are clones trailing behind the real one. Together they read as
   motion blur far more cheaply than an actual blur filter, which would cost a
   full screen repaint every frame. */
.fan__rotor--ghost { pointer-events: none; }

.fan__arm {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Short of the rim on purpose: the blades should turn inside the ring with
     daylight around the tips, not scrape it. */
  width: 42%;
  height: 18%;
  margin-top: -9%;
  transform-origin: 0 50%;
}

.fan__arm:nth-child(1) { transform: rotate(0deg); }
.fan__arm:nth-child(2) { transform: rotate(120deg); }
.fan__arm:nth-child(3) { transform: rotate(240deg); }

/* A swept petal: red along the leading edge, falling away to near black at the
   trailing edge. The slight rotation inside the arm is what gives the sweep. */
.fan__blade {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  rotate: -7deg;
  background: linear-gradient(
    174deg,
    var(--red-300) 0%,
    var(--red-400) 18%,
    var(--red-600) 38%,
    var(--red-700) 52%,
    var(--ink-700) 76%,
    var(--ink-900) 100%
  );
  box-shadow: 0 0 1.1rem rgba(238, 28, 37, 0.22);
}

/* A single specular streak down the leading edge so the blade catches light. */
.fan__blade::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    174deg,
    rgba(255, 255, 255, 0.34) 0%,
    rgba(255, 255, 255, 0.06) 14%,
    transparent 34%,
    transparent 72%,
    rgba(8, 10, 13, 0.4) 100%
  );
}

/* --- Blur disc ------------------------------------------------------------ */

/* At speed a fan stops being three blades and becomes a translucent disc.
   Opacity is driven from --blur, so it arrives exactly as the blades dissolve. */
.fan__disc {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 94%;
  height: 94%;
  translate: -50% -50%;
  border-radius: 50%;
  opacity: calc(var(--blur) * 0.55);
  background:
    conic-gradient(
      from 0deg,
      rgba(193, 37, 32, 0.00) 0deg,
      rgba(193, 37, 32, 0.20) 40deg,
      rgba(193, 37, 32, 0.00) 120deg,
      rgba(193, 37, 32, 0.20) 160deg,
      rgba(193, 37, 32, 0.00) 240deg,
      rgba(193, 37, 32, 0.20) 280deg,
      rgba(193, 37, 32, 0.00) 360deg
    ),
    radial-gradient(
      circle,
      transparent 10%,
      rgba(156, 28, 24, 0.30) 20%,
      rgba(52, 60, 70, 0.30) 72%,
      transparent 84%
    );
  will-change: opacity;
}

/* --- Hub ------------------------------------------------------------------ */

.fan__hub {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11%;
  height: 11%;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--ink-400), var(--ink-900) 72%);
  box-shadow:
    0 0 0 1px rgba(244, 246, 249, 0.10),
    0 0.5rem 1.4rem rgba(8, 10, 13, 0.8);
}

/* --- Meter, readout only -------------------------------------------------- */

.meter {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--meter-size);
  height: var(--meter-size);
}

.meter__dial {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Rotate so the 270 degree sweep opens at the bottom left. */
  transform: rotate(135deg);
  overflow: visible;
}

.meter__track,
.meter__value {
  fill: none;
  stroke-width: 7;
  stroke-linecap: round;
  /* 2 * pi * 86 = 540.35. Three quarters of that is the visible sweep. */
  stroke-dasharray: 405.3 540.4;
}

.meter__track { stroke: rgba(244, 246, 249, 0.10); }

.meter__value {
  stroke: var(--arc);
  stroke-dasharray: calc(var(--p) * 405.3) 540.4;
  filter: drop-shadow(0 0 5px rgba(238, 28, 37, 0.45));
}

.meter__readout {
  display: grid;
  justify-items: center;
  gap: 0.05rem;
}

.meter__number {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.05rem, 0.85rem + 1.2vmin, 1.5rem);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  /* Slashed tabular zeros, so the number does not jitter as it counts. */
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--text-on-dark);
}

.meter__unit {
  font-family: var(--font-technical);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
}

/* --- Track, the control --------------------------------------------------- */

.preloader__controls {
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  width: 100%;
}

.track {
  position: relative;
  width: min(100%, 22rem);
  /* Generous height for a comfortable thumb target; the visible rail is thin
     and centred inside it. */
  height: 2.5rem;
  cursor: grab;
  /* Stops the browser scrolling the page when the track is dragged on touch. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.track.is-dragging { cursor: grabbing; }

.track:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.track__rail,
.track__buffer,
.track__fill {
  position: absolute;
  top: 50%;
  left: 0;
  height: 6px;
  translate: 0 -50%;
  border-radius: var(--radius-pill);
  pointer-events: none;
}

.track__rail {
  right: 0;
  background: rgba(244, 246, 249, 0.10);
}

/* Asset loading, shown the way a video scrubber shows buffered against played. */
.track__buffer {
  width: calc(var(--buffered) * 100%);
  background: rgba(244, 246, 249, 0.20);
  transition: width 320ms var(--ease-out);
}

/* Demand, not actual speed: the fill must track the finger with no lag. */
.track__fill {
  width: calc(var(--d) * 100%);
  background: linear-gradient(90deg, var(--brass-500), var(--red-400));
  box-shadow: 0 0 14px rgba(238, 28, 37, 0.45);
}

.track__knob {
  position: absolute;
  top: 50%;
  left: calc(var(--d) * 100%);
  width: 1.15rem;
  height: 1.15rem;
  translate: -50% -50%;
  border-radius: 50%;
  background: #fff;
  box-shadow:
    0 0 0 1px rgba(8, 10, 13, 0.45),
    0 2px 10px rgba(8, 10, 13, 0.65),
    0 0 18px rgba(238, 28, 37, calc(0.25 + var(--blur) * 0.5));
  pointer-events: none;
  transition: scale 160ms var(--ease-out);
}

.track.is-dragging .track__knob { scale: 1.18; }

.preloader__hint {
  min-height: 1.2em;
  font-family: var(--font-technical);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(244, 246, 249, 0.45);
  transition: opacity var(--dur) var(--ease-out);
}

.preloader__hint.is-hidden { opacity: 0; }

.preloader__status {
  font-family: var(--font-technical);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
}

.preloader__status.is-ready { color: var(--brass-400); }

/* --- Actions -------------------------------------------------------------- */

.preloader__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.preloader__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.95rem;
  font-family: var(--font-technical);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
  background: rgba(244, 246, 249, 0.05);
  border: 1px solid rgba(244, 246, 249, 0.14);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition:
    color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.preloader__btn:hover,
.preloader__btn:focus-visible {
  color: var(--text-on-dark);
  border-color: rgba(244, 246, 249, 0.34);
  background: rgba(244, 246, 249, 0.09);
}

.preloader__btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

.preloader__btn[aria-pressed='true'] {
  color: var(--brass-400);
  border-color: var(--brass-line);
}

/* Three bars that only animate while sound is actually on. */
.preloader__btn-icon {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  height: 0.75rem;
}

.preloader__wave {
  width: 2px;
  height: 34%;
  border-radius: 1px;
  background: currentColor;
}

.preloader__btn[aria-pressed='true'] .preloader__wave {
  animation: preloader-wave 900ms var(--ease-in-out) infinite;
}

.preloader__btn[aria-pressed='true'] .preloader__wave:nth-child(2) { animation-delay: 150ms; }
.preloader__btn[aria-pressed='true'] .preloader__wave:nth-child(3) { animation-delay: 300ms; }

@keyframes preloader-wave {
  0%, 100% { height: 34%; }
  50%      { height: 100%; }
}

/* --- Responsive ----------------------------------------------------------- */

/* Landscape phones and short windows: drop the headline and tighten the stack
   so the track and the actions stay reachable without scrolling. */
@media (max-height: 33rem) {
  .preloader {
    --fan-size: clamp(5.5rem, 20vh, 8rem);
    --meter-size: 3.75rem;
    padding: 0.6rem;
  }
  .preloader__title { display: none; }
  .preloader__inner { gap: 0.45rem; }
  .track { height: 2rem; }
  /* The hint stays: it is the only thing telling anyone the track is a
     control. The fan gives up the space instead. */
}

@media (max-width: 24rem) {
  .preloader__actions { width: 100%; }
  .preloader__btn { flex: 1 1 auto; justify-content: center; }
}

/* --- Reduced motion ------------------------------------------------------- */

/* preloader.js skips the gate entirely when reduced motion is requested, so
   this is a belt and braces stop in case the overlay is ever forced up. */
@media (prefers-reduced-motion: reduce) {
  html.is-preloading .preloader,
  html.is-preloading-out .preloader,
  html.is-preloading-out .hero {
    animation: none !important;
  }
  .preloader__wave { animation: none !important; }
  .fan__disc { opacity: 0.3; }
}
