@font-face{
  font-family:"Archivo";
  font-style:normal;
  font-display:swap;
  font-weight:100 900;
  font-stretch:62% 125%;
  src:url("../fonts/archivo-var.woff2") format("woff2-variations");
}
:root{
  --pink:#F80281; --violet:#7C1EF2; --blue:#0E82F8;
  --ink:#050507; --paper:#f4f4f6;
  --p:0; --total:0; --flash:0;
  /* Lowest alpha that clears 4.5:1 on the strapline at every breakpoint, measured
     against the composited beam wash. 0.39 gave 4.48, 0.42 gives 4.69. */
  --scrim-a:.42; --scrim-b:.28;
  --ignite:0; --word:0; --strike:0; --flood:0; --strap:0; --exit:0; --fog:0;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--ink);color:var(--paper);font-family:"Archivo","Helvetica Neue",Arial,sans-serif;
  -webkit-font-smoothing:antialiased;overflow-x:hidden}

.stage{position:fixed;inset:0;overflow:hidden;z-index:0;background:var(--ink)}
/* Pre-rendered stage layers, shared by the mobile hero (.mstage) and the sub-page
   banner (.banner). Same images, same keyframes. transform and opacity only — those
   run on the compositor thread, so there is no per-frame CPU work. No blend modes
   and no filters anywhere: those are what made the phone unusable.
   .mstage is gated to mobile; .banner is active at every breakpoint. */
.mstage{display:none}
.mstage i{position:absolute;display:block;background-repeat:no-repeat}
.m-bg{inset:0;background-image:url("../brand/stage-bg.webp");
  background-size:cover;background-position:center bottom;
  animation:m-breathe 44s ease-in-out infinite alternate;will-change:transform}
/* 160% wide, 112% tall, 6% of overhang at the top, so a -14%/-4% shift never
   exposes an edge. */
.m-smoke{top:-6%;left:-30%;width:160%;height:112%;
  background-image:url("../brand/stage-smoke.webp");background-size:100% 100%;
  animation:m-drift 26s ease-in-out infinite alternate;will-change:transform}
.m-beam-a,.m-beam-b{inset:0;background-size:cover;background-position:center top;
  transform-origin:50% 0;will-change:opacity,transform}
/* The two banks are exact opposites, so they cross-fade rather than strobe, and
   the counter-rotation makes the beams sweep. */
.m-beam-a{background-image:url("../brand/stage-beams-a.webp");
  animation:m-bank-a 3.2s ease-in-out infinite}
.m-beam-b{background-image:url("../brand/stage-beams-b.webp");
  animation:m-bank-b 3.2s ease-in-out infinite}
/* cover, matching .m-bg / .m-beam-a / .m-beam-b / .m-crowd. The whole scene was
   authored at 1080x1920 and every layer that belongs to it must crop together to
   stay in register. The truss image is exactly 7.8% of the scene height, so at
   height:7.8% cover resolves to the same horizontal scale the beams get and the
   nozzles land on the beam origins. Static — the beams do the moving. */
.m-truss{top:0;left:0;right:0;height:7.8%;
  background-image:url("../brand/stage-truss.webp");
  background-size:cover;background-position:center top}
.m-crowd{left:0;right:0;bottom:0;height:30%;
  background-image:url("../brand/stage-crowd.webp");
  background-size:cover;background-position:center bottom}

/* Sub-page banner. Its own scene, authored at 1920x640 (3:1) rather than the hero's
   9:16 portrait — a landscape frame cannot be cropped out of a portrait one without
   either magnifying a sliver or squashing it. Distinct b-* class names so the banner
   and the mobile hero can never fight over the same rules; the keyframes are shared,
   because those are geometry-independent. Within the frame the truss is the top 12.2%
   and the crowd the bottom 28.1%, so cover puts every layer on one mapping again and
   the nozzles land on the beam origins. No m-breathe here: a 42vh banner does not
   need it, and the scale animation was the layer most likely to re-rasterise. */
.banner{position:relative;height:42vh;min-height:320px;overflow:hidden;background:var(--ink)}
.banner i{display:block;background-repeat:no-repeat}
.banner .b-bg,.banner .b-beam-a,.banner .b-beam-b{
  position:absolute;inset:0;background-size:cover;background-position:center top}
.banner .b-bg{background-image:url("../brand/banner-bg.webp")}
.banner .b-beam-a{background-image:url("../brand/banner-beams-a.webp");
  transform-origin:50% 0;animation:m-bank-a 3.2s ease-in-out infinite;will-change:opacity,transform}
.banner .b-beam-b{background-image:url("../brand/banner-beams-b.webp");
  transform-origin:50% 0;animation:m-bank-b 3.2s ease-in-out infinite;will-change:opacity,transform}
.banner .b-smoke{position:absolute;top:0;left:-25%;width:150%;height:100%;
  background-image:url("../brand/banner-smoke.webp");background-size:100% 100%;
  animation:m-drift 26s ease-in-out infinite alternate;will-change:transform}
.banner .b-truss{position:absolute;top:0;left:0;right:0;height:12.2%;
  background-image:url("../brand/banner-truss.webp");background-size:cover;background-position:center top}
.banner .b-crowd{position:absolute;left:0;right:0;bottom:0;height:28.1%;
  background-image:url("../brand/banner-crowd.webp");background-size:cover;background-position:center bottom}

/* Scaled to the banner's smaller lockup rather than reusing the hero dimensions. */
.banner .scrim{top:50%;width:min(130vw,1000px);height:170%}

/* The lockup sits at rest in the banner: no reveal, no scroll-driven transforms,
   and roughly 60% of its homepage size. */
.banner .lockup{top:50%;width:min(92vw,560px);opacity:1;animation:none;
  transform:translate3d(-50%,-50%,0)}
.banner .mark{width:min(54vw,528px)}
.banner .strap,.banner .xrow i,.banner .mark .lg-word,.banner .mark .lg-x{opacity:1;transform:none}
.banner .xrow i:nth-child(1),.banner .xrow i:nth-child(2),.banner .xrow i:nth-child(3){transform:none}
/* Carries filter:blur(30px) and is driven by --strike, which nothing writes here. */
.banner .lg-glow{display:none}
.layer{position:absolute;inset:0;pointer-events:none}

.haze i{position:absolute;border-radius:50%;mix-blend-mode:screen;will-change:transform}
.h1{width:80vw;height:80vw;left:-14vw;top:-6vh;
  background:radial-gradient(circle,rgba(255,255,255,.18),rgba(255,255,255,.05) 42%,transparent 68%);
  animation:d1 52s ease-in-out infinite}
.h2x{width:72vw;height:72vw;right:-12vw;top:4vh;
  background:radial-gradient(circle,rgba(255,255,255,.14),transparent 66%);
  animation:d2 67s ease-in-out infinite}
.h3{width:96vw;height:64vw;left:6vw;bottom:-22vh;
  background:radial-gradient(circle,rgba(255,255,255,.13),transparent 64%);
  animation:d3 59s ease-in-out infinite}
.tint{opacity:calc(var(--ignite)*.9)}
.tint i{position:absolute;border-radius:50%;mix-blend-mode:screen;will-change:transform}
.tp{width:78vw;height:78vw;left:-6vw;top:-4vh;
  background:radial-gradient(circle,rgba(255,30,142,.55),rgba(123,47,247,.18) 44%,transparent 70%);
  animation:d2 48s ease-in-out infinite}
.tb{width:76vw;height:76vw;right:-8vw;bottom:-10vh;
  background:radial-gradient(circle,rgba(27,140,255,.50),rgba(123,47,247,.16) 44%,transparent 70%);
  animation:d1 63s ease-in-out infinite}
@keyframes d1{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(7vw,-5vh,0) scale(1.18)}}
@keyframes d2{0%,100%{transform:translate3d(0,0,0) scale(1.1)}50%{transform:translate3d(-8vw,6vh,0) scale(.94)}}
@keyframes d3{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(5vw,4vh,0) scale(1.14)}}

.smoke{position:absolute;top:-10%;left:-60%;width:220%;height:120%;
  mix-blend-mode:screen;will-change:transform}
.smoke svg{width:100%;height:100%;display:block}
.sA{opacity:calc(.09 + var(--fog)*.22 + var(--flash)*.20);animation:pan1 96s linear infinite}
.sB{opacity:calc(.05 + var(--fog)*.18 + var(--flash)*.16);animation:pan2 142s linear infinite;
  top:6%;left:-80%;width:260%;height:110%}
.sC{opacity:calc(var(--fog)*.28 + var(--flash)*.22);animation:pan3 118s linear infinite;
  top:26%;left:-50%;width:200%;height:96%}
@keyframes pan1{0%{transform:translate3d(0,0,0)}100%{transform:translate3d(-27%,0,0)}}
@keyframes pan2{0%{transform:translate3d(-22%,0,0)}100%{transform:translate3d(0,-3%,0)}}
@keyframes pan3{0%{transform:translate3d(-18%,2%,0)}100%{transform:translate3d(4%,-2%,0)}}

.truss{position:absolute;left:0;right:0;top:0;height:9vh;z-index:3;opacity:var(--ignite)}
.truss svg{width:100%;height:100%;display:block}

.beam{position:absolute;top:7vh;width:24vw;margin-left:-12vw;height:150vh;
  transform-origin:50% 0;clip-path:polygon(46% 0,54% 0,100% 100%,0 100%);
  opacity:calc(var(--ignite) * var(--k) * .55);will-change:transform,opacity}
.beam i{position:absolute;inset:0;
  background:linear-gradient(to bottom,currentColor,transparent calc(58% + var(--k)*24%))}
.src{position:absolute;top:6.2vh;width:7vw;height:7vw;margin-left:-3.5vw;border-radius:50%;
  mix-blend-mode:screen;pointer-events:none;
  background:radial-gradient(circle,currentColor,transparent 62%);
  opacity:calc(var(--ignite) * var(--k) * .8);will-change:opacity}

.blinder{position:absolute;inset:0;background:#fff;mix-blend-mode:screen;
  opacity:calc(var(--flash)*.55);will-change:opacity}

.crowd{position:absolute;left:0;right:0;bottom:0;height:34vh;
  transform:translate3d(0,calc((1 - var(--ignite))*24vh),0)}
.crowd svg{position:absolute;bottom:0;width:100%;height:100%}

.grain{background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3'/></filter><rect width='160' height='160' filter='url(%23n)' opacity='0.55'/></svg>");
  opacity:.045;mix-blend-mode:overlay}

/* Local scrim behind the lockup. Without it the strapline sits on the beam wash
   at roughly 2.7:1. Radial gradient only — no filter, no blend mode. z-index 3
   puts it above the beams, smoke and crowd and below the lockup's 4. Fades with
   --exit so it does not linger as a dark patch once the logo has drifted away. */
.scrim{position:absolute;left:50%;top:46%;transform:translate3d(-50%,-50%,0);
  width:min(150vw,1500px);height:80vh;pointer-events:none;z-index:3;
  opacity:calc(1 - var(--exit)*.92);
  background:radial-gradient(ellipse at center,
    rgba(5,5,7,var(--scrim-a)) 0%,
    rgba(5,5,7,var(--scrim-b)) 38%,
    rgba(5,5,7,0) 72%)}

.lockup{position:absolute;left:50%;top:46%;width:min(92vw,900px);text-align:center;z-index:4;
  transform:translate3d(-50%,calc(-50% - var(--exit)*26vh),0);
  opacity:calc(1 - var(--exit)*.92)}
.mark{display:block;width:min(90vw,880px);margin:0 auto;overflow:visible}
.lg-word{opacity:var(--word);transform:translate(calc((var(--word) - 1)*250px),0)}
.lg-x,.lg-glow{transform-box:fill-box;transform-origin:50% 50%;
  transform:translate(calc((1 - var(--strike))*160px),calc((var(--strike) - 1)*230px))
            scale(calc(.86 + var(--strike)*.14))}
.lg-x{opacity:var(--strike)}
.lg-glow{opacity:calc(var(--strike)*(var(--flood)*.7 + var(--flash)*.55));filter:blur(30px)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.strap{font-weight:500;font-stretch:108%;margin-top:clamp(14px,2.6vw,30px);font-size:clamp(10px,1.25vw,15px);
  letter-spacing:.42em;text-transform:uppercase;color:rgba(255,255,255,.92);
  opacity:var(--strap);transform:translate3d(0,calc((1 - var(--strap))*18px),0)}
/* Solid white, not the brand gradient. At 11px a gradient cannot clear 4.5:1 on
   any backdrop — the gradient belongs on display type and graphics only. */
.strap em{font-style:normal}
.xrow{display:flex;justify-content:center;gap:clamp(18px,3vw,38px);margin-top:clamp(18px,3vw,34px)}
.xrow i{width:clamp(9px,1.1vw,14px);height:clamp(9px,1.1vw,14px);display:block;opacity:var(--strap);
  clip-path:polygon(20% 0,50% 32%,80% 0,100% 20%,68% 50%,100% 80%,80% 100%,50% 68%,20% 100%,0 80%,32% 50%,0 20%)}
.xrow i:nth-child(1){background:var(--pink);transform:translate3d(0,calc((1 - var(--strap))*20px),0)}
.xrow i:nth-child(2){background:var(--violet);transform:translate3d(0,calc((1 - var(--strap))*15px),0)}
.xrow i:nth-child(3){background:var(--blue);transform:translate3d(0,calc((1 - var(--strap))*10px),0)}

.cue{position:absolute;left:50%;bottom:5vh;transform:translateX(-50%);font-size:10px;
  letter-spacing:.42em;color:rgba(255,255,255,.5);opacity:calc(1 - var(--p)*8);z-index:4;
  animation:bob 2.6s ease-in-out infinite}
@keyframes bob{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,7px)}}

.bar{position:fixed;left:0;top:0;height:2px;width:100%;z-index:9;
  transform:scaleX(var(--total));transform-origin:0 50%;
  background:linear-gradient(90deg,var(--pink),var(--violet),var(--blue))}

.lights{position:fixed;right:16px;top:16px;z-index:10;background:rgba(0,0,0,.55);
  color:rgba(255,255,255,.8);border:1px solid rgba(255,255,255,.28);border-radius:2px;
  font:400 10px/1 "Helvetica Neue",Arial,sans-serif;letter-spacing:.24em;text-transform:uppercase;
  padding:10px 14px;cursor:pointer;backdrop-filter:blur(6px)}
.lights:hover{border-color:rgba(255,255,255,.6);color:#fff}
.lights:focus-visible{outline:2px solid var(--pink);outline-offset:3px}
body.dark .beam,body.dark .src,body.dark .blinder,body.dark .truss{display:none}

/* Shared navigation. Transparent — it sits over the stage. Grouped to the left so
   it never collides with the lights toggle, which is fixed to the top right on the
   homepage. */
.nav{position:fixed;left:0;top:0;z-index:8;display:flex;align-items:center;
  gap:clamp(18px,3vw,34px);padding:14px clamp(16px,4vw,28px)}
.nav-home{display:flex;align-items:center;line-height:0}
.nav-home img{height:22px;width:auto;display:block}
.nav-home:focus-visible,.nav a:focus-visible{outline:2px solid var(--pink);outline-offset:3px}
.nav ul{display:flex;align-items:center;gap:clamp(14px,2.6vw,26px);list-style:none}
.nav ul a{font-size:11px;letter-spacing:.32em;text-transform:uppercase;text-decoration:none;
  color:rgba(255,255,255,.62);padding-bottom:4px;
  background-image:linear-gradient(90deg,var(--pink),var(--violet),var(--blue));
  background-repeat:no-repeat;background-position:0 100%;background-size:0 1px;
  transition:color .25s ease,background-size .3s cubic-bezier(.2,.7,.2,1)}
.nav ul a:hover,.nav ul a:focus-visible{color:#fff;background-size:100% 1px}
.nav ul a[aria-current="page"]{color:#fff;background-size:100% 1px}

.track{height:440vh;position:relative;z-index:1}
main{position:relative;z-index:2;background:linear-gradient(180deg,rgba(5,5,7,0) 0,var(--ink) 16vh)}
section{max-width:1080px;margin:0 auto;padding:16vh 6vw}
.rev{opacity:0;transform:translate3d(0,36px,0);
  transition:opacity .85s cubic-bezier(.2,.7,.2,1),transform .85s cubic-bezier(.2,.7,.2,1)}
.rev.in{opacity:1;transform:none}
.eyebrow{font-weight:500;font-stretch:108%;font-size:11px;letter-spacing:.42em;text-transform:uppercase;
  color:rgba(255,255,255,.45);margin-bottom:2.2rem}
h2{font-family:"Archivo",sans-serif;font-weight:900;font-stretch:118%;
  font-size:clamp(34px,7vw,86px);line-height:.94;letter-spacing:-.03em;
  transform:skewX(-8deg);margin-bottom:1.6rem}
h2 em{font-style:normal;background:linear-gradient(100deg,var(--pink),var(--violet),var(--blue));
  -webkit-background-clip:text;background-clip:text;color:transparent}
p.lede{font-size:clamp(16px,1.7vw,21px);line-height:1.65;max-width:52ch;color:rgba(255,255,255,.78)}
.event{margin-top:4rem;border:1px solid rgba(255,255,255,.12);padding:3rem 2.6rem;
  position:relative;overflow:hidden}
/* Lighter variants of the brand stops, local to the card. The standard
   #0E82F8 -> #F80281 does not clear 4.5:1 at 13px on near-black. */
.event{--card-pink:#FF57A8; --card-blue:#6EB4FF}
/* 60% of card width, a third of it pushed outside the right edge and cropped by
   the card's overflow, so it bleeds rather than sitting inside. z-index 0 keeps
   it behind the copy — a positioned pseudo-element would otherwise paint over it. */
.event::before{content:"";position:absolute;right:-20%;top:-46%;width:60%;height:200%;z-index:0;
  background:linear-gradient(140deg,var(--pink),var(--blue));opacity:.22;
  clip-path:polygon(20% 0,50% 32%,80% 0,100% 20%,68% 50%,100% 80%,80% 100%,50% 68%,20% 100%,0 80%,32% 50%,0 20%)}
/* A lit edge along the top of the card. */
.event::after{content:"";position:absolute;left:0;right:0;top:0;height:2px;z-index:0;
  background:linear-gradient(90deg,var(--pink),var(--violet),var(--blue))}
.event > *{position:relative;z-index:1}
.event .when{font-weight:700;font-stretch:108%;font-size:13px;letter-spacing:.42em;text-transform:uppercase;
  background:linear-gradient(90deg,var(--card-pink),var(--card-blue));
  -webkit-background-clip:text;background-clip:text;color:transparent;
  margin-bottom:1.1rem}
.event .series{font-family:"Archivo",sans-serif;font-weight:900;font-stretch:118%;
  font-size:clamp(24px,4vw,48px);letter-spacing:-.025em;transform:skewX(-8deg);
  transform-origin:0 50%;margin:.3rem 0 .9rem}
.event .what{font-size:14px;letter-spacing:.34em;text-transform:uppercase;
  color:rgba(255,255,255,.62);margin-bottom:2.2rem}
/* The 1px gap shows the grid's own background through as the rule between panels:
   pink at the top fading to blue at the bottom. */
.nights{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:1px;background:linear-gradient(180deg,var(--pink),var(--blue));margin-bottom:2rem}
.night{background:var(--ink);padding:1.6rem 1.4rem}
/* Each day carries its night's colour: Friday the sunset end, Saturday after dark. */
.night .day{display:block;font-size:12px;letter-spacing:.3em;text-transform:uppercase;
  color:rgba(255,255,255,.5);margin-bottom:.7rem}
.night:nth-child(1) .day{color:var(--card-pink)}
.night:nth-child(2) .day{color:var(--card-blue)}
.night strong{display:block;font-family:"Archivo",sans-serif;font-weight:900;font-stretch:118%;
  font-size:clamp(19px,2.3vw,27px);letter-spacing:-.02em;transform:skewX(-9deg);
  transform-origin:0 50%;margin-bottom:.6rem}
.night .sub{display:block;font-size:14px;color:rgba(255,255,255,.7);line-height:1.5}
/* No description line under a session name, so drop the gap it was leaving. */
.night strong:last-child{margin-bottom:0}
/* The two session names, stacked as display lines under the lede. */
.sessions{display:block;margin:2.4rem 0 2.2rem}
.session{display:block;font-family:"Archivo",sans-serif;font-weight:900;font-stretch:118%;
  font-size:clamp(20px,2.6vw,32px);line-height:1.12;letter-spacing:-.02em;
  transform:skewX(-8deg);transform-origin:0 50%;color:rgba(255,255,255,.92)}
.event .where{font-size:15px;color:rgba(255,255,255,.82);margin-bottom:.5rem}
.event .tickets{font-size:13px;letter-spacing:.2em;text-transform:uppercase}
/* Enquiry routes. The whole row is the link; the X mark rides in on hover. */
.routes{margin-top:3.4rem;border-top:1px solid rgba(255,255,255,.12)}
.route{display:flex;align-items:center;gap:1.5rem;padding:2.1rem 0;
  border-bottom:1px solid rgba(255,255,255,.12);text-decoration:none;color:inherit}
.route-main{flex:1;min-width:0}
.route-h{display:block;font-family:"Archivo",sans-serif;font-weight:900;font-stretch:118%;
  font-size:clamp(22px,3.6vw,42px);line-height:1.02;letter-spacing:-.025em;
  transform:skewX(-8deg);transform-origin:0 50%;color:rgba(255,255,255,.92);
  transition:color .25s ease}
.route-d{display:block;margin-top:.75rem;font-size:14px;line-height:1.5;
  color:rgba(255,255,255,.55);transition:color .25s ease}
.route-x{flex:none;width:clamp(13px,1.7vw,20px);height:clamp(13px,1.7vw,20px);display:block;
  background:linear-gradient(140deg,var(--pink),var(--violet),var(--blue));
  opacity:0;transform:translate3d(-12px,0,0);
  transition:opacity .3s ease,transform .3s cubic-bezier(.2,.7,.2,1);
  clip-path:polygon(20% 0,50% 32%,80% 0,100% 20%,68% 50%,100% 80%,80% 100%,50% 68%,20% 100%,0 80%,32% 50%,0 20%)}
.route:hover .route-h,.route:focus-visible .route-h{color:#fff}
.route:hover .route-d,.route:focus-visible .route-d{color:rgba(255,255,255,.78)}
.route:hover .route-x,.route:focus-visible .route-x{opacity:1;transform:none}
.route:focus-visible{outline:2px solid var(--pink);outline-offset:6px}
.contact-details{margin-top:3.2rem;font-size:15px;line-height:1.9;color:rgba(255,255,255,.72)}
.contact-details a{color:rgba(255,255,255,.92)}

footer{font-stretch:105%;padding:10vh 6vw;border-top:1px solid rgba(255,255,255,.1);font-size:12px;
  letter-spacing:.2em;text-transform:uppercase;color:rgba(255,255,255,.45);
  max-width:1080px;margin:0 auto}

@media (prefers-reduced-motion:reduce){
  .haze i,.tint i,.smoke,.cue{animation:none!important}
  .rev{transition:none;opacity:1;transform:none}
  /* Banner layers are active at every breakpoint, so they need the opt-out here
     rather than only in the mobile block. Both banks visible and still. */
  .b-smoke,.b-beam-a,.b-beam-b{animation:none}
  .b-beam-a,.b-beam-b{opacity:.75}
  .route,.route-h,.route-d,.route-x,.nav ul a{transition:none}
}

/* Mobile performance tier. Desktop rendering is untouched — every rule below is
   scoped to the media query. The stage composites roughly two dozen full-viewport
   layers, most of them mix-blend-mode:screen; at 3x device pixel ratio the layer
   set alone exhausts what a phone will hold. Cut the layer count, not the frame
   rate — the lerp smoothing depends on running at display rate. */
@media (max-width:820px){
  /* Every live stage layer, in one rule. The hero is painted by the stage image
     alone — the haze, tint, beams, smoke and crowd are all already in it, so
     drawing them live means compositing a blended, filtered copy of something
     the photograph already shows.
     .beam and .src stay listed even though the mobile branch no longer builds
     them: --k is only ever set by the engine, so if they returned, their
     opacity calc() would be invalid at computed-value time and fall back to 1.
     .lights goes too — it toggles a rig that does not exist on a phone, and a
     dead button is worse than no button. */
  .haze,.tint,.smoke,.beams,.beam,.src,.truss,.crowd,.grain,.blinder,.lg-glow,.lights{
    display:none;
  }

  /* No scroll-linked animation on mobile. iOS Safari defers scroll events during
     momentum scrolling, so anything driven from scrollY stutters however cheap the
     per-frame work is. The stage becomes an ordinary section and the page scrolls
     natively. svh rather than vh: the small-viewport unit is stable while the
     Safari toolbar collapses, which removes the mid-scroll jump at source. */
  .stage{position:relative;inset:auto;height:100vh;height:100svh;background-image:none}

  .mstage{display:block;position:absolute;inset:0;overflow:hidden}
  .track{display:none}
  /* The gradient bleed assumed a fixed stage underneath. */
  main{background:var(--ink)}
  /* Driven by --total, which is never written on mobile. */
  .bar{display:none}

  /* A keyframe animation with `both` fill holds the start state through its own
     delay and runs when the element is first rendered, whatever the load order.
     A transition needs the pre-transition state painted first, which is a race we
     kept losing on a phone. No JS touches .lockup on mobile. */
  .lockup{opacity:0;animation:m-reveal 1.15s cubic-bezier(.2,.7,.2,1) .35s both}
  /* Neutralise the phase-driven transforms and opacities inside the lockup, or
     they sit at their start positions for ever — the X translated 160px right and
     230px up, off the mark. Note .xrow i, not .xrow: the opacity is on the
     children, so targeting the container alone would leave the three X marks
     invisible. */
  .strap,.xrow i,.mark .lg-word,.mark .lg-x{opacity:1;transform:none}
  /* The per-mark offsets are set on :nth-child, which outranks .xrow i, so they
     need matching specificity or the three marks stay stepped down the page. */
  .xrow i:nth-child(1),.xrow i:nth-child(2),.xrow i:nth-child(3){transform:none}
}

/* The reveal is the one piece of motion added for mobile, so it needs the same
   opt-out as the rest. Ordered after the block above so it wins. */
@media (max-width:820px) and (prefers-reduced-motion:reduce){
  .lockup{animation:none;opacity:1}
  /* Both banks visible and still. */
  .m-bg,.m-smoke,.m-beam-a,.m-beam-b{animation:none}
  .m-beam-a,.m-beam-b{opacity:.75}
}

@keyframes m-breathe{from{transform:scale(1)}to{transform:scale(1.06)}}
@keyframes m-drift{
  from{transform:translate3d(0,0,0)}
  to{transform:translate3d(-14%,-4%,0)}
}
@keyframes m-reveal{
  from{opacity:0;transform:translate3d(-50%,-50%,0) scale(.93)}
  to{opacity:1;transform:translate3d(-50%,-50%,0) scale(1)}
}
@keyframes m-bank-a{0%,100%{opacity:1;transform:rotate(-1.1deg)}
                    50%{opacity:.34;transform:rotate(1.1deg)}}
@keyframes m-bank-b{0%,100%{opacity:.34;transform:rotate(1.1deg)}
                    50%{opacity:1;transform:rotate(-1.1deg)}}
