/* ============================================================
   the sad red ball — design system
   palette: a cold grey-blue world, one red that refuses to leave
   ============================================================ */

:root{
  --ink:#15181b;            /* primary text — soft charcoal on light */
  --paper:#e9ecec;          /* the luminous world, start to finish  */
  --mist:#586269;           /* secondary text on light              */
  --haze:#cfd4d4;
  --ember:#df241d;          /* the ball — sampled red   */
  --ember-soft:#ff5b46;
  --red:#b21712;            /* the word "red", everywhere */
  --halo:0 1px 2px rgba(255,255,255,.55),0 0 22px rgba(233,236,236,.9),0 0 52px rgba(233,236,236,.6);
  --text:#15181b;

  --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Cormorant Garamond",
          Georgia,"Times New Roman",serif;
  --sans:"Inter","Helvetica Neue",-apple-system,BlinkMacSystemFont,
          "Segoe UI",system-ui,sans-serif;

  --ease:cubic-bezier(.22,1,.36,1);
  --ease-soft:cubic-bezier(.4,0,.1,1);
}

*{margin:0;padding:0;box-sizing:border-box}
html{ -webkit-text-size-adjust:100%; }
body{
  background:var(--paper);
  color:var(--text);
  font-family:var(--sans);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
  cursor:default;
  -webkit-tap-highlight-color:transparent;
}
::selection{ background:var(--ember); color:#fff; }

/* ---------- the fixed cinematic stage ---------- */
.stage{
  position:fixed; inset:0;
  width:100%; height:100%;
  overflow:hidden;
  background:radial-gradient(120% 120% at 50% 40%,#d6dadb 0%,#c4cacb 55%,#b3babc 100%);
  z-index:1;
}
#gl{ position:absolute; inset:0; width:100%; height:100%; display:block; }

/* scroll length only — invisible, drives "time" */
.scroll{ position:relative; height:1780vh; z-index:2; pointer-events:none; }

/* The real, readable poem (screen-reader / no-JS / search) */
.reader{
  position:absolute; left:-99999px; top:0;
  width:1px; height:1px; overflow:hidden;
}

/* ============================================================
   Kinetic captions
   ============================================================ */
.captions{
  position:absolute; inset:0;
  display:grid; place-items:center;
  pointer-events:none;
  z-index:3;
  padding:7vw;
  text-align:center;
}
.beat{
  position:absolute;
  max-width:min(1100px,86vw);
  opacity:0;
  will-change:opacity,transform,filter;
}
.beat p{
  font-family:var(--serif);
  font-weight:400;
  font-size:clamp(1.1rem,2.55vw,1.95rem);   /* a refined film caption, never a billboard */
  line-height:1.34;
  letter-spacing:.006em;
  color:var(--ink);
  text-wrap:balance;
  text-shadow:var(--halo);
}
/* a soft light breath only behind lines that fall over the footage */
.beat[data-beat^="l"]::before,
.beat[data-beat="c4"]::before,
.beat[data-beat="in2"]::before,
.beat[data-beat="s2"]::before,
.beat[data-beat="heal"]::before{
  content:""; position:absolute; inset:-.9em -2.4em; z-index:-1;
  background:radial-gradient(ellipse at center,
    rgba(236,239,239,.56) 0%,rgba(236,239,239,.30) 50%,transparent 78%);
  filter:blur(22px); pointer-events:none;
}
.beat p.soft{ color:rgba(21,24,27,.56); font-style:italic; }
.beat p.ember{
  color:#ec3326;
  text-shadow:0 0 26px rgba(226,30,22,.7),0 0 64px rgba(190,16,12,.5),0 2px 30px rgba(0,0,0,.5);
}
/* "the grey came all the way in" — dark, so it reads as the screen blanches */
.beat[data-beat="e1"] p{
  color:#0e1114;
  text-shadow:0 1px 24px rgba(255,255,255,.6),0 0 56px rgba(255,255,255,.42);
}
.stanza p + p{ margin-top:.18em; }

/* Title */
.title .kicker{
  font-family:var(--sans);
  font-size:clamp(.66rem,1.5vw,.86rem);
  letter-spacing:.46em;
  text-transform:uppercase;
  color:var(--mist);
  margin-bottom:clamp(1.4rem,4vw,2.6rem);
  padding-left:.46em;
  text-shadow:0 0 16px rgba(233,236,236,.8);
}
/* a hand-struck correction — used in the kicker and in the closing title */
.strike{ position:relative; }
.strike::after{
  content:""; position:absolute;
  left:-.12em; right:-.04em; top:52%;
  height:max(2px,.072em); border-radius:3px;
  background:var(--red);
  box-shadow:0 0 8px rgba(178,23,18,.6);
  transform:translateY(-50%) rotate(-2.2deg);
  pointer-events:none;
}
.corr{ margin-left:.42em; }
.title .kicker .strike{ color:var(--mist); }
.title .kicker .corr{ color:var(--ink); margin-left:.55em; }
.colophon h2 .strike,.colophon h2 .corr{ color:inherit; }
.colophon h2 .corr{ margin-left:0; }   /* just one space between "sad" and "happy" */
/* the word "red" is always the ball's red, wherever it appears */
.rd{ color:var(--red); }
.beat .rd{ text-shadow:0 1px 1px rgba(255,255,255,.5),0 0 18px rgba(178,23,18,.45); }
.title h1{
  font-family:var(--serif);
  font-weight:400;
  font-size:clamp(2.9rem,11.5vw,10rem);
  line-height:.96;
  letter-spacing:-.015em;
  color:var(--ink);
  text-shadow:0 1px 1px rgba(255,255,255,.4);
}
.title h1 span{ display:inline; }
.title h1 .rd{
  color:var(--red);                      /* the ball's red */
  text-shadow:0 1px 2px rgba(255,255,255,.45),0 0 30px rgba(178,23,18,.4);
}
.title .byline{
  font-family:var(--sans);
  font-size:clamp(.7rem,1.5vw,.84rem);
  letter-spacing:.34em;
  text-transform:uppercase;
  color:var(--mist);
  margin-top:clamp(1rem,3vw,1.9rem);
  text-shadow:0 0 16px rgba(233,236,236,.8);
}
.title .howto{
  font-family:var(--sans);
  font-size:clamp(.68rem,1.15vw,.8rem);
  line-height:1.75;
  letter-spacing:.03em;
  color:rgba(88,98,105,.82);
  max-width:34em;
  margin:clamp(1rem,2.6vw,1.6rem) auto 0;
  text-shadow:0 0 14px rgba(233,236,236,.85);
}

/* Colophon */
.colophon .resolve{
  font-family:var(--serif);
  font-size:clamp(1.5rem,3.8vw,3.1rem);
  line-height:1.32;
  color:var(--ink);
  text-wrap:balance;
  text-shadow:var(--halo);
  margin-bottom:clamp(1.6rem,4.5vw,2.8rem);
}
.colophon h2{
  font-family:var(--serif);
  font-weight:400; font-style:italic;
  font-size:clamp(2.4rem,7.4vw,5.4rem);
  line-height:1.04;
  color:var(--ink);
  letter-spacing:-.018em;
  text-shadow:0 1px 2px rgba(255,255,255,.55),0 0 26px rgba(233,236,236,.85);
  opacity:1;
}
.colophon .caption{
  font-family:var(--sans);
  font-size:clamp(.66rem,1.4vw,.8rem);
  letter-spacing:.32em;
  text-transform:uppercase;
  color:var(--mist);
  margin-top:1.6rem;
  text-shadow:0 0 16px rgba(233,236,236,.8);
}
#replay{
  margin-top:2.6rem;
  pointer-events:auto;
  background:rgba(255,255,255,.28); border:1px solid rgba(21,24,27,.28);
  color:var(--ink);
  font-family:var(--sans);
  font-size:.74rem; letter-spacing:.22em; text-transform:uppercase;
  padding:1.05em 2.1em; border-radius:999px;
  cursor:pointer; -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px);
  transition:border-color .5s var(--ease),background .5s var(--ease),color .5s var(--ease);
}
#replay:hover{ border-color:var(--red); color:var(--red); background:rgba(255,255,255,.5); }

/* the close: a smooth, self-timed staggered roll-in (decoupled from scroll) */
.colophon{ opacity:1; }
.colophon .resolve,
.colophon h2,
.colophon .caption,
.colophon #replay{
  opacity:0; transform:translateY(26px);
  transition:opacity 1s var(--ease), transform 1.35s var(--ease);
}
.colophon #replay{
  pointer-events:none;
  transition:opacity 1s var(--ease), transform 1.35s var(--ease),
             border-color .5s var(--ease), background .5s var(--ease), color .5s var(--ease);
}
.colophon.show .resolve,
.colophon.show h2,
.colophon.show .caption,
.colophon.show #replay{ opacity:1; transform:none; }
.colophon.show #replay{ pointer-events:auto; }
.colophon.show .resolve{ transition-delay:.15s; }
.colophon.show h2{ transition-delay:.62s; }
.colophon.show .caption{ transition-delay:1.12s; }
.colophon.show #replay{ transition-delay:1.46s; }
@media (prefers-reduced-motion:reduce){
  .colophon .resolve,.colophon h2,.colophon .caption,.colophon #replay{
    transition-duration:.2s; transition-delay:0s; transform:none;
  }
}

/* ============================================================
   UI furniture
   ============================================================ */
.ui{ position:absolute; inset:0; z-index:4; pointer-events:none; }

/* scrub progress — a hairline filling red */
.progress{
  position:absolute; right:0; top:0; bottom:0;
  width:2px; background:rgba(20,24,28,.08);
}
.progress span{
  position:absolute; left:0; top:0; width:100%; height:0%;
  background:linear-gradient(to bottom,var(--ember),var(--ember-soft));
  box-shadow:0 0 14px rgba(223,36,29,.6);
}

/* chapter dots — a quiet thread through the poem; the live one is the ball */
.chapters{
  position:absolute; left:max(26px,2.6vw); top:50%;
  transform:translateY(-50%);
  display:flex; flex-direction:column; gap:11px;
  pointer-events:auto;
}
.chapters::before{                       /* the through-line */
  content:""; position:absolute; left:50%; top:-3px; bottom:-3px;
  width:1px; transform:translateX(-50%);
  background:linear-gradient(to bottom,
    transparent, rgba(20,24,28,.13) 14%, rgba(20,24,28,.13) 86%, transparent);
  pointer-events:none;
}
.chapters button{
  position:relative; width:9px; height:9px; padding:0; border:0;
  border-radius:50%; cursor:pointer; background:transparent;
  box-shadow:inset 0 0 0 1px rgba(20,24,28,.32);       /* still to come — a hollow ring */
  transition:transform .5s var(--ease),background .5s var(--ease),
             box-shadow .5s var(--ease);
}
.chapters button::after{                 /* generous, invisible tap target */
  content:""; position:absolute; inset:-8px;
}
.chapters button:hover{
  transform:scale(1.55);
  box-shadow:inset 0 0 0 1px var(--red),0 0 10px rgba(223,36,29,.35);
}
.chapters button.done{                   /* a line already read — softly filled */
  background:rgba(20,24,28,.40);
  box-shadow:inset 0 0 0 1px transparent;
}
.chapters button.on{                     /* the station the ball is resting at */
  background:rgba(178,23,18,.20);
  box-shadow:inset 0 0 0 1px rgba(178,23,18,.55);
}
/* the little red ball, rolling the path between the stations */
.chapters .pathball{
  position:absolute; left:50%; top:0;
  width:14px; height:14px; border-radius:50%;
  background:radial-gradient(circle at 36% 32%,
    #ff8164 0%, var(--ember) 45%, #aa1410 100%);
  box-shadow:0 0 0 1px rgba(170,20,16,.55),0 0 14px 3px rgba(223,36,29,.65);
  transform:translate(-50%,-50%);
  pointer-events:none; z-index:2; will-change:top,transform;
  animation:pathglow 2.8s var(--ease-soft) infinite;
}
@keyframes pathglow{
  0%,100%{ box-shadow:0 0 0 1px rgba(170,20,16,.55),0 0 11px 2px rgba(223,36,29,.5); }
  50%    { box-shadow:0 0 0 1px rgba(170,20,16,.55),0 0 18px 5px rgba(223,36,29,.8); }
}
@media (prefers-reduced-motion:reduce){
  .chapters .pathball{ animation:none; }
}

/* scroll cue */
.scrollcue{
  position:absolute; left:50%; bottom:34px; transform:translateX(-50%);
  width:1px; height:54px; overflow:hidden;
  background:rgba(20,24,28,.14);
  transition:opacity .8s var(--ease);
}
.scrollcue span{
  position:absolute; left:0; top:-60%;
  width:100%; height:60%;
  background:linear-gradient(to bottom,transparent,rgba(21,24,27,.65));
  animation:drip 2.4s var(--ease-soft) infinite;
}
@keyframes drip{
  0%{ transform:translateY(0); }
  60%{ transform:translateY(220%); }
  100%{ transform:translateY(220%); }
}

/* ============================================================
   About — one quiet link, and a proper colophon
   ============================================================ */
.about{
  position:absolute; right:max(22px,3vw); bottom:max(20px,2.6vw);
  z-index:5; pointer-events:auto;
  background:none; border:0; cursor:pointer;
  font-family:var(--sans);
  font-size:.68rem; letter-spacing:.32em; text-transform:uppercase;
  color:var(--ink); opacity:.6;
  transition:opacity .5s var(--ease),color .5s var(--ease);
}
.about:hover,.about:focus-visible{ opacity:1; color:var(--red); outline:none; }

.about-panel{
  position:fixed; inset:0; z-index:60;
  display:grid; place-items:center;
  padding:6vw;
  background:rgba(226,229,229,.74);
  -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px);
  animation:aboutIn .45s var(--ease);
}
.about-panel[hidden]{ display:none; }
@keyframes aboutIn{ from{ opacity:0 } to{ opacity:1 } }
.about-card{
  position:relative;
  width:100%; max-width:640px; max-height:86vh; overflow-y:auto;
  background:rgba(249,250,250,.92);
  border:1px solid rgba(21,24,27,.10);
  border-radius:18px;
  padding:clamp(26px,5vw,52px);
  box-shadow:0 30px 80px -28px rgba(20,24,28,.45);
  color:var(--ink);
  -webkit-overflow-scrolling:touch;
}
.about-card h2{
  font-family:var(--serif); font-style:italic; font-weight:400;
  font-size:clamp(1.7rem,5vw,2.5rem); letter-spacing:-.012em;
}
.about-card .lede{
  font-family:var(--serif);
  font-size:clamp(1.02rem,2.4vw,1.22rem); line-height:1.62;
  color:rgba(21,24,27,.84); margin:.7em 0 1.7em;
  text-wrap:balance;
}
.about-card h3{
  font-family:var(--sans); font-size:.64rem; letter-spacing:.32em;
  text-transform:uppercase; color:var(--mist); margin-bottom:1.05em;
}
.about-card ul{ list-style:none; display:flex; flex-direction:column; gap:.9em; }
.about-card li{
  font-family:var(--sans); font-size:.92rem; line-height:1.55;
  color:rgba(21,24,27,.78); padding-left:1.15em; position:relative;
}
.about-card li::before{
  content:""; position:absolute; left:0; top:.6em;
  width:6px; height:6px; border-radius:50%; background:var(--red);
}
.about-card li b{ color:var(--ink); font-weight:600; }
.about-card code{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.86em; background:rgba(21,24,27,.07);
  padding:.08em .35em; border-radius:4px;
}
.about-credit{
  margin-top:1.9em; padding-top:1.4em;
  border-top:1px solid rgba(21,24,27,.1);
  font-family:var(--sans); font-size:.86rem; letter-spacing:.03em;
  color:var(--mist);
}
.about-credit a{
  color:var(--red); text-decoration:none;
  border-bottom:1px solid rgba(178,23,18,.35);
  padding-bottom:1px; transition:border-color .4s var(--ease);
}
.about-credit a:hover{ border-color:var(--red); }
.about-close{
  position:absolute; top:14px; right:16px;
  width:38px; height:38px; border-radius:50%;
  display:grid; place-items:center;
  background:none; border:1px solid rgba(21,24,27,.18); color:var(--ink);
  font-size:.9rem; cursor:pointer;
  transition:border-color .4s var(--ease),color .4s var(--ease);
}
.about-close:hover,.about-close:focus-visible{ border-color:var(--red); color:var(--red); outline:none; }
html.about-on, html.about-on body{ overflow:hidden; }

/* ============================================================
   Preloader
   ============================================================ */
.loader{
  position:fixed; inset:0; z-index:50;
  display:grid; place-items:center;
  background:radial-gradient(120% 120% at 50% 42%,#e4e7e7,#cdd2d2);
  transition:opacity 1.1s var(--ease),visibility 1.1s;
}
.loader.gone{ opacity:0; visibility:hidden; }
.loader-stage{ display:flex; flex-direction:column; align-items:center; }
.loader-ball{ width:118px; height:118px; overflow:visible; }
.loader-ball .ring{
  fill:none; stroke:rgba(20,24,28,.22); stroke-width:1;
}
.loader-ball .fill{
  fill:var(--ember);
  transition:none;
}
.loader-text{
  margin-top:30px;
  font-family:var(--serif);
  font-size:1.5rem; color:var(--ink);
  letter-spacing:.02em;
}
.loader-text i{ font-style:normal; color:var(--mist); font-size:.8em; margin-left:2px; }
.loader-sub{
  margin-top:12px;
  font-family:var(--sans);
  font-size:.62rem; letter-spacing:.4em; text-transform:uppercase;
  color:var(--mist);
}

/* ============================================================
   "From the beginning" — the film gently reverses, then a soft
   luminous landing eases the title back in. No flash, no flicker.
   ============================================================ */
.rewind-veil{
  position:fixed; inset:0; z-index:55;
  pointer-events:none; opacity:0;
  background:radial-gradient(135% 135% at 50% 44%,
    rgba(255,255,255,.95) 0%, rgba(233,236,236,.86) 46%, rgba(205,210,210,.80) 100%);
  will-change:opacity;
}
.rewind-veil.active{ pointer-events:auto; }
@media (prefers-reduced-motion:reduce){
  .rewind-veil{ transition:opacity .25s linear; }
}

/* ============================================================
   "sad again" — the cold-grey reverse. A drained fog instead of
   the warm landing, and a rewritten poem that walks back down
   into sadness, line by line, above it.
   ============================================================ */
.rewind-veil.sad{
  background:radial-gradient(135% 135% at 50% 46%,
    rgba(214,219,221,.92) 0%, rgba(186,193,196,.90) 46%, rgba(150,158,162,.88) 100%);
}
.sad-stage{
  position:fixed; inset:0; z-index:58;
  display:grid; place-items:center;
  padding:7vw; text-align:center;
  pointer-events:none; visibility:hidden;
}
.sad-stage.on{ visibility:visible; }
.sad-line{
  position:absolute; left:50%; top:63%; transform:translate(-50%,-50%);
  margin:0; max-width:min(1000px,84vw);
  font-family:var(--serif); font-weight:400; font-style:italic;
  font-size:clamp(1.05rem,2.5vw,1.85rem); line-height:1.36; letter-spacing:.01em;
  color:#5b6266;                                   /* cold, colour-drained ink */
  text-wrap:balance;
  text-shadow:0 1px 2px rgba(255,255,255,.7), 0 0 22px rgba(236,239,239,.6);
  opacity:0; will-change:opacity; pointer-events:none;
}
.sad-stage #replay{
  position:absolute; left:50%; bottom:12%; top:auto; transform:translateX(-50%);
  margin:0; opacity:.32; pointer-events:auto;
  background:rgba(255,255,255,.16); border-color:rgba(21,24,27,.20); color:#5b6266;
}
.sad-stage #replay:hover{
  opacity:.9; border-color:var(--red); color:var(--red); background:rgba(255,255,255,.42);
}
/* while the egg runs, the forward poem & colophon recede quietly */
html.sad-on .beat{ opacity:0 !important; transition:opacity .45s linear; }
html.sad-on .colophon .resolve,
html.sad-on .colophon h2,
html.sad-on .colophon .caption{ opacity:0 !important; }
@media (prefers-reduced-motion:reduce){
  .sad-line{ transition:opacity .25s linear; }
}
@media (max-width:640px){
  .sad-line{ top:62%; font-size:clamp(1rem,4.2vw,1.5rem); max-width:88vw; }
  .sad-stage #replay{ bottom:14%; }
}

/* ============================================================
   Lone-journey click moments — a faint glow on the ball during
   the few "spots", and the ephemeral overlays it spawns when
   clicked: a search ring (looking), a sonar pulse (calling),
   red embers (coming apart), a heart pulse (remembering).
   Pure additive DOM; the film stays deterministic.
   ============================================================ */
.fxLayer{
  position:absolute; inset:0; z-index:2;          /* above the canvas, below the captions */
  pointer-events:none;
}
.ball-hint{
  position:absolute; left:0; top:0;
  width:96px; height:96px; border-radius:50%;
  transform:translate(-50%,-50%);
  background:radial-gradient(circle,
    var(--hint-c1, rgba(255,235,225,.38)) 0%,
    var(--hint-c2, rgba(255,220,200,.18)) 36%,
    transparent 70%);
  filter:blur(8px);
  opacity:0; pointer-events:none;
  mix-blend-mode:screen;
  transition:background .6s ease;
  will-change:opacity,left,top;
}
/* the hint quietly warms as the journey moves into the red beats */
.ball-hint[data-kind="slip"],
.ball-hint[data-kind="drift"],
.ball-hint[data-kind="search"],
.ball-hint[data-kind="sonar"]{
  --hint-c1: rgba(245,248,250,.36);
  --hint-c2: rgba(218,224,228,.18);
}
.ball-hint[data-kind="embers"],
.ball-hint[data-kind="gather"],
.ball-hint[data-kind="heartbeat"]{
  --hint-c1: rgba(255,190,178,.42);
  --hint-c2: rgba(232,90,80,.20);
}
.fx-slip{
  position:absolute; left:0; top:0;
  width:46px; height:46px; border-radius:50%;
  background:radial-gradient(circle,
    rgba(255,250,245,.45) 0%, rgba(255,245,235,.18) 50%, transparent 75%);
  transform:translate(-50%,-50%) scale(.55);
  filter:blur(4px);
  opacity:0; pointer-events:none;
  mix-blend-mode:screen;
  animation:fxSlip 1.35s ease-out forwards;
}
@keyframes fxSlip{
  0%   { transform:translate(-50%,-50%) scale(.55); opacity:0; }
  20%  { opacity:.78; }
  100% { transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1.6); opacity:0; }
}
.fx-drift{
  position:absolute; left:0; top:0;
  width:4px; height:4px; border-radius:50%;
  background:rgba(245,243,238,.9);
  box-shadow:0 0 8px rgba(255,255,255,.5);
  transform:translate(-50%,-50%);
  opacity:0; pointer-events:none;
  filter:blur(.4px);
  mix-blend-mode:screen;
  animation:fxDrift var(--dur,2s) cubic-bezier(.3,.1,.4,1) forwards;
}
@keyframes fxDrift{
  0%   { transform:translate(-50%,-50%) scale(1); opacity:0; }
  22%  { opacity:.85; }
  100% { transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.5); opacity:0; }
}
.fx-gather{
  position:absolute; left:0; top:0;
  width:5px; height:5px; border-radius:50%;
  background:#e9281e;
  box-shadow:0 0 10px rgba(232,28,22,.68), 0 0 22px rgba(232,28,22,.36);
  transform:translate(-50%,-50%) scale(.6);
  opacity:0; pointer-events:none;
  animation:fxGather var(--dur,1.2s) cubic-bezier(.45,.05,.55,.95) forwards;
}
@keyframes fxGather{
  0%   { transform:translate(-50%,-50%) scale(.6); opacity:0; }
  12%  { opacity:.92; }
  85%  { transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1); opacity:1; }
  100% { transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.35); opacity:0; }
}
.fx-search{
  position:absolute; left:0; top:0;
  width:60px; height:60px; border-radius:50%;
  border:1.5px solid rgba(255,250,240,.5);
  box-shadow:0 0 14px rgba(255,250,240,.22);
  transform:translate(-50%,-50%) scale(.4);
  opacity:0; pointer-events:none;
  mix-blend-mode:screen;
  animation:fxSearch 2.2s cubic-bezier(.25,.5,.25,1) forwards;
}
@keyframes fxSearch{
  0%   { transform:translate(-50%,-50%) scale(.4); opacity:0; }
  14%  { opacity:.78; }
  100% { transform:translate(-50%,-50%) scale(7);  opacity:0; }
}
.fx-sonar{
  position:absolute; left:0; top:0;
  width:36px; height:36px; border-radius:50%;
  border:1.5px solid rgba(255,255,255,.72);
  box-shadow:0 0 12px rgba(255,255,255,.25), inset 0 0 8px rgba(255,255,255,.15);
  transform:translate(-50%,-50%) scale(.3);
  opacity:0; pointer-events:none;
  mix-blend-mode:screen;
  animation:fxSonar 1.7s cubic-bezier(.2,.55,.2,1) forwards;
}
@keyframes fxSonar{
  0%   { transform:translate(-50%,-50%) scale(.3);  opacity:0;   border-color:rgba(255,255,255,.88); }
  10%  { opacity:.92; }
  100% { transform:translate(-50%,-50%) scale(10);  opacity:0;   border-color:rgba(255,255,255,0); }
}
.fx-ember{
  position:absolute; left:0; top:0;
  width:6px; height:6px; border-radius:50%;
  background:#e9281e;
  box-shadow:0 0 12px rgba(232,28,22,.72), 0 0 26px rgba(232,28,22,.4);
  transform:translate(-50%,-50%);
  opacity:1; pointer-events:none;
  animation:fxEmber var(--dur,1.2s) cubic-bezier(.18,.55,.4,1) forwards;
}
@keyframes fxEmber{
  0%   { transform:translate(-50%,-50%) scale(1);  opacity:1; }
  85%  { opacity:.42; }
  100% { transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.4); opacity:0; }
}
.fx-heartbeat{
  position:absolute; left:0; top:0;
  width:170px; height:170px; border-radius:50%;
  transform:translate(-50%,-50%) scale(.55);
  background:radial-gradient(circle,
    rgba(232,28,22,.55) 0%, rgba(232,28,22,.22) 38%, transparent 70%);
  opacity:0; pointer-events:none;
  filter:blur(3px);
  mix-blend-mode:screen;
  animation:fxHeart 1.85s ease-out forwards;
}
@keyframes fxHeart{
  0%   { transform:translate(-50%,-50%) scale(.55); opacity:0; }
  16%  { transform:translate(-50%,-50%) scale(1.2); opacity:.88; }
  38%  { transform:translate(-50%,-50%) scale(.95); opacity:.42; }
  58%  { transform:translate(-50%,-50%) scale(1.4); opacity:.78; }
  100% { transform:translate(-50%,-50%) scale(1.9); opacity:0; }
}
@media (prefers-reduced-motion:reduce){
  .ball-hint, .fx-slip, .fx-drift, .fx-search, .fx-sonar, .fx-ember, .fx-gather, .fx-heartbeat{ display:none !important; }
}
@media (max-width:640px){
  .ball-hint{ width:112px; height:112px; }
  .fx-heartbeat{ width:148px; height:148px; }
}

/* ============================================================
   Fallback (no WebGL)
   ============================================================ */
.fallback{ position:absolute; inset:0; z-index:6; }
.fallback img{ width:100%; height:100%; object-fit:cover; filter:saturate(.55) brightness(1.04); }
.fallback-veil{
  position:absolute; inset:0;
  background:radial-gradient(120% 120% at 50% 40%,transparent 40%,rgba(233,236,236,.55));
}
.no-webgl .reader{
  position:static; left:0; width:auto; height:auto; overflow:visible;
  max-width:680px; margin:18vh auto; padding:0 32px; z-index:7;
}
.no-webgl .reader h1{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(2.5rem,9vw,5.5rem); color:var(--ink);
  margin-bottom:1.2em; letter-spacing:-.01em;
}
.no-webgl .reader p{
  font-family:var(--serif);
  font-size:clamp(1.1rem,2.4vw,1.5rem); line-height:1.7;
  color:rgba(21,24,27,.8); margin-bottom:1.1em;
}
.no-webgl .scroll{ height:auto; min-height:100vh; }
.no-webgl .captions,.no-webgl .ui,.no-webgl #gl{ display:none; }

/* ============================================================
   Reduced motion — calmer, still legible
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  .scrollcue span{ animation:none; }
  .loader{ transition:opacity .3s linear; }
}

@media (max-width:640px){
  .chapters{ left:14px; gap:10px; }
  .chapters button{ width:8px; height:8px; }
  .chapters .pathball{ width:12px; height:12px; }
  .title .kicker{ letter-spacing:.26em; font-size:.62rem; }
  .title .byline{ letter-spacing:.26em; }
  .title .howto{ font-size:.7rem; line-height:1.65; max-width:22em; }
  .colophon .caption{ letter-spacing:.22em; }
  .colophon .resolve{ font-size:clamp(1.25rem,5.2vw,1.8rem); margin-bottom:1.4rem; }
  .colophon h2{ font-size:clamp(1.95rem,8.6vw,3rem); }
  .about{ right:16px; bottom:16px; font-size:.64rem; letter-spacing:.26em; }
  .about-panel{ padding:0; }
  .about-card{ max-height:100vh; max-height:100dvh; border-radius:0; border:0; padding:30px 24px calc(34px + env(safe-area-inset-bottom)); }
  .about-close{ top:12px; right:12px; }
  .beat{ max-width:90vw; }
  .beat p{ font-size:clamp(1rem,4.4vw,1.55rem); line-height:1.3; }
  .stanza p + p{ margin-top:.12em; }
  #replay{ padding:1.1em 1.9em; font-size:.78rem; }
  .scrollcue{ bottom:26px; }
  .scroll{ height:1440vh; }
}
@media (max-width:640px) and (orientation:landscape){
  .scroll{ height:1060vh; }
}
