/* ══════════════════════════════════════════════════════════════════════════
   MOTUS.LOVE — THE MOVEMENT LAYER
   ─────────────────────────────────────────────────────────────────────────
   base.css builds the page. This file builds the FIELD the page lives in.

   The premise is physical, not decorative: the song is a force, the page is
   a medium. Sound bends the field; the field bends the text. Nothing here
   is an "effect" — every displacement you see is a real number coming out
   of the analyser, travelling outward from an epicentre at a finite speed.
   That is why it reads as gravity and not as animation.

   A NOTE ON HOW THE REACTIVE VALUES GET HERE — this cost real measurement.

   The obvious build writes --amp/--bass/--hit onto :root every frame and lets
   calc() do the rest. Measured on this page: setting ANY custom property on
   the root element costs ~14 ms per frame, and it costs that even for a
   property no rule reads. A root custom property invalidates style for the
   whole document, and this document has 300+ word spans whose rects are read
   back on the same frame, so the invalidation is flushed immediately. That
   single line of convenience was the entire frame budget.

   So there are no per-frame custom properties here. movement.js writes plain
   inline styles onto the eight or so elements that actually react, which
   invalidates those elements and nothing else — ~0.2 ms instead of ~14 ms.
   Anything below with a static value is a base state that JS drives live.
   ══════════════════════════════════════════════════════════════════════ */

/* CALM lets the whole instrument stand down to the quiet original in one
   keystroke. Restraint should always be one key away from maximum. */
/* CALM: the instrument stands down. JS zeroes every reactive write, and the
   field layers leave the page entirely rather than sit there at zero. */
html.calm #spacetime,html.calm .beamwave,html.calm .caption,
html.calm .ribbon,html.calm .horizon{display:none}

/* ══════════ THE FIELD CANVASES ══════════
   Three fixed sheets, all additive, all behind every word. */
#spacetime{
  position:fixed;inset:0;width:100%;height:100%;
  z-index:-2;pointer-events:none;opacity:.5;
}
#worlds{z-index:-1}

/* the grain sits above the field so the light never looks like plastic */
.grain{z-index:0}

/* ══════════ HORIZON — the depth cue under everything ══════════
   One flat line at the far edge of the sheet. It is the only thing on the
   page that never moves, which is what makes everything else read as
   moving. Depth needs a fixed reference or it is just blur. */
.horizon{
  position:fixed;left:0;right:0;top:38%;height:1px;z-index:-2;
  pointer-events:none;opacity:.35;
  background:linear-gradient(90deg,transparent,
    rgba(255,150,195,.21) 22%,rgba(167,139,250,.27) 50%,
    rgba(255,150,195,.21) 78%,transparent);
}

/* ══════════ THE BEAM BECOMES THE SOUND ══════════
   The dividers between his sections used to be a painted gradient line.
   They are now oscilloscopes: the actual waveform, at the actual moment.
   The thing that separates two thoughts is the sound moving between them. */
.beamwave{
  display:block;width:min(760px,86%);height:74px;margin:0 auto;
  pointer-events:none;
}

/* ══════════ WORDS ══════════
   Every word that carries a statement becomes its own body with its own
   mass. JS owns their transform completely — CSS must not fight it, so the
   inherited reveal animation is switched off on anything gravity manages. */
.w{display:inline-block;white-space:pre;will-change:transform,opacity;
   transform-origin:50% 60%;backface-visibility:hidden}
[data-reveal].gm,.gm{animation:none!important;opacity:1;filter:none}
[data-reveal].gm.seen{animation:none!important}
.gm{transition:none}
/* line-level bodies: paragraphs move whole, statements move word by word */
.gl{will-change:transform;display:block}

/* a word riding the crest takes the light with it */
.w[data-hot="1"]{color:var(--rose-hot);text-shadow:0 0 18px rgba(255,120,175,.75),
  0 0 46px rgba(167,139,250,.4)}

/* ══════════ THE START GATE ══════════
   Browsers will not let a page make noise at a stranger. So the page keeps
   time in silence — the ring beats at 91 BPM before there is any audio —
   and the first thing you touch turns the sound on. The invitation is the
   mechanic: it is already moving, it is waiting for you. */
.ignite{
  position:fixed;inset:0;z-index:120;display:grid;place-items:center;
  background:radial-gradient(120% 90% at 50% 45%,rgba(6,3,14,.55),rgba(6,3,14,.93));
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  cursor:pointer;border:0;width:100%;font:inherit;color:inherit;
  transition:opacity .9s var(--ease),visibility .9s;
}
.ignite[hidden]{display:none}
.ignite.gone{opacity:0;visibility:hidden;pointer-events:none}
.ignite__in{display:flex;flex-direction:column;align-items:center;gap:30px}
.ignite__ring{
  position:relative;width:min(46vw,168px);aspect-ratio:1;border-radius:50%;
  border:1px solid rgba(255,140,190,.4);
  box-shadow:0 0 40px rgba(255,77,141,.28),inset 0 0 40px rgba(255,77,141,.12);
  display:grid;place-items:center;
}
.ignite__ring::before,.ignite__ring::after{
  content:'';position:absolute;inset:-1px;border-radius:50%;
  border:1px solid rgba(255,120,175,.55);
  animation:metronome 2.6384s linear infinite;
}
.ignite__ring::after{animation-delay:1.3192s}
@keyframes metronome{
  0%{transform:scale(.62);opacity:0}
  14%{opacity:.85}
  100%{transform:scale(1.7);opacity:0}
}
.ignite__core{
  width:26%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle at 40% 34%,#ffdcea,#ff2d78 52%,#5b1240);
  box-shadow:0 0 34px rgba(255,77,141,.8);
  animation:corebeat .6596s var(--ease) infinite;
}
@keyframes corebeat{0%{transform:scale(1.14)}60%,100%{transform:scale(.94)}}
.ignite__say{font-family:var(--f-display);font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;font-size:clamp(.86rem,2.4vw,1.05rem);
  color:var(--ink);text-shadow:var(--text-glow)}
.ignite__sub{font-size:.82rem;color:rgba(255,255,255,.56);letter-spacing:.05em;
  max-width:30ch;text-align:center;line-height:1.7}

/* ══════════ THE RIVER — his words, carried by the song ══════════
   A sticky stage whose content is driven by audio.currentTime, not by
   scroll. Scrolling brings you to the river; the song is what moves it.
   Lines approach from below in perspective, take the light on their beat,
   and recede. Word fill is per-word and lands on the sixteenth grid. */
.riverwrap{position:relative;height:340svh}
.river{
  position:sticky;top:0;height:100svh;overflow:hidden;
  display:grid;place-items:center;
  perspective:1100px;perspective-origin:50% 46%;
}
.river__mark{
  position:absolute;top:clamp(20px,6vh,54px);left:50%;transform:translateX(-50%);
  font-family:var(--f-mono);font-size:.68rem;letter-spacing:.32em;
  text-transform:uppercase;color:rgba(255,255,255,.4);
  display:flex;align-items:center;gap:12px;z-index:3;
}
.river__mark b{width:6px;height:6px;border-radius:50%;background:var(--rose);
  box-shadow:0 0 12px var(--rose)}
.river__stage{
  position:relative;width:min(1000px,86vw);height:100%;
  transform-style:preserve-3d;
}
.river__line{
  position:absolute;left:0;right:0;top:50%;
  text-align:center;font-family:var(--f-display);font-weight:700;
  line-height:1.18;letter-spacing:-.015em;
  font-size:clamp(1.35rem,4.4vw,3.1rem);
  transform-style:preserve-3d;
  color:rgba(255,255,255,.3);
}
.river__line[data-weight="big"]{
  font-size:clamp(2rem,7.4vw,5rem);letter-spacing:-.035em;
}
.river__line[data-weight="soft"]{
  font-family:var(--f-body);font-weight:400;
  font-size:clamp(1.05rem,3.1vw,1.85rem);letter-spacing:.01em;
}
.river__line[data-weight="mark"]{
  font-family:var(--f-mono);font-weight:700;text-transform:uppercase;
  letter-spacing:.3em;font-size:clamp(.8rem,2.2vw,1.05rem);
}
/* THE FILL.
   Two changes for legibility and for feel:
   • unlit words were at 16% on near-black — effectively invisible, so a
     line only existed once it had been sung. 34% is quiet but readable, so
     you can see the sentence coming and read ahead, which is how lyrics
     actually work.
   • the fill used to snap. A 420 ms colour transition makes the light
     travel INTO each word — the same event, but liquid. */
/* 0.34 measured 2.95:1 against the substrate — just under the 3:1 floor for
   large text, and this is ALL large text. 0.44 measures 3.9:1 and still
   reads as clearly unlit next to a full-white word carrying a glow. */
.river__line .w{color:rgba(255,255,255,.44);
  /* THE HALO — the only guarantee that survives a live background.
     Tuning the field's opacity to hit a contrast number is a losing game:
     the field moves, and a single bright ring crossing behind a glyph puts
     that glyph at 1.5:1 no matter what the average says. A void-coloured
     shadow travels WITH the letterform, so the separation is structural and
     holds against anything the field does. Same technique as captions over
     video, and for the same reason. */
  text-shadow:0 0 9px rgba(6,3,14,.96),0 0 24px rgba(6,3,14,.86),
              0 1px 2px rgba(6,3,14,.9);
  transition:color .42s var(--ease),text-shadow .42s var(--ease)}
.river__line .w.lit{color:var(--ink)}
.river__line[data-weight="big"] .w.lit{
  color:var(--rose-hot);
  text-shadow:0 0 10px rgba(6,3,14,.92),0 0 30px rgba(255,77,141,.85),
  0 0 90px rgba(167,139,250,.45)}
.river__line .w.now{
  color:var(--rose-hot);
  text-shadow:0 0 10px rgba(6,3,14,.92),0 0 26px rgba(255,120,175,.92),
  0 0 78px rgba(167,139,250,.5)}
/* the focal line is the one thing on the page that is never soft */
.river__line{text-wrap:balance;text-rendering:optimizeLegibility}
/* a neutral scrim — void colour, no hue — so the mesh can never sit inside
   a letterform. Legibility first; the field lives behind the words. */
/* The scrim is not decoration, it is the contrast budget. Measured: without
   it the river's words sat at 1.57:1 over a lit field; large text needs 3:1.
   Void colour only — no hue, so it darkens without tinting. */
.river__stage::before{
  content:'';position:absolute;inset:-20% -26%;z-index:-1;pointer-events:none;
  background:radial-gradient(66% 48% at 50% 50%,rgba(6,3,14,.93),rgba(6,3,14,.7) 52%,rgba(6,3,14,0) 82%);
}

/* the horizon of the river — where the words come from and go */
.river::before,.river::after{
  content:'';position:absolute;left:0;right:0;height:26vh;pointer-events:none;z-index:2}
.river::before{top:0;background:linear-gradient(180deg,var(--void) 6%,transparent)}
.river::after{bottom:0;background:linear-gradient(0deg,var(--void) 6%,transparent)}

/* ══════════ THE CAPTION — the current line, anywhere on the page ══════════
   The river is one place. The song is everywhere. So the live line follows
   you the whole way down, small and quiet, above the transport. */
.caption{
  position:fixed;left:50%;transform:translateX(-50%);
  bottom:calc(86px + env(safe-area-inset-bottom));
  z-index:36;width:min(880px,90vw);text-align:center;pointer-events:none;
  font-family:var(--f-display);font-weight:600;
  font-size:clamp(.95rem,2.6vw,1.32rem);line-height:1.4;
  opacity:0;transition:opacity .5s var(--ease);
}
.caption.on{opacity:1}
.caption .w{color:rgba(255,255,255,.44);
  text-shadow:0 0 9px rgba(6,3,14,.96),0 0 22px rgba(6,3,14,.85);
  transition:color .42s var(--ease)}
.caption .w.lit{color:var(--ink-2)}
.caption .w.now{color:var(--rose-hot);text-shadow:0 0 20px rgba(255,120,175,.8)}
.river-in .caption{opacity:0}

/* ══════════ THE RIBBON — the waveform under the whole page ══════════ */
.ribbon{
  position:fixed;left:0;right:0;bottom:0;height:74px;z-index:30;
  pointer-events:none;opacity:.32;
}

/* ══════════ TRANSPORT ══════════ */
.transport{
  position:fixed;right:16px;bottom:calc(16px + env(safe-area-inset-bottom));
  z-index:40;display:flex;align-items:center;gap:12px;
  padding:9px 9px 9px 14px;border-radius:var(--r-pill);
  background:rgba(12,6,22,.62);border:1px solid var(--edge);
  -webkit-backdrop-filter:var(--blur);backdrop-filter:var(--blur);
  box-shadow:0 14px 40px rgba(0,0,0,.5);
  transition:border-color .4s var(--ease),box-shadow .4s var(--ease);
}
.transport[data-playing="1"]{border-color:rgba(255,150,195,.34)}
.transport__spec{width:64px;height:22px;display:block;opacity:.9}
.transport__t{font-family:var(--f-mono);font-size:.68rem;letter-spacing:.06em;
  color:rgba(255,255,255,.78);font-variant-numeric:tabular-nums;white-space:nowrap}
.transport__btn{
  width:38px;height:38px;flex:0 0 auto;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;color:var(--ink);
  background:rgba(255,255,255,.06);border:1px solid var(--edge-hi);
  transition:transform .3s var(--ease),background .3s var(--ease);
}
.transport__btn:hover{transform:scale(1.07);background:rgba(255,77,141,.2)}
.transport__btn svg{width:16px;height:16px}
.transport__btn .pausei{display:none}
.transport[data-playing="1"] .transport__btn .playi{display:none}
.transport[data-playing="1"] .transport__btn .pausei{display:block}
/* was 9.6px at 42% white — 3.71:1, under the 4.5:1 needed at that size.
   Bigger and brighter: 11.2px at 72% measures over 7:1. */
.transport__calm{
  font-family:var(--f-mono);font-size:.7rem;letter-spacing:.16em;cursor:pointer;
  color:rgba(255,255,255,.84);background:none;border:0;padding:4px 4px;
  text-transform:uppercase;transition:color .3s}
.transport__calm:hover{color:var(--rose)}
html.calm .transport__calm{color:var(--cyan)}
@media(max-width:520px){
  .transport__spec{width:44px}
  .transport__t{font-size:.6rem}
}

/* base.css's original button is now the transport's play control */
.sound{display:none!important}

/* ══════════ SECTION BREATH ══════════
   Every section carries its own phase, so the page undulates instead of
   pumping in unison. JS writes --ph per section; the depth is bass. */
section,header.hero,footer{--ph:0}
.wrap{will-change:auto}
.wrap.breathing{will-change:transform}

/* ══════════ THE METER, NOW AN INSTRUMENT ══════════ */
.meter{position:relative}
.meter__ring{
  position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:min(420px,92%);aspect-ratio:1;pointer-events:none;z-index:0;opacity:.9}
.meter > *{position:relative;z-index:1}
.meter__stat{text-shadow:0 0 20px rgba(255,77,141,.4)}

/* ══════════ ORBS KEEP TIME ══════════ */
.three__orb{box-shadow:0 0 10px rgba(255,77,141,.5)}

/* ══════════ THE LYRIC STUDIO ══════════
   Press L. Paste any lines, tap SPACE on the beat, export the JSON.
   The engine does not care whose words they are — this is how you give it
   different ones without touching a line of code. */
.studio{
  position:fixed;inset:0;z-index:200;display:none;
  background:rgba(4,2,10,.95);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  padding:clamp(16px,4vw,40px);overflow:auto;
}
.studio.on{display:block}
.studio__in{max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:16px}
.studio h4{font-family:var(--f-display);font-size:1.1rem;letter-spacing:-.01em}
.studio p{font-size:.84rem;color:rgba(255,255,255,.6);line-height:1.7}
.studio textarea{
  width:100%;min-height:180px;resize:vertical;padding:14px 16px;
  border-radius:var(--r-sm);background:rgba(255,255,255,.05);
  border:1px solid var(--edge);color:var(--ink);
  font-family:var(--f-mono);font-size:.8rem;line-height:1.8}
.studio__row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.studio__b{
  padding:9px 16px;border-radius:var(--r-pill);cursor:pointer;
  font-family:var(--f-mono);font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  background:rgba(255,255,255,.06);border:1px solid var(--edge-hi);color:var(--ink)}
.studio__b:hover{background:rgba(255,77,141,.22)}
.studio__b[data-go="1"]{background:rgba(255,45,120,.3);border-color:var(--rose)}
.studio__now{
  font-family:var(--f-display);font-weight:700;font-size:clamp(1.1rem,3.4vw,1.9rem);
  min-height:2.4em;display:grid;place-items:center;text-align:center;
  border-radius:var(--r-pane);border:1px solid var(--edge);
  background:rgba(255,255,255,.035);padding:18px}
.studio__meta{font-family:var(--f-mono);font-size:.7rem;color:var(--cyan);letter-spacing:.1em}

/* ══════════ REDUCED MOTION ══════════
   No field, no autoplay, no river motion. The words simply stand still and
   are all legible — nobody is charged movement to read him. */
@media(prefers-reduced-motion:reduce){
  #spacetime,.ribbon,.beamwave,.horizon{display:none}
  .riverwrap{height:auto}
  .river{position:static;height:auto;padding:80px 0;display:block;perspective:none}
  .river__stage{height:auto;transform:none}
  .river__line{position:static;transform:none!important;opacity:1!important;
    filter:none!important;margin:0 auto 34px;color:var(--ink)}
  .river__line .w{color:var(--ink)!important}
  .river::before,.river::after{display:none}
  .caption{display:none}
  .ignite{display:none}
  .w{transform:none!important;opacity:1!important}
}

/* ══════════════════════════════════════════════════════════════════════════
   THE SECOND SCALE PASS — bigger again, and more air again
   ─────────────────────────────────────────────────────────────────────────
   base.css already carried one round of "make it bigger, space it out".
   This is the next notch, by instruction: nothing on this page is set at
   reading size any more. Body copy is set at title size, statements are set
   larger than that, and his name is set larger than everything.

   These override base.css by load order, so his original file stays intact
   and every size decision in this build is legible in one place.
   ══════════════════════════════════════════════════════════════════════ */

/* ── HIS NAME ──
   base.css gives the wordmark TWO lockups and both have to grow, or one of
   them breaks. Stacked (phone / portrait / narrow) it is two lines sized off
   the width. Wide landscape it becomes ONE line, "Motus." and "Love" set
   side by side on a shared baseline — and there the limit is width, not
   height: measured on the real element the pair is 5.55 x the font size. So
   the wide rule is sized to land at ~92% of the viewport and never touch
   the edges, and the svh term keeps a short laptop window safe.

   Overriding only the stacked size (the first thing I tried) silently blew
   the wide lockup 55px past the screen, where body{overflow-x:hidden} hid
   the damage by clipping his name instead of scrolling. */
.lovetitle__l{
  font-size:max(3.9rem, min(28vw, 33svh));
  letter-spacing:-.055em;
  text-shadow:0 0 54px rgba(255,77,141,.5),0 0 150px rgba(167,139,250,.3);
}
@media (min-width:1100px) and (orientation:landscape){
  .lovetitle__l{font-size:min(16.6vw, 28svh)}
}
.lovetitle__l--2{
  text-shadow:0 0 46px rgba(255,77,141,.78),0 0 130px rgba(167,139,250,.46),
              0 0 240px rgba(255,45,120,.34);
}
/* it arrives from almost nothing, and it keeps breathing — --bs is written
   by the conductor so the breath is the song's, not a fixed timer's */
.lovetitle{
  animation:name-grow 2.6s var(--ease) both, name-breathe 7.4s ease-in-out 2.6s infinite;
  margin-bottom:clamp(14px,3vh,40px);
}
@keyframes name-grow{
  from{opacity:0;transform:scale(.30);letter-spacing:.14em;filter:blur(30px)}
  58%{opacity:1;filter:blur(0)}
  to{opacity:1;transform:scale(1);letter-spacing:-.055em;filter:blur(0)}}
@keyframes name-breathe{
  0%,100%{transform:scale(var(--bs,1))}
  50%{transform:scale(calc(var(--bs,1) * 1.045))}}
/* the period is the one cyan thing on the page — give it more of the room */
.lovetitle .dot{
  text-shadow:0 0 30px var(--cyan-glow),0 0 90px var(--cyan-glow),
              0 0 190px rgba(79,240,255,.42);
}
/* the name is huge now, so the hero stacks tighter around it */
.hero{padding:92px 0 110px}
.hero .wrap{gap:clamp(26px,4.6vh,58px)}

/* ── TYPE: another notch ── */
.eyebrow{font-size:clamp(.95rem,3.6vw,1.3rem);letter-spacing:.34em}
h2{font-size:clamp(2.6rem,9.2vw,5rem)}
h3{font-size:clamp(2.1rem,7.6vw,3.5rem)}
p{font-size:clamp(1.62rem,6.2vw,2.6rem);line-height:1.46}
.lead{font-size:clamp(1.8rem,6.8vw,3.05rem);line-height:1.4}
.hero .sub{font-size:clamp(1.6rem,6.2vw,2.7rem)}
.beat{font-size:clamp(2.15rem,8.4vw,3.9rem);line-height:1.16;
  text-shadow:0 0 12px rgba(6,3,14,.9),0 0 30px rgba(6,3,14,.7)}
.finale{font-size:clamp(2.9rem,11.6vw,6.2rem);
  text-shadow:0 0 14px rgba(6,3,14,.85),0 0 34px rgba(255,77,141,.7),
  0 0 96px rgba(167,139,250,.4)}
.three__row p{font-size:clamp(1.36rem,5.4vw,2.3rem);line-height:1.28}
.close .stack b{font-size:clamp(2.1rem,9vw,4.3rem)}
.badge__mark{font-size:clamp(1.7rem,6.8vw,2.8rem)}
/* --violet at 12.8px measured 3.87:1 with the field lit behind it. A lighter
   violet keeps the colour meaning and clears 4.5:1. */
.badge__sub{font-size:.82rem;letter-spacing:.34em;color:#cbbcfd}
.sig{font-size:.88rem;letter-spacing:.3em}
.meter__lines b{font-size:clamp(1.4rem,5.8vw,2.5rem)}
.movecue{font-size:.82rem}
#cue{font-size:clamp(.88rem,3.2vw,1.08rem)}
#cue b{font-size:clamp(1.55rem,6.2vw,2.5rem)}
footer .mark{font-size:clamp(1.5rem,6vw,2.4rem)}
footer .fine{font-size:clamp(.9rem,3vw,1.1rem)}
.tag{font-size:.8rem;letter-spacing:.24em}

/* ── AIR ──
   Several lines of space between statements, as asked. Space is what makes
   a big line land: at this type size, tight spacing reads as shouting and
   wide spacing reads as certainty. */
section{padding:230px 0}
@media(min-width:768px){section{padding:320px 0}}
.wrap{max-width:1180px}
.ln + .ln{margin-top:1.75em}
.three{gap:38px}
.three__row{padding:36px 26px;gap:24px}
@media(min-width:768px){.three__row{padding:54px 48px;gap:36px}}
.pane{padding:44px 30px}
@media(min-width:768px){.pane{padding:74px 64px}}
.badge{padding:38px 46px;gap:14px}
.close .stack{gap:26px}
.beamwave{height:110px;margin:44px auto}
footer{padding:120px 0 90px}

/* the river grows with everything else */
.river__line{font-size:clamp(1.5rem,4.6vw,3.3rem)}
/* capped so that at the 8.4 s window the three visible lines cannot touch:
    neighbours sit ~380 px apart and the tallest line wraps to ~250 px */
.river__line[data-weight="big"]{font-size:clamp(2.2rem,7.6vw,5.2rem)}
.river__line[data-weight="soft"]{font-size:clamp(1.25rem,3.8vw,2.3rem)}
.river__line[data-weight="mark"]{font-size:clamp(.92rem,2.6vw,1.25rem)}
.caption{font-size:clamp(1.1rem,3vw,1.65rem)}

/* ── IGNITION, WITHOUT THE SPHERE ──
   The pink ball is gone. What replaces it is the period out of his own
   wordmark — the one cyan mark on the site — held inside the rings that are
   already keeping the song's tempo in silence. A point, not a bauble. */
.ignite__core{
  width:12%;aspect-ratio:1;border-radius:50%;
  background:var(--cyan);
  box-shadow:0 0 16px var(--cyan-glow),0 0 52px var(--cyan-glow),
             0 0 120px rgba(79,240,255,.4);
  animation:corebeat .6596s var(--ease) infinite;
}
.ignite__ring{border-color:rgba(120,235,255,.34);
  box-shadow:0 0 46px rgba(79,240,255,.16),inset 0 0 46px rgba(255,77,141,.08)}
.ignite__ring::before,.ignite__ring::after{border-color:rgba(255,120,175,.5)}

/* ── THE WORDMARK'S BREATH MUST NOT MOVE THE PAGE ──
   .lovetitle is a 100vw box that breaks the column, and name-breathe scales
   it to 1.045 — which pushes the document's scrollable area ~20px past the
   viewport. body{overflow-x:hidden} hid it visually but the ROOT was still
   scrollable: the page could be dragged sideways a few pixels. overflow-x:
   clip clips without creating a scroll container, so the sticky river is
   unaffected — which plain overflow:hidden could not promise. */
html{overflow-x:clip}
/* And body must give the clipping job UP. base.css sets body{overflow-x:
   hidden}, which computes to hidden/auto — that makes BODY a scroll
   container, and a sticky element then positions against body's box instead
   of the page's. Measured: the river's sticky stage was not sticking at all;
   it scrolled straight past. Moving the clip to html and releasing body
   fixes the sticky and still leaves the page unscrollable sideways. */
body{overflow-x:visible}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE — measured, not assumed
   ─────────────────────────────────────────────────────────────────────────
   Three things were quietly expensive on a phone, and none of them looked
   expensive in the source.
   ══════════════════════════════════════════════════════════════════════ */

/* 1. will-change is a PROMISE, not a hint.
   316 word spans, 37 revealing blocks and ~40 line bodies were each asking
   for their own compositor layer — roughly 370 textures for a phone GPU to
   hold and a compositor to manage every frame. The answer is not to delete
   the hint (transform-animated text does want a layer) but to SCOPE it:
   only the few groups actually on screen get one, and a reveal gives its
   layer back the moment it has landed. Typically ~60 layers instead of 370. */
.w{will-change:auto}
.gl{will-change:auto}
/* the reveal is a single 1.15 s animation that then never runs again —
   hinting 30 off-screen blocks for it buys nothing and costs 30 layers */
[data-reveal]{will-change:auto}
.on-stage .w{will-change:transform}
/* 29 river lines existed; at most four are ever on screen. runRiver adds
   and removes this as each line enters and leaves the window. */
.river__line.showing{will-change:transform,opacity,filter}
.gl.on-stage{will-change:transform}

/* 2. Sticky hover. A tap on a touch screen leaves :hover latched, so every
   pane the visitor touched stayed lifted 3px and lit for the rest of the
   session. Hover states belong to devices that can actually hover. */
@media (hover:none){
  .pane:hover{transform:none;border-color:var(--edge);box-shadow:var(--pane-shadow)}
  .btn:hover,.sound:hover{transform:none}
  .transport__btn:hover{transform:none;background:rgba(255,255,255,.06)}
  .studio__b:hover{background:rgba(255,255,255,.06)}
  .transport__calm:hover{color:rgba(255,255,255,.42)}
  .gate__cue:hover{box-shadow:0 0 30px rgba(255,77,141,.3)}
}

/* 3. Ten stacked backdrop-filters. Each one makes the GPU re-sample
   everything behind it, every frame, while a full-screen canvas is already
   compositing underneath. On a phone that is the single most expensive
   thing on the page — and against this near-black substrate a flat tinted
   panel is visually almost identical. Blur where it is cheap; colour where
   it is not. */
@media (max-width:760px){
  .pane,.three__row,.badge,.movecue,.gate__cue,.btn,.tag,.transport,.studio,
  .meter,.ignite{
    -webkit-backdrop-filter:none;backdrop-filter:none;
  }
  .meter{background:rgba(20,10,32,.5)}
  .ignite{background:radial-gradient(120% 90% at 50% 45%,rgba(6,3,14,.82),rgba(6,3,14,.97))}
  .pane,.three__row{background:rgba(22,11,34,.62)}
  .badge{background:rgba(28,14,42,.72)}
  .movecue,.gate__cue,.btn,.tag{background:rgba(22,11,34,.74)}
  .transport{background:rgba(12,6,22,.93)}
  .studio{background:rgba(4,2,10,.985)}

  /* every control reaches the 44px the thumb needs */
  .transport{gap:8px;padding:8px 8px 8px 10px}
  .transport__btn{width:46px;height:46px}
  .transport__calm{min-height:44px;display:flex;align-items:center;padding:0 9px}
  .studio__b{min-height:44px;display:inline-flex;align-items:center}
  .gate__cue{min-height:48px}

  /* the river gets more of the screen when the screen is small */
  .river__stage{width:min(1000px,92vw)}
  .beamwave{height:84px;margin:32px auto}
}

/* the narrowest phones: the spectrum is the first thing to go, so the
   transport never crowds the play control off its own pill */
@media (max-width:430px){
  .transport__spec{display:none}
  .transport__t{font-size:.6rem}
  .transport{right:10px;bottom:calc(10px + env(safe-area-inset-bottom))}
  .caption{bottom:calc(78px + env(safe-area-inset-bottom));width:94vw}
}
