/* ============================================================
   MOMENTZ — "Where music takes over!"
   Retro day clubbing · Bonzai / Cherry Moon / 93–05

   Visual language lifted from the event's own posters:
     · true black ground, single dominant accent per edition
     · chrome/bevelled logotype with a lens-flare sweep
     · ink-in-water smoke (pre-rendered PNG masks, so they retint
       with --accent — see §3)
     · two-tone uppercase lines (white / accent, alternating)
     · hard edges — no rounded glass cards anywhere
   ============================================================ */

/* ── 1. TOKENS ───────────────────────────────────────────── */
:root{
  --void:      #000;
  --off:       #050608;
  --panel:     #0A0D10;

  /* teal is the house colour; each edition overrides --accent */
  --teal:      #24E3D0;
  --teal-lo:   #0C6E66;
  --teal-hi:   #B8FFF6;
  --orange:    #FF7A18;
  --orange-hi: #FFD2A3;
  --purple:    #A855F7;
  --purple-hi: #E9D5FF;

  --accent:    var(--teal);
  --accent-hi: var(--teal-hi);
  --accent-lo: var(--teal-lo);

  --white:     #FFF;
  --text:      #D7E3E6;
  --muted:     #93A7AC;   /* 7.1:1 on --void */
  --dim:       #6E8288;   /* 4.6:1 on --void */
  --rule:      rgba(255,255,255,.14);

  /* type */
  --display:'Archivo',ui-sans-serif,system-ui,sans-serif;
  --techno:'Michroma',var(--display);

  --fs-xs:.6875rem; --fs-sm:.8125rem; --fs-base:.9375rem;
  --fs-lg:1.0625rem;
  --fs-h3:clamp(1.25rem,.9rem + 1.5vw,1.75rem);
  --fs-h2:clamp(2rem,1.1rem + 4.4vw,4.25rem);

  /* rhythm */
  --s-1:4px;  --s-2:8px;  --s-3:12px; --s-4:16px; --s-5:24px;
  --s-6:32px; --s-7:48px; --s-8:64px; --s-9:96px; --s-10:128px;

  --e-out:cubic-bezier(.2,.8,.25,1);
  --t-fast:140ms; --t-mid:240ms; --t-slow:420ms;

  --z-bg:0; --z-fx:2; --z-content:10; --z-nav:40; --z-top:100; --z-preload:1000;
  --wrap:1180px;
  --nav-h:68px;
}

/* per-edition accent swaps */
.t-teal  {--accent:var(--teal);  --accent-hi:var(--teal-hi);  --accent-lo:var(--teal-lo)}
.t-purple{--accent:var(--purple);--accent-hi:var(--purple-hi);--accent-lo:#4C1D95}
.t-orange{--accent:var(--orange);--accent-hi:var(--orange-hi);--accent-lo:#7C3208}

/* ── 2. RESET ────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box}
*{margin:0}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:calc(var(--nav-h) + 12px)}
body{
  min-height:100dvh; background:var(--void); color:var(--text);
  font-family:var(--display); font-size:var(--fs-base); font-weight:500;
  line-height:1.6; -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img,svg{display:block;max-width:100%;height:auto}
button,input{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
a{color:inherit;text-decoration:none}
ul,ol{list-style:none;padding:0}
::selection{background:var(--accent);color:#000}
svg[viewBox]:not(.logo){stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}

:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

.skip-link{
  position:fixed;top:8px;left:8px;z-index:var(--z-top);
  padding:12px 16px;background:var(--accent);color:#000;font-weight:800;
  text-transform:uppercase;letter-spacing:.1em;font-size:var(--fs-sm);
  transform:translateY(-200%);transition:transform var(--t-fast) var(--e-out);
}
.skip-link:focus{transform:translateY(0)}

/* ── 3. SMOKE / FX ───────────────────────────────────────── */
/* Pre-rendered greyscale+alpha PNGs used as a MASK, so --accent tints them.
   These were feTurbulence SVG filters; the browser had to rasterise the filter
   before the hero could paint, and re-rasterised it as the scale() animation
   ran — long main-thread tasks that also stalled the hero. A raster mask
   is resampled by the compositor instead. The source is only 160px: it scales
   up soft, which is exactly what smoke should look like.
   Never put a CSS filter:blur() or an SVG filter back on these. */
.smoke{
  /* -34% => 1.68x the parent. Sized so the layers stay covering at their
     SMALLEST scale; see tools/check-smoke-coverage.py. */
  position:absolute; inset:-34%; pointer-events:none; z-index:var(--z-bg);
  background:var(--accent);
  -webkit-mask-size:cover; mask-size:cover;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
}

.smoke--a{opacity:.72; -webkit-mask-image:url("../assets/smoke-a.png"); mask-image:url("../assets/smoke-a.png"); animation:smokeA 22s ease-in-out infinite}
.smoke--b{opacity:.44; -webkit-mask-image:url("../assets/smoke-b.png"); mask-image:url("../assets/smoke-b.png"); animation:smokeB 31s ease-in-out infinite}
.smoke--c{opacity:.28; -webkit-mask-image:url("../assets/smoke-c.png"); mask-image:url("../assets/smoke-c.png"); animation:smokeC 39s ease-in-out infinite}

/* Motion budget, in order of cost:
     opacity  - free, and the most perceptible change of all
     scale    - free above 1.0 (worst-case coverage is at the SMALLEST scale)
     translate- costs coverage linearly
     rotate   - costs the most, because these layers are rectangles: under
                rotation the SHORT side must span the parent's LONG side
   So the life comes from a big scale swing and opacity breathing, with the
   translate/rotate kept modest. An earlier pass had +/-7deg and 3% drift over
   48s, which measured as 0% of pixels changed over 20s - technically animating,
   visually dead. The three durations are coprime-ish so the layers keep sliding
   out of phase instead of pulsing together.
   Re-run tools/check-smoke-coverage.py after touching any value here. */
@keyframes smokeA{
  0%,100%{transform:translate3d(-4%,3%,0) rotate(-9deg) scale(1.00); opacity:.48}
  50%    {transform:translate3d(4%,-3%,0) rotate(8deg) scale(1.42); opacity:.88}
}
@keyframes smokeB{
  0%,100%{transform:translate3d(4%,-3%,0) rotate(8deg) scale(1.38); opacity:.28}
  50%    {transform:translate3d(-4%,3%,0) rotate(-9deg) scale(1.00); opacity:.55}
}
@keyframes smokeC{
  0%,100%{transform:translate3d(-3%,-3%,0) rotate(-7deg) scale(1.06); opacity:.18}
  50%    {transform:translate3d(4%,3%,0) rotate(9deg) scale(1.45); opacity:.38}
}

/* dark core so the smoke reads as ink-in-water, not fog */
.vignette{
  position:absolute; inset:0; pointer-events:none; z-index:var(--z-fx);
  /* centre scrim is translucent, not solid — it protects the type without
     wiping out the ink behind it; outer ramp keeps the frame anchored in black */
  background:radial-gradient(ellipse 68% 56% at 50% 47%,rgba(0,0,0,.80) 0%,rgba(0,0,0,.42) 48%,transparent 74%),
             radial-gradient(ellipse 130% 92% at 50% 50%,transparent 32%,#000 96%);
}

/* Film grain.
   NO mix-blend-mode here: a fixed, full-viewport blended layer re-composites the
   entire page on every scroll frame. Plain alpha is visually near-identical on
   a black ground and costs nothing.
   A CRT scanline layer used to sit alongside this. It painted ABOVE the hero
   (isolation:isolate on .hero keeps its children in a lower stacking context)
   and striped the logotype, which read as a rendering fault on a crisp vector
   mark. Removed deliberately — don't reintroduce it over the wordmark. */

.grain{
  position:fixed; inset:0; pointer-events:none; z-index:var(--z-fx);
  opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* phones: drop the scanline layer entirely — invisible at 3x DPR, not free */
@media (max-width:900px){
  .smoke--c{display:none}
}

.scroll-progress{
  position:fixed;top:0;left:0;height:2px;width:100%;
  transform:scaleX(0);transform-origin:0 50%;
  background:var(--accent);box-shadow:0 0 12px var(--accent);
  z-index:var(--z-top);will-change:transform;
}

/* ── 4. LOGOTYPE (chrome + flare) ────────────────────────── */
.logo-wrap{
  position:relative; display:block; isolation:isolate;
  --logo-mask:url("../assets/momentz-logo.svg");   /* used by the flare sweep below */
}
/* The wordmark ships as a real <img> so it always renders — if the chrome
   overlay ever fails to load, you get the plain white logotype rather than a
   hole where the hero used to be. The chrome ramp sits on top, masked to the
   same artwork, and is built from --accent so it retints per edition. */
.logo__base{width:100%}
/* A separate copy of the spiral, parked exactly over the wordmark's own O.
   Only used by the intro: it spins up like a record, then the wordmark unfurls
   out of it. 25.55% is the O's centre in the master artwork; the crop is square
   with a side equal to the logo's height, so aspect-ratio:1 + height:100% lands
   it on the nose. Both numbers come from tools/vectorize-logo.py — if the
   wordmark is ever redrawn, re-read them from its output. */
.logo__spin{
  position:absolute; top:0; left:25.55%; height:100%; aspect-ratio:1;
  transform:translateX(-50%); transform-origin:50% 50%;
  background:var(--accent-hi); opacity:0; pointer-events:none;
  -webkit-mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
          mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
}
.logo__chrome{
  position:absolute; inset:0; pointer-events:none;
  /* The bevel's dark band must stay well clear of the near-black hero, or the
     middle of every letter merges into the background and the wordmark reads as
     two disconnected halves — which looked like the flare animation was broken.
     --accent-lo (#7C3208 on orange) sat at ~2x the background luminance; this
     keeps it at ~4.5x while still reading as a bevel. */
  background:linear-gradient(180deg,#fff 5%,var(--accent-hi) 24%,var(--accent) 44%,
             color-mix(in srgb,var(--accent) 80%,#000) 50%,var(--accent) 56%,
             var(--accent-hi) 76%,#fff 95%);
  -webkit-mask:url("../assets/momentz-logo.svg") center/contain no-repeat;
          mask:url("../assets/momentz-logo.svg") center/contain no-repeat;
}

/* lens-flare streak sweeping across the mark, like the posters */
.logo-wrap::after{
  content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.85) 48%,
             var(--accent-hi) 51%,rgba(255,255,255,.85) 54%,transparent 64%);
  -webkit-mask-image:var(--logo-mask); mask-image:var(--logo-mask);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
  background-size:280% 100%; background-position:120% 0;
  mix-blend-mode:screen; opacity:.9;
  animation:flare 7s var(--e-out) infinite;
}
@keyframes flare{
  0%,62%{background-position:150% 0}
  86%,100%{background-position:-70% 0}
}

/* Pause gate — MUST come after the `animation:` shorthands above, which reset
   animation-play-state to running. JS adds .is-live to a section while it is
   on screen; everything else stops compositing. */
.smoke,.watermark,.logo-wrap::after{animation-play-state:paused}
.is-live .smoke,.is-live .watermark,.is-live .logo-wrap::after{animation-play-state:running}

/* ── 4b. THE DEVICE ──────────────────────────────────────── */
/* The spiral out of the O. This is the brand's one ownable shape, so it does
   the work everywhere a generic dingbat would otherwise creep in: section
   markers, ticker separators, watermarks. Driven as a mask so it inherits
   --accent and retints with the edition. */
.mark{
  display:inline-block; flex:none; width:1em; height:1em;
  background:currentColor; vertical-align:-.12em;
  -webkit-mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
          mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
}
.watermark{
  position:absolute; pointer-events:none; z-index:var(--z-bg);
  aspect-ratio:1; background:var(--accent); opacity:.05;
  -webkit-mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
          mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
  animation:spin 90s linear infinite;   /* it's a record. it turns. */
}
@keyframes spin{to{transform:rotate(360deg)}}

/* ── 5. TYPOGRAPHY ───────────────────────────────────────── */
.techno{
  font-family:var(--techno); font-weight:400;
  text-transform:uppercase; letter-spacing:.16em;
  font-size:var(--fs-xs); line-height:1.5;
}
.kicker{
  font-family:var(--techno); font-size:var(--fs-xs); font-weight:400;
  letter-spacing:.2em; text-transform:uppercase; color:var(--accent);
  display:flex; align-items:center; gap:var(--s-3); margin-bottom:var(--s-5);
}
.kicker::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--accent),transparent)}
.kicker--center{justify-content:center}
.kicker--center::after{display:none}
.kicker--center::before{
  content:''; width:1.15em; height:1.15em; flex:none; background:var(--accent);
  -webkit-mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
          mask:url("../assets/momentz-spiral.svg") center/contain no-repeat;
}

.h2{
  font-family:var(--display); font-weight:900; font-stretch:118%;
  font-size:var(--fs-h2); line-height:.92; letter-spacing:-.02em;
  text-transform:uppercase; text-wrap:balance;
}
.h3{font-weight:800;font-size:var(--fs-h3);line-height:1.1;text-transform:uppercase;letter-spacing:-.01em}

/* the poster's alternating white / accent lines */
.alt{color:var(--accent)}
.chrome{
  background:linear-gradient(180deg,#fff 4%,var(--accent-hi) 20%,var(--accent) 40%,
            var(--accent-lo) 50%,var(--accent) 60%,var(--accent-hi) 80%,#fff 96%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
  filter:drop-shadow(0 0 22px color-mix(in srgb,var(--accent) 55%,transparent));
}

.lede{font-size:var(--fs-lg);color:var(--text);max-width:46ch;text-wrap:pretty}
.body{color:var(--muted);max-width:54ch;text-wrap:pretty}
.body + .body{margin-top:var(--s-4)}
.center{text-align:center;margin-inline:auto}

/* ── 6. LAYOUT ───────────────────────────────────────────── */
.wrap{width:min(100% - 2*var(--s-5),var(--wrap));margin-inline:auto}
@media (min-width:768px){.wrap{width:min(100% - 2*var(--s-7),var(--wrap))}}

.section{position:relative;z-index:var(--z-content);padding-block:var(--s-8)}
@media (min-width:768px){.section{padding-block:var(--s-8)}}

.rule{
  display:flex;align-items:center;justify-content:center;gap:var(--s-4);
  color:var(--accent);font-size:.7rem;
}
.rule::before,.rule::after{content:'';height:1px;flex:1;max-width:220px}
.rule::before{background:linear-gradient(90deg,transparent,var(--accent))}
.rule::after{background:linear-gradient(90deg,var(--accent),transparent)}

/* small utilities — used sparingly, keeps markup free of inline styles */
.above{position:relative;z-index:var(--z-content)}
.mt-2{margin-top:var(--s-2)}
.mt-4{margin-top:var(--s-4)}
.mt-5{margin-top:var(--s-5)}
.narrow{max-width:18ch}

/* ── 7. BUTTONS ──────────────────────────────────────────── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  min-height:52px;padding:0 var(--s-6);
  font-family:var(--display);font-weight:800;font-size:var(--fs-sm);
  text-transform:uppercase;letter-spacing:.12em;
  clip-path:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px);
  transition:background var(--t-mid),color var(--t-mid),box-shadow var(--t-mid),transform var(--t-fast) var(--e-out);
}
.btn svg{width:17px;height:17px;stroke-width:2.4;transition:transform var(--t-mid) var(--e-out)}
.btn--sm{min-height:44px;padding:0 var(--s-5);font-size:var(--fs-xs)}

/* Hover is a straight crossfade, same register as .btn--ghost below: the fill
   warms, a pale keyline draws itself inside the notch and the glow comes up.
   Nothing moves except the arrow. */
.btn--primary{
  background:var(--accent);color:#000;
  box-shadow:0 0 0 rgba(0,0,0,0), inset 0 0 0 0 rgba(0,0,0,0);
}
.btn--primary:hover,.btn--primary:focus-visible{
  /* only a touch towards --accent-hi — --orange-hi is a pale peach, and much
     past this the fill desaturates into sherbet instead of reading hotter */
  background:color-mix(in srgb,var(--accent-hi) 22%,var(--accent));
  /* Pale, not black: a dark keyline sits against a dark page, so it reads as
     the button shrinking rather than as an outline. Drawn as an inset shadow,
     not a border — a real border would resize the button and the clip-path
     notch would jump with it. */
  box-shadow:0 0 40px color-mix(in srgb,var(--accent) 65%,transparent),
             inset 0 0 0 2px rgba(255,255,255,.6);
}
.btn--primary:hover svg,
.btn--primary:focus-visible svg{transform:translateX(4px)}
.btn--primary:active{transform:translateY(1px)}

.btn--ghost{border:1px solid var(--rule);color:var(--white)}
.btn--ghost:hover{border-color:var(--accent);color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}
.btn--ghost:active{transform:translateY(1px)}

/* ── 8. NAV ──────────────────────────────────────────────── */
.nav{
  position:fixed;inset:0 0 auto;z-index:var(--z-nav);
  padding-top:env(safe-area-inset-top);
  border-bottom:1px solid transparent;
  transition:background var(--t-mid),border-color var(--t-mid);
}
.nav.is-stuck{background:rgba(0,0,0,.86);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-bottom-color:var(--rule)}
.nav__inner{
  width:min(100% - 2*var(--s-5),var(--wrap));margin-inline:auto;
  height:var(--nav-h);display:flex;align-items:center;gap:var(--s-5);
}
@media (min-width:768px){.nav__inner{width:min(100% - 2*var(--s-7),var(--wrap))}}

.nav__logo{width:150px;flex:none;color:var(--white);z-index:2;display:flex;align-items:center;min-height:44px}
.nav__links{display:none;margin-inline:auto;gap:var(--s-6)}
@media (min-width:980px){.nav__links{display:flex}}
.nav__links a{
  font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.14em;
  text-transform:uppercase;color:var(--dim);position:relative;
  display:inline-flex;align-items:center;min-height:44px;
  transition:color var(--t-fast);
}
.nav__links a::after{
  content:'';position:absolute;left:0;bottom:0;width:100%;height:1px;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  transition:transform var(--t-mid) var(--e-out);
}
.nav__links a:hover,.nav__links a.is-active{color:var(--accent)}
.nav__links a:hover::after,.nav__links a.is-active::after{transform:scaleX(1)}

.nav__actions{display:flex;align-items:center;gap:var(--s-3);margin-left:auto}
@media (min-width:980px){.nav__actions{margin-left:0}}
.nav__toggle{width:44px;height:44px;display:grid;place-content:center;gap:5px;flex:none}
@media (min-width:980px){.nav__toggle{display:none}}
.nav__toggle span{display:block;width:22px;height:2px;background:var(--white);transition:transform var(--t-mid) var(--e-out),opacity var(--t-fast)}
.nav__toggle[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.nav__toggle[aria-expanded="true"] span:nth-child(2){opacity:0}
.nav__toggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

@media (max-width:979px){
  .nav__links{
    position:fixed;inset:0;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:var(--s-5);
    background:rgba(0,0,0,.96);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);
    opacity:0;visibility:hidden;transition:opacity var(--t-mid),visibility var(--t-mid);
  }
  .nav__links.is-open{opacity:1;visibility:visible}
  .nav__links a{font-family:var(--display);font-weight:900; font-stretch:112%;font-size:1.9rem;color:var(--white);letter-spacing:.02em}
}

/* ── 9. HERO ─────────────────────────────────────────────── */
.hero{
  position:relative;min-height:100dvh;display:grid;place-items:center;
  /* Biased upward on purpose. With symmetric padding the wordmark's optical
     centre lands at ~42% of the viewport, which reads bottom-heavy; a poster
     hangs its logotype higher. Extra space is pushed to the bottom, where the
     scroll cue lives. */
  padding:calc(var(--nav-h) + var(--s-3)) 0 var(--s-10);
  text-align:center;overflow:hidden;isolation:isolate;
}
.hero__inner{position:relative;z-index:var(--z-content);width:min(100% - 2*var(--s-5),980px)}

.hero__meta{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;
  gap:var(--s-2) var(--s-6);margin-bottom:var(--s-6);
  font-family:var(--display);font-weight:800;text-transform:uppercase;
  font-size:clamp(.95rem,2.6vw,1.5rem);letter-spacing:.02em;line-height:1.15;
}
.hero__meta b{font-weight:900; font-stretch:112%;color:var(--white)}
.hero__meta span{color:var(--accent)}

.hero__logo{width:min(100%,880px);margin-inline:auto;color:var(--white)}
.hero__tag{
  margin-top:var(--s-5);font-family:var(--display);font-weight:800;
  font-size:clamp(.95rem,2.8vw,1.4rem);text-transform:uppercase;letter-spacing:.04em;
  display:flex;flex-wrap:wrap;justify-content:center;gap:var(--s-2) var(--s-5);
}
.hero__tag em{font-style:normal;color:var(--accent)}

.hero__cta{display:flex;flex-wrap:wrap;gap:var(--s-3);justify-content:center;margin-top:var(--s-7)}

.scroll-cue{
  position:absolute;left:50%;bottom:calc(var(--s-4) + env(safe-area-inset-bottom));
  translate:-50% 0;z-index:var(--z-content);display:grid;gap:var(--s-2);justify-items:center;
  font-family:var(--techno);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);
}
.scroll-cue:hover{color:var(--accent)}
.scroll-cue__line{width:1px;height:34px;background:linear-gradient(var(--accent),transparent);overflow:hidden}
.scroll-cue__line::after{content:'';display:block;width:1px;height:12px;background:var(--accent);animation:cue 1.8s var(--e-out) infinite}
@keyframes cue{0%{transform:translateY(-12px)}70%,100%{transform:translateY(34px)}}

/* ── 10. TICKER ──────────────────────────────────────────── */
.ticker{
  position:relative;z-index:var(--z-content);
  border-block:1px solid var(--rule);background:#000;
  padding-block:var(--s-3);overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
.ticker__track{display:flex;width:max-content;animation:marquee 30s linear infinite}
.ticker:hover .ticker__track{animation-play-state:paused}
.ticker__group{display:flex;align-items:center;gap:var(--s-5);padding-right:var(--s-5);white-space:nowrap}
.ticker b{font-family:var(--display);font-weight:900; font-stretch:112%;font-size:clamp(.9rem,2.4vw,1.3rem);text-transform:uppercase;letter-spacing:.02em;color:var(--white)}
.ticker span{font-family:var(--techno);font-size:clamp(.6rem,1.6vw,.8rem);letter-spacing:.18em;text-transform:uppercase;color:var(--accent)}
.ticker .mark{color:var(--accent);width:.9rem;height:.9rem;opacity:.8}
@keyframes marquee{to{transform:translateX(-50%)}}

/* ── 10b. EDITION IDENTITY ───────────────────────────────── */
/* The series is the brand: each edition owns a colour, and the whole site
   wears whichever one is current. Swapping .t-teal / .t-purple / .t-orange on
   <body> retints everything — smoke, chrome logotype, device, buttons, rules. */
.section--clip{overflow:hidden}

.poster__num{
  position:absolute; top:var(--s-4); left:var(--s-4);
  font-family:var(--techno); font-size:10px; letter-spacing:.14em; color:var(--accent);
}


.watermark--footer{left:-8%; bottom:-30%; width:min(38%,360px); opacity:.04}

/* ── 11. AFFICHE — line-up IS the running order ──────────── */
/* One block, not a "line-up section" plus a "timetable section" listing the
   same eight names twice. Set times sit as small tags beside the name. */
.band{overflow:hidden}
.band + .band{border-top:1px solid var(--rule)}

.bill{margin-top:var(--s-6); border-top:1px solid var(--rule)}
/* One column. There is no timetable — the time and genre columns were removed
   with the invented set times, so this is a bill of names, the way the posters
   set it: stacked, centred, alternating white / accent. */
.bill__row{
  padding:var(--s-3) 0; border-bottom:1px solid var(--rule); text-align:center;
}
@media (min-width:640px){ .bill__row{padding:var(--s-4) 0} }
.bill__name{
  white-space:nowrap; font-weight:900; font-stretch:112%; text-transform:uppercase;
  line-height:.92; letter-spacing:-.025em;
  font-size:clamp(1.5rem,6vw,3.4rem); color:var(--white);
}
/* The headliner is row 1, so the alternation counts from row 2. */
.bill__row:nth-child(odd):not(.bill__row--hl) .bill__name{color:var(--accent)}
.bill__row--hl .bill__name{
  font-size:clamp(1.8rem,7.5vw,4.4rem);
  background:linear-gradient(180deg,#fff 12%,var(--accent-hi) 45%,var(--accent) 88%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.bill__guest{
  margin-top:var(--s-4); text-align:center;
  font-family:var(--techno); font-size:var(--fs-xs); letter-spacing:.2em;
  text-transform:uppercase; color:var(--dim);
}
.bill__guest b{color:var(--white); font-family:var(--display); font-weight:900; font-stretch:112%; letter-spacing:.02em}

/* ── 13. EDITIONS ────────────────────────────────────────── */
.editions{display:grid; gap:var(--s-4); margin-top:var(--s-6)}
@media (min-width:760px){ .editions{grid-template-columns:repeat(3,1fr)} }
.poster{
  position:relative; aspect-ratio:3/4; overflow:hidden; isolation:isolate;
  border:1px solid var(--rule); background:#000;
  display:grid; place-items:center; text-align:center;
  transition:border-color var(--t-mid), transform var(--t-slow) var(--e-out);
}
.poster:hover{border-color:var(--accent); transform:translateY(-4px)}
.poster__body{position:relative; z-index:var(--z-content); padding:var(--s-5); width:100%}
.poster__date{font-weight:900; font-stretch:112%; text-transform:uppercase; font-size:clamp(.95rem,2.4vw,1.2rem); line-height:1.1; color:var(--white)}
.poster__date span{display:block; color:var(--accent)}
.poster__venue{font-family:var(--techno); font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--muted); margin-top:var(--s-2)}
.poster__logo{width:82%; margin:var(--s-5) auto var(--s-3); color:var(--white)}
.poster__tag{font-weight:800; font-size:10px; text-transform:uppercase; letter-spacing:.08em; color:var(--accent)}
.poster__bill{margin-top:var(--s-4); font-weight:800; font-size:clamp(.62rem,1.5vw,.75rem); text-transform:uppercase; line-height:1.7; color:var(--muted); letter-spacing:.04em}
.poster__bill b{color:var(--white)}

/* ── 14. CLOSE — tickets + small print, side by side ─────── */
.close{display:grid; gap:var(--s-7)}
@media (min-width:900px){ .close{grid-template-columns:minmax(0,26rem) 1fr; gap:var(--s-9); align-items:start} }

.buy{border:1px solid var(--accent); padding:var(--s-6); text-align:center}
.buy__price{
  font-weight:900; font-stretch:112%; line-height:.9; letter-spacing:-.04em; color:var(--white);
  font-size:clamp(3.4rem,9vw,5rem); font-variant-numeric:tabular-nums;
}
.buy__price span{font-size:.42em; vertical-align:super; color:var(--accent); margin-right:2px}
.buy__phase{
  font-family:var(--techno); font-size:var(--fs-xs); letter-spacing:.18em;
  text-transform:uppercase; color:var(--accent); margin-top:var(--s-2);
}
.buy__cta{width:100%; margin-top:var(--s-5)}
.buy__note{margin-top:var(--s-4); font-size:var(--fs-xs); color:var(--dim); line-height:1.7}
.buy__note s{color:var(--dim); opacity:.7}

.dl{margin-top:var(--s-5); border-top:1px solid var(--rule)}
.dl dt{
  font-family:var(--techno); font-size:10px; letter-spacing:.18em;
  text-transform:uppercase; color:var(--dim); padding-top:var(--s-4);
}
.dl dd{
  font-weight:800; text-transform:uppercase; font-size:var(--fs-base);
  letter-spacing:-.005em; color:var(--white);
  padding-bottom:var(--s-4); border-bottom:1px solid var(--rule);
}
.dl dd small{
  display:block; margin-top:3px; font-weight:500; text-transform:none;
  letter-spacing:0; color:var(--muted); font-size:var(--fs-sm);
}
.dl a{color:var(--accent); text-decoration:underline; text-underline-offset:3px}
@media (min-width:560px){
  .dl{display:grid; grid-template-columns:9rem 1fr}
  .dl dt{border-bottom:1px solid var(--rule); padding-bottom:var(--s-4)}
}

/* ── 15. FOOTER ──────────────────────────────────────────── */
.footer{position:relative;z-index:var(--z-content);border-top:1px solid var(--rule);padding-block:var(--s-7) var(--s-5)}
.footer__top{display:grid;gap:var(--s-6);align-items:end;padding-bottom:var(--s-6);border-bottom:1px solid var(--rule)}
@media (min-width:760px){.footer__top{grid-template-columns:1fr minmax(0,22rem);gap:var(--s-8)}}
.footer__logo{display:flex;align-items:center;width:min(100%,240px);min-height:44px;color:var(--white)}
.footer__tag{margin-top:var(--s-3);font-weight:800;text-transform:uppercase;font-size:var(--fs-sm);letter-spacing:.04em;color:var(--accent)}

/* Dormant — the newsletter form is not in the markup (see the comment in the
   footer of index.html). Kept so restoring it is a markup-only change. */
.sub label{display:block;font-family:var(--techno);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);margin-bottom:8px}
.sub__row{display:flex;border:1px solid var(--rule)}
.sub__row:focus-within{border-color:var(--accent)}
.sub input{flex:1;min-width:0;min-height:46px;padding:0 var(--s-4);border:0;background:#000;color:var(--white)}
.sub input::placeholder{color:var(--dim)}
.sub input:focus{outline:none}
.sub input[aria-invalid="true"]{color:#FF9E9E}
.sub__btn{padding:0 var(--s-5);background:var(--accent);color:#000;font-weight:900; font-stretch:112%;font-size:var(--fs-sm);letter-spacing:.1em;text-transform:uppercase;transition:background var(--t-mid),box-shadow var(--t-mid),opacity var(--t-fast)}
.sub__btn:hover:not(:disabled),
.sub__btn:focus-visible:not(:disabled){
  background:color-mix(in srgb,var(--accent-hi) 22%,var(--accent));
  box-shadow:0 0 22px color-mix(in srgb,var(--accent) 55%,transparent),
             inset 0 0 0 2px rgba(255,255,255,.6);
}
.sub__btn:disabled{opacity:.5;cursor:default}
.sub__ok{grid-column:1/-1;font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.field__err{margin-top:6px;font-size:var(--fs-xs);color:#FF9E9E}

.footer__next-label{font-family:var(--techno);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);margin-bottom:8px}
.footer__next-body{font-size:var(--fs-sm);line-height:1.6;color:var(--dim)}
.footer__next-body a{color:var(--accent);text-decoration:underline;text-underline-offset:3px}

.footer__bottom{display:flex;flex-wrap:wrap;gap:var(--s-4) var(--s-6);align-items:center;padding-top:var(--s-5)}
.footer__bottom nav{display:flex;flex-wrap:wrap;gap:var(--s-4)}
.footer__bottom nav a{display:inline-flex;align-items:center;min-height:44px;font-family:var(--techno);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);transition:color var(--t-fast)}
.footer__bottom nav a:hover{color:var(--accent)}
.socials{display:flex;gap:8px;margin-left:auto}
.socials a{width:44px;height:44px;display:grid;place-content:center;border:1px solid var(--rule);color:var(--muted);transition:color var(--t-mid),border-color var(--t-mid),background var(--t-mid)}
.socials svg{width:17px;height:17px}
.socials a:hover{color:#000;border-color:var(--accent);background:var(--accent)}
.footer__legal{width:100%;font-family:var(--techno);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}

/* ── 16b. INTRO / FULL-PAGE LOADER ───────────────────────── */
/* The site loads behind a black screen showing only the logotype. The spiral
   spins up like a record, the wordmark unfurls out of it, then the whole
   lockup FLIPs into the hero's logo position while the black fades away —
   so it lands in place rather than cutting.

       0.00-0.80  spiral spins in, decelerating
       0.50-1.10  wordmark unfurls outward from the O
       1.10-1.65  black screen fades off
       1.15-1.75  logotype flies to the hero position (JS-measured, main.js §8)
       1.45-2.20  rest of the hero staggers in

   .preload is display:none by default and only shown under .intro, which the
   inline head script adds. No JS or reduced-motion => it never renders, so it
   can't trap the page behind a black screen. */
/* layout for .preload / .preload__logo lives INLINE in <head> — a loader can't
   depend on this file having arrived, or being fresh. Only its animation is here. */
.intro .preload{animation:preloadFade .55s var(--e-out) 1.10s both}

@keyframes preloadFade{from{background-color:#000}to{background-color:transparent}}

.intro .hero__inner > *:not(.hero__logo){animation:introRise .45s var(--e-out) both}
/* these MUST match the group selector's specificity (0,3,0) — the `animation`
   shorthand above resets animation-delay to 0, so a weaker selector here is
   silently ignored and the whole hero animates at once */
.intro .hero__inner > .hero__meta {animation-delay:1.45s}
.intro .hero__inner > .hero__tag  {animation-delay:1.52s}
.intro .hero__inner > .hero__cta  {animation-delay:1.59s}
.intro .scroll-cue{animation:introRise .45s var(--e-out) 1.72s both}

/* the clip goes on the artwork, NOT on .logo-wrap — the spinning spiral is a
   child of the wrap and would be clipped away with it */
.intro .logo__base,
.intro .logo__chrome{animation:introWipe .6s cubic-bezier(.22,1,.36,1) .5s both}
.intro .logo__spin  {animation:introSpin .8s cubic-bezier(.16,1,.3,1) both}

@keyframes introRise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
@keyframes introWipe{
  from{clip-path:inset(0 74.45% 0 25.55%)}
  to  {clip-path:inset(0 0 0 0)}
}
@keyframes introSpin{
  0%  {opacity:0;transform:translateX(-50%) rotate(-660deg) scale(.25)}
  15% {opacity:1}
  65% {opacity:1;transform:translateX(-50%) rotate(0deg) scale(1)}
  100%{opacity:0;transform:translateX(-50%) rotate(0deg) scale(1)}
}

/* ── 17. REVEAL ──────────────────────────────────────────── */
.reveal{opacity:0;transform:translateY(14px);transition:opacity 320ms var(--e-out),transform 320ms var(--e-out);transition-delay:calc(var(--d,0) * 35ms)}
.reveal.is-in{opacity:1;transform:none}

/* ── 18. REDUCED MOTION ──────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}
  .reveal{opacity:1 !important;transform:none !important}
  .grain{display:none}
  .ticker__track{animation:none;transform:none}
}
