/* =====================================================================
   effects.css — grain, glow, hero blobs, entrance + scroll animations
   All of it is decorative and switches off under prefers-reduced-motion.
   ===================================================================== */

/* Reading progress bar */
.progress {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
}

/* Film grain overlay */
.grain {
  position: fixed; inset: -25%; z-index: 40; pointer-events: none;
  width: 150%; height: 150%;
  will-change: transform;
  opacity: var(--grain-opacity);
  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='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
  animation: grain 0.9s steps(4) infinite;
}
/* Dark themes want white grain: emit it from the SVG directly rather than filter:invert(1) on a full-screen layer every frame */
[data-theme="dark"] .grain, html[data-palette] .grain {
  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='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@keyframes grain {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-2%, 3%); }
  50%  { transform: translate(3%, -1%); }
  75%  { transform: translate(-1%, -3%); }
  100% { transform: translate(2%, 2%); }
}

/* Cursor glow */
.glow {
  position: fixed; top: 0; left: 0; z-index: 0; pointer-events: none;
  width: 600px; height: 600px; border-radius: 50%;
  background: radial-gradient(circle, var(--glow) 0%, transparent 65%);
  opacity: 0; transition: opacity 0.6s ease;
  will-change: transform;
}
.glow.is-on { opacity: 1; }

/* Hero background blobs */
/* The blobs fade out with a mask rather than an opaque gradient, so the
   cursor glow behind the hero is never clipped at the section boundary. */
.hero-bg {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
}
.blob {
  position: absolute; border-radius: 50%;
  filter: blur(60px);
  will-change: transform;
  transform: translateZ(0);
  animation: drift 20s ease-in-out infinite alternate;
}
.blob-1 { width: 55vw; height: 55vw; max-width: 760px; max-height: 760px; top: -18%; right: -12%; background: var(--blob-1); }
.blob-2 { width: 42vw; height: 42vw; max-width: 600px; max-height: 600px; bottom: -14%; left: -10%; background: var(--blob-2); animation-duration: 26s; animation-delay: -6s; }
.blob-3 { width: 30vw; height: 30vw; max-width: 420px; max-height: 420px; top: 40%; left: 38%; background: var(--blob-3); animation-duration: 32s; animation-delay: -12s; }
@keyframes drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(6%, -9%, 0) scale(1.15); }
}
@media (max-width: 600px) {
  .blob { filter: blur(48px); }
  .blob-3 { display: none; }
  .blob-1 { width: 90vw; height: 90vw; }
  .blob-2 { width: 70vw; height: 70vw; }
}

/* Network field canvas sits above the blobs, below the content */
.hero-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  /* quieter behind the name and tagline on the left, full strength to the right */
  -webkit-mask-image: linear-gradient(100deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.55) 45%, #000 70%);
  mask-image: linear-gradient(100deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.55) 45%, #000 70%);
}
@media (max-width: 899px) { .hero-canvas { -webkit-mask-image: none; mask-image: none; opacity: 0.7; } }

/* Intro wipe */
.intro {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center;
  background: var(--bg);
  animation: intro-out 0.9s cubic-bezier(0.76, 0, 0.24, 1) 1.15s forwards;
}
@keyframes intro-out { to { transform: translateY(-100%); visibility: hidden; } }
.intro-seen .intro { display: none; }
.intro-mark {
  font-family: var(--font-display); font-size: clamp(3rem, 10vw, 6rem); font-weight: 500; letter-spacing: -0.03em;
  color: var(--ink);
  animation: intro-mark 1.1s var(--ease-out) both;
}
.intro-line { position: absolute; left: 50%; bottom: 18%; width: min(240px, 40vw); height: 1px; background: var(--line); transform: translateX(-50%); overflow: hidden; }
.intro-line i { position: absolute; inset: 0; background: var(--accent); transform-origin: left; animation: intro-line 1s var(--ease-out) 0.1s both; }
@keyframes intro-mark { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
@keyframes intro-line { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.has-intro:not(.intro-done) { overflow: hidden; }
/* hold the hero entrance until the wipe has cleared */
.has-intro .hero-word { animation-delay: calc(1.05s + var(--i, 0) * 0.12s); }
.has-intro .hero-anim { animation-delay: calc(var(--d, 0s) + 0.9s); }

/* Custom cursor (desktop only; class is added by JS) */
/* Sits above everything the pointer can reach (the edition switcher is z-index 120) — the native
   cursor is hidden, so this must never disappear behind an overlay */
.cursor { position: fixed; top: 0; left: 0; z-index: 130; pointer-events: none; opacity: 0; transition: opacity 0.3s; mix-blend-mode: difference; }
.cursor.is-on { opacity: 1; }
.cursor-dot, .cursor-ring { position: fixed; top: 0; left: 0; border-radius: 50%; }
.cursor-dot { width: 6px; height: 6px; background: #fff; }
.cursor-ring {
  width: 36px; height: 36px; border: 1px solid rgba(255, 255, 255, 0.9);
  transition: width 0.3s var(--ease), height 0.3s var(--ease), background-color 0.3s, border-color 0.3s;
}
.cursor.is-hover .cursor-ring { width: 56px; height: 56px; background: rgba(255, 255, 255, 0.18); border-color: transparent; }
.cursor.is-text .cursor-ring { width: 24px; height: 24px; }
.cursor.is-down .cursor-ring { width: 28px; height: 28px; }
.has-cursor, .has-cursor a, .has-cursor button, .has-cursor [data-tilt], .has-cursor .skin-switch summary { cursor: none; }
/* The ⌘K palette is a top-layer <dialog>, which paints above the custom cursor: give the native one back there */
.has-cursor .cmdk { cursor: auto; }

/* Section titles rise in one character at a time */
.section-title .split { display: inline-block; overflow: hidden; padding-bottom: 0.12em; margin-bottom: -0.12em; vertical-align: top; }
.section-title .ch { display: inline-block; }
.has-reveal .section-title .ch { transform: translateY(110%); transition: transform 0.9s var(--ease-out); transition-delay: calc(var(--i, 0) * 0.035s); }
.has-reveal .is-visible .section-title .ch { transform: none; }

/* Pillar icons draw themselves in */
.has-reveal .pillar-icon .icon * { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.3s var(--ease-out); transition-delay: calc(0.25s + var(--i, 0) * 0.15s); }
.has-reveal .pillar.is-visible .pillar-icon .icon * { stroke-dashoffset: 0; }

/* Hero entrance — pure CSS keyframes so it never waits on script timing */
.hero-word {
  opacity: 0;
  animation: rise-word 1.1s var(--ease-out) forwards;
  animation-delay: calc(0.15s + var(--i, 0) * 0.12s);
}
.hero-anim {
  opacity: 0;
  animation: rise 0.9s var(--ease-out) forwards;
  animation-delay: var(--d, 0s);
}
@keyframes rise-word {
  from { opacity: 0; transform: translateY(0.35em); }
  to   { opacity: 1; transform: none; }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* Scroll reveal — only hidden once JS adds .has-reveal to <html> */
.has-reveal .reveal {
  opacity: 0; transform: translateY(26px);
  transition: opacity 0.9s var(--ease-out), transform 1s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 0.07s);
}
.has-reveal .reveal.is-visible { opacity: 1; transform: none; }

/* Reduced motion: no decoration, no movement */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .grain, .glow, .cursor, .intro { display: none; }
  .marquee-track { animation: none; }
  .section-title .ch { transform: none !important; }
  .pillar-icon .icon * { stroke-dashoffset: 0 !important; }
  .hero-word, .hero-anim, .reveal { opacity: 1 !important; transform: none !important; }
}

/* Windows High Contrast / forced colours: the custom cursor is drawn with backgrounds, which are stripped,
   while the real pointer is hidden underneath it. Give the pointer back and drop the decorative canvas. */
@media (forced-colors: active) {
  .cursor, .hero-canvas, .grain, .glow { display: none; }
  .has-cursor, .has-cursor a, .has-cursor button, .has-cursor [data-tilt], .has-cursor .skin-switch summary { cursor: auto; }
}