/* =====================================================================
   base.css — reset + full-page canvas + hero + loader + states.
   V2 delta vs starter kit: #hero-canvas is FIXED and spans the whole
   scroll journey (the particle universe is the site's spine); chapter
   sections scroll over it. Mobile / reduced-motion never boot WebGL —
   chapters show static posters instead (styled in sections.css).
   ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
/* NO scroll-behavior:smooth — it hijacks the loop-wrap's instant teleport and
   fights ScrollTrigger snap on the native path (gate fix R1, W3D-X1). Motion
   smoothness is owned by ScrollSmoother/GSAP. */
/* overflow-x: clip on BOTH html and body clamps the mobile layout viewport to
   device-width (kills the "loads zoomed-in" bug) WITHOUT breaking .ch-sticky
   pinning. Note: body must be `clip`, NOT `hidden` — `hidden` makes body a
   scroll container and de-sticks the chapter cards; `clip` does not. */
html { -webkit-text-size-adjust: 100%; overflow-x: clip; }
body {
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}
img, canvas { display: block; max-width: 100%; }
h1, h2, h3 { font-family: var(--font-display); line-height: var(--leading-tight); font-weight: 600; }
a { color: inherit; text-decoration: none; }

.skip {
  position: fixed; top: -100%; left: var(--space-md); z-index: var(--z-loader);
  padding: var(--space-xs) var(--space-md); background: var(--color-action); color: #fff;
  border-radius: var(--radius); transition: top var(--dur-fast);
}
.skip:focus-visible { top: var(--space-md); }

/* ---- The universe canvas: fixed, behind everything, zero CLS ---- */
#hero-canvas {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: var(--z-canvas); display: block; pointer-events: none;
  opacity: 0; transition: opacity 1000ms var(--ease-cinematic); /* eases IN on hero-active — soft poster→universe crossfade, no hard pop */
}

/* ---- Hero (CH1): real DOM text = LCP; canvas is scenery ---- */
.hero { position: relative; min-height: 100dvh; display: flex; align-items: center; }
.hero__poster {
  position: fixed; inset: 0; z-index: var(--z-poster); pointer-events: none;
  background: radial-gradient(120% 120% at 50% 40%, #101427 0%, var(--color-bg) 70%);
  background-size: cover; background-position: center;
  transition: opacity var(--dur-slow) var(--ease-cinematic);
}
/* Interaction-boot crossfade: the live universe eases IN while the poster banner
   eases OUT and the hero copy gives a soft dip-and-settle — one smooth transition
   into the prisms, not a hard cut. */
html.hero-active #hero-canvas { opacity: 1; }
html.hero-active .hero__poster { opacity: 0; transition: opacity 1000ms var(--ease-cinematic); } /* live WebGL took over */
html.hero-active .hero__content { animation: heroSettle 1000ms var(--ease-cinematic); }
@keyframes heroSettle { 0%, 100% { opacity: 1; } 42% { opacity: .55; } }
.hero__content { position: relative; z-index: var(--z-content); }
.hero__title { font-size: var(--fs-h1); max-width: 14ch; letter-spacing: -0.02em; }
.hero__subtext { margin-top: var(--space-md); font-size: var(--fs-lead); color: var(--color-muted); max-width: 46ch; }
.hero__cta { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-top: var(--space-lg); }
.hero__tagline { margin-top: var(--space-lg); font-size: var(--fs-sm); color: var(--color-faint); max-width: 52ch; }

/* Readability scrim between the moving canvas and copy */
.scrim {
  position: fixed; inset: 0; z-index: var(--z-scrim); pointer-events: none;
  background: radial-gradient(70% 70% at 30% 60%, rgba(5,6,11,.5), transparent 72%);
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .85rem 1.5rem; border-radius: 999px; border: 0; cursor: pointer;
  font-family: var(--font-body); font-weight: 600; font-size: var(--fs-base);
  transition: transform var(--dur-fast) var(--ease-cinematic), box-shadow var(--dur-fast), background var(--dur-fast);
}
.btn--primary { background: var(--color-action); color: var(--color-action-contrast); box-shadow: 0 0 0 0 rgba(108,60,240,.45); }
.btn--primary:hover { box-shadow: 0 8px 32px -8px rgba(108,60,240,.65); }
.btn--ghost { background: transparent; border: 1px solid var(--color-line); color: var(--color-ink); }
.btn--ghost:hover { border-color: var(--color-faint); }
.btn:hover { transform: translateY(-2px); }
.btn:focus-visible { outline: 2px solid var(--color-cyan); outline-offset: 3px; }
.btn--block { width: 100%; }

/* ---- Loader (loader.js contract: [data-loader]/[data-loader-pct]/[data-loader-bar]) ---- */
#loader {
  position: fixed; inset: 0; z-index: var(--z-loader); overflow: clip;
  display: grid; place-content: center; justify-items: center; gap: var(--space-md);
  background: var(--color-bg); color: var(--color-ink);
  transition: transform var(--dur-slow) var(--ease-cinematic);
}
#loader[data-loader-state="done"] { transform: translateY(-101%); pointer-events: none; }
.loader__logo { width: 64px; height: auto; animation: loader-breathe 2.4s ease-in-out infinite; }
.loader__pct { font-family: var(--font-display); font-size: var(--fs-h2); }
.loader__pct::after { content: "%"; font-size: 0.5em; color: var(--color-faint); margin-left: 2px; }
.loader__bar {
  display: block; width: 44vw; max-width: 320px; height: 2px;
  background: var(--gradient-brand); transform-origin: left center; transform: scaleX(0);
  border-radius: 2px; box-shadow: 0 0 14px -2px var(--color-violet);
}

/* ---- Loader delight (04f): the chaos→system story, pre-paint ----
   loader.js feeds --p (0..1 eased progress) onto #loader each frame; every
   shard flies its OWN scattered offset (--sx/--sy px, --sr deg — inline in
   the markup) linearly down to zero, so all eight crystals land ON the
   logo exactly at 100%. Transform-only + one shared keyframe: cheap enough
   for the pre-bundle phase. Reduced motion never sees any of it (the
   whole #loader is display:none below). */
.loader__field { position: absolute; inset: 0; pointer-events: none; }
.loader__shard {
  position: absolute; left: 50%; top: calc(50% - 72px); /* the logo's center */
  width: 12px; height: 18px;
  background: linear-gradient(160deg, var(--color-violet), var(--color-blue));
  clip-path: polygon(50% 0, 100% 62%, 50% 100%, 0 62%);
  /* brighten along the flight, then get ABSORBED by the mark: rises
     0.35→1 until ~p=0.8, falls to 0 at p=1 (min() = cheap piecewise) */
  opacity: calc(min(0.35 + var(--p, 0) * 1.1, (1 - var(--p, 0)) * 5));
  filter: drop-shadow(0 0 6px rgba(108, 60, 240, 0.55));
  transform:
    translate(-50%, -50%)
    translate(calc(var(--sx, 0) * (1 - var(--p, 0)) * 1px),
              calc(var(--sy, 0) * (1 - var(--p, 0)) * 1px))
    rotate(calc(var(--sr, 0) * (1 - var(--p, 0)) * 1deg))
    scale(calc(var(--ss, 1) * (0.7 + 0.3 * var(--p, 0))));
}
.loader__shard:nth-child(2n) { background: linear-gradient(200deg, var(--color-blue), var(--color-cyan)); filter: drop-shadow(0 0 6px rgba(0, 212, 255, 0.4)); }
.loader__line {
  min-height: 1.4em; font-size: var(--fs-sm); letter-spacing: 0.05em;
  color: var(--color-muted);
}
.loader__line.is-swap { animation: loader-line 0.5s var(--ease-cinematic); }
@keyframes loader-line { from { opacity: 0; transform: translateY(9px); } }
@keyframes loader-breathe { 50% { transform: scale(1.05); } }

/* ---- Subtle film grain (premium finish; pure CSS, no requests) ---- */
.grain {
  position: fixed; inset: 0; z-index: var(--z-scrim); pointer-events: none; opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");
}

/* ---- Reduced motion: no theatre (W3D-D2) ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  #loader { display: none; }
  .btn { transition: none; }
  .hero__poster, .grain { transition: none; }
}

/* 04d mobile-lite: capable phones progressively upgrade to a LITE universe
   after load, so the canvas is never display:none'd — pre-boot it is a
   transparent empty element over the posters (harmless), and if the lite
   scene proves unsustainable main.js disposes it in code (W3D). */

/* 02c item 9 · Aurora: soft drifting glows BEHIND the universe (z:-1 —
   visible through the transparent canvas). Compositor-only animation. */
body::before {
  content: ""; position: fixed; inset: -20%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(42% 34% at 24% 28%, rgba(108, 60, 240, 0.16), transparent 70%),
    radial-gradient(38% 30% at 78% 66%, rgba(0, 212, 255, 0.10), transparent 70%),
    radial-gradient(30% 26% at 60% 18%, rgba(56, 106, 246, 0.08), transparent 70%);
  animation: aurora 42s ease-in-out infinite alternate;
}
@keyframes aurora {
  to { transform: translate3d(4%, -3%, 0) scale(1.08); filter: hue-rotate(14deg); }
}
@media (prefers-reduced-motion: reduce) { body::before { animation: none; } }
