/* ============================================================
   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

   Motion architecture (see js/main.js):
     · the hero is position:sticky and the rest of the page (.stage) scrolls
       up over it while the hero content recedes — one continuous scene
       instead of a hard cut at the ticker
     · pointer position feeds --mx / --my on .hero; the smoke and the logotype
       answer to it through the `translate` property, which composes with the
       keyframed `transform` on .smoke instead of fighting it
     · no animation library: CSS, the Web Animations API and one rAF loop
   ============================================================ */

/* ── 0. FONTS (self-hosted) ──────────────────────────────── */
/* Downloaded from Google Fonts' own woff2 subsets; served from assets/fonts so
   the critical path has no third-party hop and no EU data-transfer question.
   Archivo is the variable build: weight 400–900, width 62–125%. */
@font-face{font-family:'Archivo';font-style:normal;font-weight:400 900;font-stretch:62% 125%;font-display:swap;src:url("../assets/fonts/archivo-latin.woff2") format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Archivo';font-style:normal;font-weight:400 900;font-stretch:62% 125%;font-display:swap;src:url("../assets/fonts/archivo-latin-ext.woff2") format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Michroma';font-style:normal;font-weight:400;font-display:swap;src:url("../assets/fonts/michroma-latin.woff2") format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Michroma';font-style:normal;font-weight:400;font-display:swap;src:url("../assets/fonts/michroma-latin-ext.woff2") format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* ── 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:       #7A8E94;   /* 5.4:1 on --void — was #6E8288, lifted for the 12px labels */
  --rule:      rgba(255,255,255,.14);

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

  /* 12px floor. The audience skews 35–50; nothing on the page is set smaller. */
  --fs-xs:.75rem; --fs-sm:.8125rem; --fs-base:.9375rem;
  --fs-lg:1.0625rem;

  /* 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;

  /* pointer offset in the hero, −1…1, written by js/main.js §9 */
  --mx:0; --my:0;
  /* how far the stage has covered the hero, 0…1, written by §8 */
  --hp:0;
}

/* 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,video{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 512px: 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: the third smoke layer is invisible at 3x DPR, not free */
@media (max-width:900px){
  .smoke--c{display:none}
}

/* Pause / play for auto-moving content (ticker, photo strips). WCAG 2.2.2:
   anything that moves for more than five seconds needs a way to stop it that
   does not depend on hovering. The icon is drawn in CSS: two bars, or a
   triangle when paused. */
.pause{
  display:inline-flex;align-items:center;gap:8px;min-width:44px;min-height:44px;padding:0 10px;
  font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.16em;text-transform:uppercase;color:var(--dim);
  transition:color var(--t-fast);
}
.pause:hover{color:var(--accent)}
.pause span{position:relative;width:12px;height:12px;flex:none}
.pause span::before,.pause span::after{content:'';position:absolute;top:0;bottom:0;width:3px;background:currentColor;transition:all var(--t-fast)}
.pause span::before{left:1px}
.pause span::after{right:1px}
.pause[aria-pressed="true"] span::before{left:2px;width:0;height:0;bottom:auto;background:none;border-left:10px solid currentColor;border-top:6px solid transparent;border-bottom:6px solid transparent}
.pause[aria-pressed="true"] span::after{display:none}
.pause--ticker{position:absolute;right:var(--s-3);top:50%;translate:0 -50%;z-index:2;background:rgba(0,0,0,.7)}
.is-paused .ticker__track,.is-paused .strip__track{animation-play-state:paused !important}

.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;
}

/* ── 3b. CURSOR ──────────────────────────────────────────── */
/* Mounted by js/main.js and only on (pointer:fine). The native cursor is
   hidden through .has-cursor, which JS adds after the element is moving — so
   a script failure leaves the normal cursor in place. */
.cursor{
  position:fixed; left:0; top:0; z-index:var(--z-top); pointer-events:none;
  width:10px; height:10px; margin:-5px 0 0 -5px; border-radius:50%;
  background:var(--accent); opacity:0;
  transition:opacity var(--t-mid),width var(--t-mid) var(--e-out),height var(--t-mid) var(--e-out),
             margin var(--t-mid) var(--e-out),background var(--t-mid),border-color var(--t-mid);
  will-change:transform;
}
.has-cursor .cursor{opacity:1}
.has-cursor .cursor.is-off{opacity:0}
.cursor.is-hover{width:48px;height:48px;margin:-24px 0 0 -24px;background:transparent;border:1.5px solid var(--accent)}
.cursor.is-down{width:6px;height:6px;margin:-3px 0 0 -3px}
@media (pointer:fine){
  .has-cursor, .has-cursor a, .has-cursor button, .has-cursor [role="button"]{cursor:none}
}


/* ── 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,.strip__track,.hero__photo.is-on img{animation-play-state:paused}
.is-live .smoke,.is-live .watermark,.is-live .logo-wrap::after,.is-live .strip__track,.is-live .hero__photo.is-on img{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 ───────────────────────────────────────── */
.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);
}
/* the rule draws itself when the kicker reveals */
.kicker::after{
  content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--accent),transparent);
  transform-origin:0 50%;transition:transform .9s var(--e-out) .15s;
}
.kicker.reveal::after{transform:scaleX(0)}
.kicker.reveal.is-in::after{transform:scaleX(1)}
.kicker--center{justify-content:center;text-align: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;
}

/* ── 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)}

/* Everything after the hero rides on this. It is positioned and opaque so it
   paints OVER the sticky hero as it scrolls up, and the hero underneath fades
   out on --hp (js/main.js §8). */
.stage{position:relative;z-index:var(--z-content);background:var(--void)}
/* the accent hairline where the stage meets the hero */
.stage::before{
  content:'';position:absolute;left:0;right:0;top:-1px;height:1px;
  background:linear-gradient(90deg,transparent,var(--accent) 30%,var(--accent) 70%,transparent);
  opacity:.7;
}

/* small utilities — used sparingly, keeps markup free of inline styles */
.above{position:relative;z-index:var(--z-content)}

/* ── 7. BUTTONS ──────────────────────────────────────────── */
.btn{
  position:relative;
  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)}

/* FOCUS. The clip-path notch clips anything painted outside the polygon —
   including `outline` — so the default focus ring was invisible on every
   button. The ring is drawn INSIDE instead, as an inset shadow, which the
   clip leaves alone. */
.btn:focus-visible{outline:none}
.btn--primary:focus-visible{box-shadow:0 0 40px color-mix(in srgb,var(--accent) 65%,transparent),inset 0 0 0 3px #000,inset 0 0 0 5px var(--white)}
.btn--ghost:focus-visible{box-shadow:inset 0 0 0 2px var(--accent);color:var(--accent);border-color:var(--accent)}

/* 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{
  /* 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-4),var(--wrap));margin-inline:auto;
  height:var(--nav-h);display:flex;align-items:center;gap:var(--s-4);
}
@media (min-width:768px){.nav__inner{width:min(100% - 2*var(--s-7),var(--wrap));gap:var(--s-5)}}

.nav__logo{width:118px;flex:none;color:var(--white);z-index:2;display:flex;align-items:center;min-height:44px}
@media (min-width:480px){.nav__logo{width:150px}}
.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-2);margin-left:auto}
@media (min-width:768px){.nav__actions{gap:var(--s-3)}}
@media (min-width:980px){.nav__actions{margin-left:0}}

/* language toggle — EN / NL, the pressed one in accent */
.lang{
  display:flex;align-items:center;gap:2px;flex:none;
  font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.1em;color:var(--dim);
}
.lang button{
  min-width:34px;min-height:44px;padding:0 4px;color:var(--dim);
  transition:color var(--t-fast);
}
.lang button:hover{color:var(--white)}
.lang button[aria-pressed="true"]{color:var(--accent)}
.lang button:focus-visible{outline-offset:-4px}
/* narrow phones: a single button showing the NEXT language in the cycle
   (EN → NL → FR → EN; js/i18n.js marks it .is-next) — the logo, ticket button
   and burger otherwise overflow a 390px bar */
@media (max-width:479px){
  .lang span,.lang button:not(.is-next){display:none}
  .lang button{min-width:30px;color:var(--white)}
}

.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:2.2rem;color:var(--white);letter-spacing:.02em;
    opacity:0;transform:translateY(18px);transition:opacity var(--t-slow) var(--e-out),transform var(--t-slow) var(--e-out),color var(--t-fast);
  }
  .nav__links.is-open a{opacity:1;transform:none}
  .nav__links.is-open a:nth-child(2){transition-delay:60ms}
  .nav__links.is-open a:nth-child(3){transition-delay:120ms}
}

/* ── 9. HERO ─────────────────────────────────────────────── */
.hero{
  position:sticky;top:0;
  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;
}
/* the whole scene recedes as the stage covers it (--hp written by GSAP) */
.hero__inner{
  position:relative;z-index:var(--z-content);width:min(100% - 2*var(--s-5),980px);
  transform:translateY(calc(var(--hp) * -60px)) scale(calc(1 - var(--hp) * .12));
  opacity:calc(1 - var(--hp) * 1.15);
  will-change:transform,opacity;
}

/* pointer parallax — `translate` composes with the keyframed `transform`, so
   the drift keeps running underneath. Amplitudes are a few px only: the smoke
   inset has ~34% of slack but the keyframes already spend most of it. */
.hero .smoke--a{translate:calc(var(--mx) * 18px) calc(var(--my) * 12px + var(--hp) * -6%)}
.hero .smoke--b{translate:calc(var(--mx) * -12px) calc(var(--my) * -8px + var(--hp) * -10%)}
.hero .smoke--c{translate:calc(var(--mx) * 8px) calc(var(--my) * 6px + var(--hp) * -3%)}

/* MEDIA SLOT — the aftermovie. Sits under the smoke, which acts as a colour
   wash over it; the vignette keeps the type protected. Hidden until JS has a
   playable source and adds .has-media. */
.hero__media{position:absolute;inset:0;z-index:var(--z-bg);opacity:0;transition:opacity 1.2s var(--e-out)}
.hero.has-media .hero__media{opacity:1}
.hero__video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:saturate(.7) contrast(1.05)}
/* PHOTOS — three frames, one visible at a time, on a slow Ken Burns. They
   are pulled towards monochrome so the edition accent owns the colour; the
   smoke (screen-blended above) tints them. JS rotates .is-on (§6). */
.hero__photo{position:absolute;inset:0;opacity:0;transition:opacity 1.8s var(--e-out)}
.hero__photo img{width:100%;height:100%;object-fit:cover;filter:saturate(.35) contrast(1.12) brightness(.62)}
.hero__photo.is-on{opacity:1}
.hero__photo.is-on img{animation:kenburns 11s linear both}
.hero.has-video .hero__photo{display:none}
@keyframes kenburns{from{transform:scale(1.04) translate(0,0)}to{transform:scale(1.14) translate(-1.5%,1%)}}
.hero.has-media .smoke--a{opacity:.5;mix-blend-mode:screen}
.hero.has-media .smoke--b{opacity:.32;mix-blend-mode:screen}
.hero.has-media .vignette{
  background:radial-gradient(ellipse 68% 56% at 50% 47%,rgba(0,0,0,.86) 0%,rgba(0,0,0,.55) 48%,rgba(0,0,0,.2) 74%),
             radial-gradient(ellipse 130% 92% at 50% 50%,transparent 32%,#000 96%);
}

.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 time,.hero__meta-pair{display:inline-flex;gap:.5em;white-space:nowrap}
.hero__meta b{font-weight:900; font-stretch:112%;color:var(--white)}
.hero__meta span{color:var(--accent)}

/* logotype tilts towards the pointer — only once the intro has released it,
   because the FLIP handoff measures this box */
.hero__logo{width:min(100%,880px);margin-inline:auto;color:var(--white)}
html:not(.intro) .hero__logo{
  transform:perspective(1400px) rotateX(calc(var(--my) * -5deg)) rotateY(calc(var(--mx) * 7deg));
  transform-style:preserve-3d;
}
.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__tag span{color:var(--text)}

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

.hero__count{
  margin-top:var(--s-6);display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:var(--s-2) var(--s-4);
}
.hero__count-label{font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.2em;text-transform:uppercase;color:var(--dim)}
.hero__count-num{
  font-weight:900;font-stretch:112%;font-size:clamp(1.1rem,3vw,1.6rem);letter-spacing:.02em;
  color:var(--white);font-variant-numeric:tabular-nums;text-transform:uppercase;
}
.hero__count-num small{font-size:.6em;color:var(--accent);margin:0 .35em 0 .05em;font-weight:800}

.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:var(--fs-xs);letter-spacing:.2em;text-transform:uppercase;color:var(--dim);
  opacity:calc(1 - var(--hp) * 3);
}
.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(.75rem,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}
.watermark--footer{left:-8%; bottom:-30%; width:min(38%,360px); opacity:.04}

/* ── 11. AFFICHE — the bill ──────────────────────────────── */
.band{overflow:hidden}
/* Every section on the stage enters with a line that draws itself across the
   full width and a soft accent glow under it (js/main.js §3 adds .is-in). This
   replaces the static 1px rules between sections. */
.stage .section{isolation:isolate}
.stage .section::before{
  content:'';position:absolute;left:0;right:0;top:0;height:1px;z-index:var(--z-content);
  background:linear-gradient(90deg,transparent,var(--accent) 25%,var(--accent) 75%,transparent);
  transform:scaleX(0);transition:transform 1.4s var(--e-out);
}
.stage .section.is-in::before{transform:scaleX(1)}
.stage .section::after{
  content:'';position:absolute;left:0;right:0;top:0;height:220px;z-index:-1;pointer-events:none;
  background:radial-gradient(60% 100% at 50% 0,color-mix(in srgb,var(--accent) 18%,transparent),transparent 70%);
  opacity:0;transition:opacity 1.4s var(--e-out) .25s;
}
.stage .section.is-in::after{opacity:1}

.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.
   Rows slide in from alternating sides (js/main.js §10); each carries a faint
   index that surfaces on hover, and the name lifts into the accent. */
.bill__row{
  position:relative; padding:var(--s-3) 0; border-bottom:1px solid var(--rule); text-align:center;
  overflow:hidden; isolation:isolate;
}
@media (min-width:640px){ .bill__row{padding:var(--s-4) 0} }
/* a crowd shot wipes in behind the hovered name (--bg set by js §9 on first
   hover, so nothing loads until it is wanted). Darkened and desaturated so
   the name stays the subject and the accent stays the colour. */
.bill__row::before{
  content:'';position:absolute;inset:0;z-index:-1;
  background:var(--bg,none) center 40%/cover no-repeat;
  filter:saturate(.55) brightness(.8);
  clip-path:inset(0 100% 0 0);opacity:0;
  transition:clip-path .7s var(--e-out),opacity .4s;
}
.bill__row::after{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(90deg,#000 0%,transparent 22%,transparent 78%,#000 100%),linear-gradient(180deg,rgba(0,0,0,.35),rgba(0,0,0,.35));
  opacity:0;transition:opacity .4s;
}
@media (hover:hover){
  .bill__row:hover::before{clip-path:inset(0 0 0 0);opacity:1}
  .bill__row:hover::after{opacity:1}
}
.bill__num{
  position:absolute; left:0; top:50%; translate:0 -50%;
  font-family:var(--techno); font-size:var(--fs-xs); letter-spacing:.2em; color:var(--dim);
  opacity:0; transition:opacity var(--t-mid),translate var(--t-mid) var(--e-out);
}
.bill__row:hover .bill__num{opacity:1; translate:var(--s-3) -50%}
.bill__name{
  display:inline-block;
  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);
  transition:transform var(--t-slow) var(--e-out),color var(--t-mid),letter-spacing var(--t-slow) var(--e-out);
}
.bill__row:hover .bill__name{transform:scale(1.06);letter-spacing:-.01em;text-shadow:0 0 40px rgba(0,0,0,.9)}
/* 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:not(.bill__row--hl):hover .bill__name{color:var(--accent-hi)}
.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;
}
/* optional one-liner above the bill: who runs this and since when (content.json › story) */
.story{
  text-align:center;max-width:44ch;margin:calc(-1 * var(--s-2)) auto var(--s-6);
  font-weight:800;text-transform:uppercase;letter-spacing:.02em;
  font-size:clamp(1rem,2.2vw,1.25rem);line-height:1.35;color:var(--white);text-wrap:balance;
}
/* optional listen link under the bill (content.json › sound) */
.sound{display:flex;justify-content:center;margin-top:var(--s-6)}
.sound .btn{gap:12px}
.sound .mark{width:1.1em;height:1.1em;color:var(--accent)}
.sound svg{width:15px;height:15px}
.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}

/* ── 12. PHOTOS ──────────────────────────────────────────── */
/* Two counter-drifting strips of tiles and one wide frame. The strips are a
   plain CSS marquee on transform (compositor only); hover pauses them. Each
   strip holds two identical sets, so translating by -50% loops seamlessly. */
.gallery{padding-bottom:var(--s-9)}
.gallery__ctrl{display:flex;justify-content:center;margin:calc(-1 * var(--s-6)) 0 var(--s-5)}
.gallery__lede{
  text-align:center;max-width:34ch;margin:calc(-1 * var(--s-3)) auto var(--s-7);
  font-weight:800;text-transform:uppercase;letter-spacing:.02em;
  font-size:clamp(1rem,2.4vw,1.35rem);line-height:1.3;color:var(--white);text-wrap:balance;
}
.strip{overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.strip + .strip{margin-top:var(--s-3)}
.strip__track{display:flex;width:max-content;animation:strip 70s linear infinite}
.strip--b .strip__track{animation-duration:85s;animation-direction:reverse}
.strip:hover .strip__track{animation-play-state:paused}
.strip__set{display:flex;gap:var(--s-3);padding-right:var(--s-3)}
.strip img{
  height:clamp(150px,22vw,260px);width:auto;flex:none;
  filter:saturate(.85);transition:filter var(--t-mid),transform var(--t-slow) var(--e-out);
  clip-path:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px);
}
.strip img:hover{filter:saturate(1.05);transform:scale(1.03)}

.gallery__feat{
  margin:var(--s-7) auto 0;position:relative;overflow:hidden;aspect-ratio:16/8;
  clip-path:polygon(18px 0,100% 0,100% calc(100% - 18px),calc(100% - 18px) 100%,0 100%,0 18px);
  border:1px solid var(--rule);
}
.gallery__feat img{
  width:100%;height:120%;object-fit:cover;margin-top:-10%;
  translate:0 calc(var(--gp,0) * 10%);          /* scroll parallax, js §6 */
  filter:saturate(.9) contrast(1.05);
}
.gallery__feat figcaption{
  position:absolute;left:var(--s-5);bottom:var(--s-5);display:flex;align-items:center;gap:var(--s-2);
  font-family:var(--techno);font-size:var(--fs-xs);letter-spacing:.18em;text-transform:uppercase;color:var(--white);
  background:rgba(0,0,0,.6);padding:8px 12px;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
}
.gallery__feat .mark{color:var(--accent);width:1.1em;height:1.1em}
@media (max-width:640px){
  .gallery__feat{aspect-ratio:4/3}
  .gallery__feat figcaption{left:var(--s-3);bottom:var(--s-3)}
}

/* ── 13. EDITIONS ────────────────────────────────────────── */
/* Phone: a horizontal snap strip that bleeds to the screen edges, so three
   past posters cost one swipe instead of 1600px of scrolling.
   ≥760px: a three-up grid. */
.editions{
  display:flex; gap:var(--s-4); margin-top:var(--s-6);
  margin-inline:calc(-1 * var(--s-5)); padding-inline:var(--s-5); padding-bottom:var(--s-2);
  overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory; scroll-padding-inline:var(--s-5);
  scrollbar-width:none;
}
.editions::-webkit-scrollbar{display:none}
.editions .poster{flex:0 0 min(78%,320px); scroll-snap-align:center}
@media (min-width:760px){
  .editions{display:grid; grid-template-columns:repeat(3,1fr); margin-inline:0; padding:0; overflow:visible; perspective:1400px}
  .editions .poster{flex:none}
}
.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;
  /* --rx / --ry are the pointer tilt (js/main.js §6); they ease back to 0 */
  transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
  transition:border-color var(--t-mid), transform var(--t-slow) var(--e-out);
  transform-style:preserve-3d;
}
.poster:hover{border-color:var(--accent)}
.poster__body{position:relative; z-index:var(--z-content); padding:var(--s-5); width:100%}
.poster__num{
  position:absolute; top:var(--s-4); left:var(--s-4); z-index:var(--z-content);
  font-family:var(--techno); font-size:var(--fs-xs); letter-spacing:.14em; color:var(--accent);
}
.poster__state{
  position:absolute; top:var(--s-4); right:var(--s-4); z-index:var(--z-content);
  font-family:var(--techno); font-size:var(--fs-xs); letter-spacing:.14em; text-transform:uppercase; color:var(--dim);
}
.poster__state--next{color:#000; background:var(--accent); padding:3px 8px}
.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-day{display:block; color:var(--accent)}
.poster__venue{font-family:var(--techno); font-size:var(--fs-xs); 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:var(--fs-xs); text-transform:uppercase; letter-spacing:.08em; color:var(--accent)}
.poster__bill{margin-top:var(--s-4); font-weight:800; font-size:clamp(.75rem,1.5vw,.8rem); text-transform:uppercase; line-height:1.7; color:var(--muted); letter-spacing:.04em}
.poster__bill b{color:var(--white)}

/* MEDIA SLOT — real poster artwork replaces the CSS recreation */
.poster__media{position:absolute; inset:0; z-index:var(--z-fx); width:100%; height:100%; object-fit:cover; opacity:0; transition:opacity .8s var(--e-out)}
.poster.has-media .poster__media{opacity:1}
.poster.has-media .poster__body,.poster.has-media .smoke{display:none}
.poster.has-media .vignette{background:linear-gradient(180deg,rgba(0,0,0,.35),transparent 30%,transparent 70%,rgba(0,0,0,.35));z-index:calc(var(--z-fx) + 1)}
.poster.has-media:hover .poster__media{transform:scale(1.03)}
/* poster--photo keeps the typeset body over a darkened photo */
.poster--photo.has-media .poster__body{display:block}
.poster--photo.has-media .poster__media{opacity:.55;filter:saturate(.5) contrast(1.1)}
.poster--photo.has-media .vignette{display:block;background:radial-gradient(ellipse 70% 60% at 50% 50%,rgba(0,0,0,.72),rgba(0,0,0,.35) 60%,rgba(0,0,0,.6) 100%)}
.poster--photo.has-media:hover .poster__media{opacity:.7}
.poster__media{transition:opacity .8s var(--e-out),transform 1.2s var(--e-out)}

/* ── 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; position:relative; overflow:hidden}
.buy::before{
  content:''; position:absolute; inset:auto -30% -40% -30%; height:60%; z-index:-1; pointer-events:none;
  background:radial-gradient(ellipse at 50% 100%, color-mix(in srgb,var(--accent) 22%,transparent), transparent 70%);
}
.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__total{margin-top:var(--s-4); font-size:var(--fs-sm); font-weight:700; color:var(--text); line-height:1.5}
.buy__note{margin-top:var(--s-2); font-size:var(--fs-xs); color:var(--dim); line-height:1.7}
.buy__note s{color:var(--dim); opacity:.75}

.dl{margin-top:var(--s-5); border-top:1px solid var(--rule)}
.dl dt{
  font-family:var(--techno); font-size:var(--fs-xs); 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; text-decoration-thickness:1px}
.dl a:hover{color:var(--accent-hi)}
@media (min-width:560px){
  .dl{display:grid; grid-template-columns:10.5rem 1fr}
  .dl dt{border-bottom:1px solid var(--rule); padding-bottom:var(--s-4)}
}

/* ── 15. FOOTER ──────────────────────────────────────────── */
.footer{position:relative;z-index:var(--z-content);background:var(--void);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)}

.footer__next-label{font-family:var(--techno);font-size:var(--fs-xs);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(--muted)}

.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:var(--fs-xs);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:var(--fs-xs);letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}

/* ── 16. INTRO / FULL-PAGE LOADER ────────────────────────── */
/* First load of a session only (head script + sessionStorage). 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 — flying and shrinking from 1100px to 880px — 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.85  logotype flies to the hero position (JS-measured, main.js §7)
       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.5s}
.intro .hero__inner > .hero__tag   {animation-delay:1.57s}
.intro .hero__inner > .hero__cta   {animation-delay:1.64s}
.intro .hero__inner > .hero__count {animation-delay:1.71s}
.intro .scroll-cue{animation:introRise .45s var(--e-out) 1.8s 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}
/* posters carry a tilt transform of their own; reveal them on opacity alone */
.poster.reveal{transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))}

/* ── 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}
  .hero{position:relative}
  .hero__inner{transform:none;opacity:1}
  .hero .smoke,.poster{translate:none;transform:none}
  html:not(.intro) .hero__logo{transform:none}
  .cursor{display:none}
  .strip__track{animation:none}
  .gallery__feat img{translate:none}
  .stage .section::before{transform:none}
  .stage .section::after{opacity:1}
  .bill__row::before{display:none}
}
