/* BeyondFest 2026 — section layer: hero, statement, bands, formats, passes, footer. */

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: min(100svh, 940px);
  display: flex;
  align-items: flex-end;
  /* Bottom padding matches --stack like every other section, so the break
     into About reads the same as every other break on the page. */
  padding-block: clamp(5.5rem, 13vw, 9.5rem) var(--stack);
  overflow: hidden;
}

.hero__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* The scroll tween in motion.js restates this centring as xPercent/yPercent so
   GSAP never has to sniff it out of a computed pixel matrix. Keep the two in
   step if either changes. */
.hero__video {
  position: absolute;
  top: 34%;
  left: 50%;
  width: min(74vmin, 760px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  object-fit: cover;
  opacity: 1;
  mix-blend-mode: screen;
  mask-image: radial-gradient(circle at 50% 50%, #000 52%, transparent 74%);
}

.hero__glow {
  position: absolute;
  top: 34%;
  left: 50%;
  width: min(120vmin, 1200px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(123, 47, 247, 0.34), transparent 62%);
  filter: blur(20px);
}

.hero .shell { position: relative; z-index: 1; }

.hero__title {
  font-size: clamp(2.2rem, 9.2vw, 9.2rem);
  margin-block: clamp(1rem, 2vw, 1.75rem) clamp(1.5rem, 3vw, 2.5rem);
  text-shadow: 0 0 60px rgba(8, 8, 8, 0.85);
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
}

.hero__facts h2 {
  font-size: clamp(1.9rem, 4vw, 3.75rem);
  margin-bottom: 0.4rem;
}

.hero__in {
  font-family: var(--mono);
  text-transform: lowercase;
  letter-spacing: 0.2em;
  color: var(--purple-lift);
  margin: 0.6rem 0;
}

.hero__date {
  margin-top: 1.5rem;
  font-size: clamp(1.35rem, 2.6vw, 2.4rem);
}

.hero__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: clamp(1rem, 2.5vw, 2.5rem);
  margin: clamp(1.75rem, 3.5vw, 2.75rem) 0 0;
  padding-top: clamp(1.1rem, 2.2vw, 1.5rem);
  border-top: 1px solid var(--rule-soft);
}

.hero__meta dt {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 0.72rem;
  color: var(--purple-lift);
}

.hero__meta dd {
  margin: 0.35rem 0 0;
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: clamp(1.05rem, 1.7vw, 1.45rem);
}

.hero__scroll {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.72rem;
  color: rgba(253, 246, 236, 0.6);
}

/* ---------- statement ---------- */

.statement h2 { font-size: clamp(2.25rem, 6.2vw, 6rem); }
.statement h2 > span { display: block; }

.about__body {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: clamp(1.5rem, 3vw, 3rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.about__body p { margin: 0; max-width: var(--measure); }

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding: 0;
  list-style: none;
}

.chips li {
  border: 1px solid var(--purple);
  color: var(--purple-lift);
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.7rem 1.2rem;
}

.statement h2 .accent-soft { color: var(--purple); }

/* ---------- oversized word band ---------- */

.band { overflow: hidden; }

.band__word {
  font-size: clamp(4rem, 20vw, 20rem);
  color: var(--purple);
  line-height: 0.84;
}

.band__word--paper { color: var(--paper); }

/* Half the display size, for the shorter "Previous launch" line. */
.band__word--small { font-size: clamp(2rem, 10vw, 10rem); }

/* Headline, then the film, then the copy — one vertical run. */
.band__film {
  aspect-ratio: 16 / 9;
  max-width: 64rem;
  margin: clamp(2rem, 4vw, 3.5rem) auto 0;
}

.band__film iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.band__copy {
  max-width: 64rem;
  margin: clamp(2rem, 4vw, 3rem) auto 0;
}

/* ---------- formats rail ---------- */

.formats__intro {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: end;
}

/* Below 900px the rail is not pinned, so it has to be swipeable by hand.
   From 900px up GSAP translates the track and the overflow must be clipped. */
/* Pinning starts when the section's top meets the viewport top, so the section
   is exactly one screen tall from that breakpoint up. Pinning from the middle
   inserted the pin spacer while the section was already half scrolled, which is
   what made the page jump. */
@media (min-width: 900px) {
  #formats {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-block: var(--rail-pad);
  }

  /* The rail keeps a tighter inner padding than every other section, because a
     pinned section has to fit the screen. The gap it owes its neighbours is
     paid out here instead, on the rules either side, where it sits outside the
     pin and ScrollTrigger's spacer cannot absorb it. Net effect: the space
     between the rail and what surrounds it matches every other section break. */
  .rule--before-rail { margin-bottom: calc(var(--stack) - var(--rail-pad)); }
  .rule--after-rail { margin-top: calc(var(--stack) - var(--rail-pad)); }
}

.rail {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.rail::-webkit-scrollbar { display: none; }

.rail__track {
  display: flex;
  gap: clamp(1rem, 2vw, 2rem);
  padding-inline: var(--gutter);
}

.format {
  scroll-snap-align: center;
  flex: 0 0 min(78vw, 27rem);
  border: 1px solid var(--rule-soft);
  background: linear-gradient(180deg, var(--ink-lift), var(--ink));
  padding: clamp(1.25rem, 2vw, 1.75rem);
  display: grid;
  gap: 1.25rem;
  align-content: start;
}

.format__index {
  font-family: var(--mono);
  color: var(--purple-lift);
  letter-spacing: 0.2em;
  font-size: 0.75rem;
}

.format__art {
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.format__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.08);
}

.format h3 { font-size: clamp(1.35rem, 2.2vw, 1.9rem); }
.format p { margin: 0; font-size: 0.95rem; }

/* ---------- audience ---------- */

.roles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.9rem;
}

#audience .split { align-items: center; }

.roles li {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: clamp(1.1rem, 1.8vw, 1.5rem);
}

/* ---------- speakers ---------- */

/* The list runs far longer than the artwork beside it, so the artwork follows
   the scroll instead of leaving a column of dead space. */
@media (min-width: 900px) {
  .speakers__aside {
    position: sticky;
    top: calc(var(--header-h) + 2rem);
    align-self: start;
  }
}

/* The Rockstar Speakers heading sits inside the sticky aside so it stays put
   while the speaker list scrolls past it. Scaled down from .section-title--wide
   because it now shares a column rather than spanning the page. */
.speakers__aside .section-title--wide { font-size: clamp(2.4rem, 5.5vw, 4.6rem); }

.speaker {
  border-top: 1px solid var(--rule);
  padding-top: 1.5rem;
  margin-top: 2.5rem;
}

.speaker {
  display: flex;
  gap: clamp(1rem, 2vw, 1.5rem);
  align-items: flex-start;
}

.speaker__avatar {
  flex: 0 0 auto;
  width: clamp(77px, 8.4vw, 110px);
  height: clamp(77px, 8.4vw, 110px);
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--rule);
}

.speaker__text { min-width: 0; }

.speaker h3 { font-size: clamp(1.35rem, 2.4vw, 2rem); margin-bottom: 0.5rem; }
.speaker p { margin: 0; max-width: var(--measure); }

.speaker__role {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--purple-lift);
  margin-bottom: 0.75rem !important;
}

/* ---------- sponsors ---------- */

/* Every mark is transparent and sits directly on the ink; no plates. Most are
   white, but GMBB's lockup and Gutsy's are full colour, so do not assume a
   single-colour board. The five PNGs share one canvas size, so the grid can lay
   them out without knowing anything about the marks, and the optical balance
   between a 6:1 wordmark (Synapze) and a 3:4 portrait lockup (GMBB) is settled in
   ~/.buzz/OUTBOX/bf26_sponsor_plates.py rather than here. Resizing a cell
   rescales every mark together and the balance holds. */

/* The max-width only bites once the section has stacked and the board has the
   whole shell to itself; without it the marks grow past their desktop size as
   the viewport gets narrower, which reads backwards. */
.sponsor-tiers {
  display: grid;
  gap: clamp(1.75rem, 3.5vw, 2.5rem);
  margin-top: 1.75rem;
  max-width: 42rem;
}

.sponsor-tier__name { margin-bottom: 0.9rem; }

/* Three per row where there is room for three, two where there is not, and the
   browser decides from one number rather than from a list of breakpoints.

   That number is 170px, the width below which GMBB's tagline stops being
   readable. Everything else here follows from it. The board went to three-up on
   2 October; a fixed `repeat(3, ...)` passed at 1440 and 1920 and quietly failed
   in the band just above the stacking breakpoint, where the split is on but the
   shell is still narrow -- 150px cells at a 1200px viewport and 164px at 1300px.
   `auto-fit` with a 170px floor cannot make that mistake: it drops to two there
   and goes back to three at 1400 and on a stacked phone, where the board has the
   whole shell. Measured across 500 to 2560, the narrowest cell this produces is
   176px.

   Four-up is 107px at a laptop and is not worth trying. */
.sponsor-tier__logos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: clamp(0.75rem, 2vw, 1.5rem);
  align-items: center;
}

/* The Orbit rung holds one partner. Left on the two-column track it would sit
   in a half-width cell and read as the smallest logo on the page, which is the
   opposite of the ladder. Give it one wider cell of its own. */
.sponsor-tier--single .sponsor-tier__logos { grid-template-columns: minmax(0, 18rem); }

.sponsor img {
  display: block;
  width: 100%;
  height: auto;
}

/* The wall is 16:9 where the old stack was almost square, so at desktop the art
   column is roughly half the height of the board beside it. Centred, the slack
   splits top and bottom instead of pooling under the picture, and the crane shot
   grows about the middle of that space rather than away from it. */
#sponsors .split { align-items: center; }

/* The picture still takes the larger share of the split. It was 1 : 1.5, the 20%
   Miah asked for on 27 September, which put the art at 751px against 626px for an
   even split at a 1440 viewport. Going to three marks per row on 2 October bought
   that back down to 1 : 1.1, art 656px: enough width for the board to hold three
   cells at 183px, which is clear of the ~170px where GMBB's tagline goes, and
   still 5% more picture than an even split would give.

   Widening the column rather than scaling the picture inside it is what keeps the
   crane shot honest -- its 0.86 and 1.16 are proportions of the box, so the swing
   tracks the box without touching a number in motion.js.

   The two ratios are locked together. Moving this without moving the column count
   above breaks one of them: back to 1.5 and the cells drop to 151px, down to 1.0
   and the picture is smaller than it was before any of this. Above 1100 only;
   below it the section stacks and each of them gets the whole shell. */
@media (min-width: 1101px) {
  #sponsors .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); }
}

/* Between the auto-fit breakpoint and a laptop, the split still runs two columns
   and the tier grid inherits a narrow half of it, which squeezes the marks and
   loses GMBB's tagline and SAMA's descriptor line. Stack the section early so the
   board keeps the full shell: three cells of ~208px, the widest they ever get. */
@media (max-width: 1100px) {
  #sponsors .split { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- marquee ---------- */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--rule);
  padding-block: 0.75rem;
}

.marquee__track {
  display: flex;
  width: max-content;
  will-change: transform;
}

.marquee__track span {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: clamp(2rem, 6vw, 5.5rem);
  color: var(--purple);
  white-space: nowrap;
  padding-right: 2.5rem;
}

/* ---------- video frame ---------- */

.frame {
  position: relative;
  border: 1px solid var(--rule);
  overflow: hidden;
}

.frame video,
.frame img { width: 100%; height: 100%; object-fit: cover; }

/* Parallax slides the artwork inside a frame that clips, so the artwork has to
   be taller than the frame or the travel exposes a strip of empty frame at one
   edge — a hard line across the top or bottom of the picture. The frame takes a
   definite height from the art's own ratio, and the art overhangs it by 8% top
   and bottom, which covers the largest travel on the page (4.8% of its height).
   --art is the picture's width/height, set on the element. */
.frame--parallax { aspect-ratio: var(--art); }

.frame--parallax img {
  position: relative;
  height: 116%;
  top: -8%;
}

/* Unboxed variant: no border, no inner glow, the image sits free on the page.
   #sponsors clips, so the crane shot on the CRT wall cannot cross into Access no
   matter how the viewport is shaped. The travel is tuned to stay inside anyway;
   the clip is the guarantee, not the mechanism. */
#sponsors { overflow: hidden; }

.frame--bare { border: 0; overflow: visible; }
.frame--bare::after { display: none; }

/* The point of the bare frame is art sitting free on the page, and a hard
   rectangle edge undoes that -- the picture reads as a box cut into the ink
   rather than something standing in it. Feather all four edges instead.

   The ramps are not equal, because the artwork is not. Measured across
   tv-wall: the top eighth is pure black, so a 13% fade there costs nothing and
   removes the seam completely; the wet-floor reflection runs bright to the very
   last row, so the bottom fades from 90% and dissolves it into the page; and the
   two neon tubes sit about 6% in from each side, so the side ramps stop at 4%
   and leave them lit. Fading the sides any deeper puts the ramp on the tubes.

   Two mask layers intersected. A browser with neither mask-composite nor the
   -webkit- spelling unions them instead, which lands back on the unfeathered
   rectangle -- the old look, not a broken one. */
.frame--bare img {
  -webkit-mask-image:
    linear-gradient(to right, transparent 0, #000 4%, #000 96%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 13%, #000 90%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0, #000 4%, #000 96%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 13%, #000 90%, transparent 100%);
  mask-composite: intersect;
}

.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 80px rgba(123, 47, 247, 0.28);
}

/* ---------- passes ---------- */

.passes__head {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: end;
}

/* The mirrorball sits behind the tiers rather than in a letterbox beside them.
   A radial mask plus screen blending dissolves its black frame into the ink. */
#passes { overflow: hidden; }

.passes__bg {
  position: absolute;
  top: 46%;
  left: 50%;
  /* Static fallback only. motion.js restates this as xPercent/yPercent, because
     GSAP nulls the independent translate property on elements it animates. */
  transform: translate(-50%, -50%);
  width: min(125%, 1180px);
  height: auto;
  opacity: 0.28;
  mix-blend-mode: screen;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: radial-gradient(closest-side, #000 42%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 42%, transparent 100%);
}

#passes .shell { position: relative; z-index: 1; }

/* ---------- early flight ---------- */

.passes__head {
  align-items: center;
}

.promo {
  border: 1px solid var(--purple);
  background: linear-gradient(135deg, rgba(123, 47, 247, 0.22), rgba(233, 0, 255, 0.1));
  padding: clamp(1.1rem, 2.2vw, 1.6rem) clamp(1.25rem, 2.5vw, 2rem);
  display: grid;
  gap: 0.35rem;
  box-shadow: 0 0 60px rgba(123, 47, 247, 0.25);
}

.promo__flag {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.72rem;
  color: var(--purple-lift);
  margin: 0;
}

.promo__deal {
  font-family: var(--display);
  text-transform: uppercase;
  line-height: 0.92;
  font-size: clamp(1.9rem, 4.2vw, 3.2rem);
  margin: 0;
}

.promo__pct { color: var(--purple-lift); }

.promo__ends {
  font-size: 0.86rem;
  margin: 0;
  color: var(--bone);
}

.promo__clock {
  display: flex;
  gap: clamp(0.5rem, 1.4vw, 1rem);
  margin: 0.55rem 0 0;
  padding: 0;
  list-style: none;
}

.promo__clock li {
  display: grid;
  justify-items: center;
  gap: 0.15rem;
  min-width: 3.4rem;
  padding: 0.4rem 0.35rem;
  border: 1px solid var(--rule-soft);
  background: rgba(8, 8, 8, 0.45);
}

.promo__clock span {
  font-family: var(--display);
  font-size: clamp(1.35rem, 2.6vw, 2rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--paper);
}

.promo__clock i {
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--purple-lift);
}

.promo-over .promo__clock { display: none; }

.passes { display: grid; gap: 0; }

.pass {
  border-top: 1px solid var(--rule);
  padding-block: clamp(1.75rem, 3.5vw, 2.75rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  gap: clamp(1rem, 3vw, 3rem);
  align-items: center;
  transition: background-color 0.3s ease;
}

.pass:last-child { border-bottom: 1px solid var(--rule); }
.pass:hover { background: rgba(123, 47, 247, 0.08); }
.pass h3 { font-size: clamp(1.3rem, 2.2vw, 1.9rem); }
.pass p { margin: 0; }
.pass .btn { white-space: nowrap; }

.pass__buy {
  display: grid;
  justify-items: end;
  gap: 0.5rem;
}

.pass__was {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bone);
  opacity: 0.75;
}

.pass__was s { text-decoration-thickness: 1px; }
.pass__save { color: var(--purple-lift); }

/* The buy button carries the price, so it is the loudest thing in the row. */
.btn--buy {
  font-size: clamp(1rem, 1.8vw, 1.35rem);
  padding: 0.85em 1.4em;
  background: var(--purple);
  color: var(--paper);
  border-color: var(--purple);
}

.btn--buy:hover { background: var(--purple-hot); border-color: var(--purple-hot); }

.pass__standard { display: none; }

/* Once the deadline passes, promo.js flips the section and the standard price
   takes over. Nothing here needs editing on the day. */
.promo-over .promo,
.promo-over .pass__was,
.promo-over .pass__now { display: none; }

.promo-over .pass__standard { display: inline; }

/* ---------- after party ---------- */

.afterparty {
  position: relative;
  min-height: min(85svh, 760px);
  display: grid;
  align-items: center;
  overflow: hidden;
}

/* Full-bleed cover cropped the BeyondFest mark out of the top of the frame, so
   the artwork is contained and masked into the ink instead.

   It starts at the centre line rather than hanging off the right edge. Anchored
   right, the art tracked the viewport while the copy tracked the shell, which is
   centred and capped at 1440 -- so every pixel of viewport past 1440 opened
   another pixel of dead ink between the two. At 1600 the copy ended at x=637 and
   the art did not begin until x=992. From the centre line the gap is the same
   ~165px at 1440 and at 1600, and the radial mask is what finishes the right
   edge, not the edge of the window. */
.afterparty__video {
  position: absolute;
  inset: auto auto auto 50%;
  top: 50%;
  translate: 0 -50%;
  width: min(48%, 560px);
  height: auto;
  object-fit: contain;
  opacity: 0.72;
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
}

.afterparty__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(8, 8, 8, 0.92) 18%, rgba(8, 8, 8, 0.42) 78%);
}

.afterparty .shell { position: relative; z-index: 1; }
.afterparty h2 { font-size: clamp(2.5rem, 8vw, 7rem); }

/* ---------- gallery ---------- */

/* A rail rather than a wall: it snaps, it drags with a pointer, and each frame
   answers a hover. Arrow keys work once the rail has focus. */
.gallery {
  display: flex;
  gap: 1rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 0.5rem;
  cursor: grab;
}

.gallery::-webkit-scrollbar { display: none; }
.gallery.is-dragging { cursor: grabbing; scroll-snap-type: none; }

/* Snapping fights a continuous programmatic scroll, so it is off while the rail
   is drifting and comes back the moment a reader takes over. */
.gallery.is-drifting { scroll-snap-type: none; }
.gallery.is-dragging img { transition: none; }
.gallery:focus-visible { outline: 1px solid var(--rule); outline-offset: 4px; }

.gallery figure {
  margin: 0;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  flex: 0 0 min(80vw, 26rem);
  scroll-snap-align: center;
}

.gallery img,
.gallery video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06);
  transition: transform 0.7s var(--ease-out);
}

.gallery figure:hover img,
.gallery figure:hover video { transform: scale(1.16); }

/* The venue frames are 16:9 out of the camera, so the rail matches rather than
   cropping a third of each room away. */
.gallery--venue {
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.gallery--venue figure { aspect-ratio: 16 / 9; }

@media (prefers-reduced-motion: reduce) {
  .gallery img,
  .gallery figure:hover img { transition: none; transform: scale(1.06); }
}

/* ---------- agenda ---------- */

/* Times are placeholders until the lineup locks (9 Oct), so the row shape has to
   survive names and durations changing without the layout moving. Fixed time
   column, everything else flows. */
.agenda__head {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: end;
}

.agenda__note {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: rgba(253, 246, 236, 0.62);
  max-width: var(--measure);
  margin: 1.25rem 0 0;
}

/* ---------- the arc of the day ---------- */

/* A spine rather than a timetable. It warms from bone through purple as the
   day runs, so the reader gets morning-to-after-dark from the colour without a
   single clock time being printed. */
.arc {
  list-style: none;
  margin: clamp(2.5rem, 5vw, 4rem) 0 0;
  padding: 0;
  max-width: 62rem;
  display: grid;
  gap: clamp(2rem, 4vw, 3.25rem);
}

.act {
  position: relative;
  padding-left: clamp(1.5rem, 4vw, 3rem);
}

/* Real elements rather than pseudo-elements: the line is drawn top to bottom as
   the act scrolls past, and GSAP cannot animate ::before. */
.act__line {
  position: absolute;
  inset-block: 0.35rem 0;
  left: -1px;
  width: 3px;
  /* Brightest at the leading edge, so the end of the line reads as the thing
     doing the drawing rather than the line simply existing. */
  background: linear-gradient(180deg, var(--act-tone, var(--purple)) 0%, var(--act-tone, var(--purple)) 82%, #fff 100%);
  box-shadow: 0 0 14px var(--act-tone, var(--purple));
  opacity: 0.85;
  transform-origin: 50% 0;
}

.act__node {
  position: absolute;
  top: 0.35rem;
  left: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--act-tone, var(--purple));
  box-shadow: 0 0 18px var(--act-tone, var(--purple));
}

.act--morning { --act-tone: var(--bone); }
.act--midday { --act-tone: var(--lavender); }
.act--afternoon { --act-tone: var(--purple-lift); }
.act--night { --act-tone: var(--purple-hot); }

.act__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem clamp(0.75rem, 2vw, 1.25rem);
}

.act__label {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.72rem;
  color: var(--act-tone, var(--purple-lift));
  margin: 0;
}

.act__name {
  font-size: clamp(1.5rem, 3.4vw, 2.6rem);
  margin: 0;
}

.beats {
  list-style: none;
  margin: clamp(0.75rem, 2vw, 1.25rem) 0 0;
  padding: 0;
}

.beat {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: clamp(0.75rem, 2.5vw, 2rem);
  align-items: baseline;
  border-top: 1px solid var(--rule-soft);
  padding-block: clamp(0.85rem, 1.8vw, 1.35rem);
  transition: background-color 0.3s ease;
}

.beats .beat:last-child { border-bottom: 1px solid var(--rule-soft); }
.beat:hover { background: rgba(123, 47, 247, 0.06); }

.beat__tag {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--act-tone, var(--purple-lift));
  margin: 0;
}

.beat__name {
  font-size: clamp(1.1rem, 2vw, 1.55rem);
  margin: 0;
}

.beat__note {
  margin: 0.3rem 0 0;
  font-size: 0.95rem;
  color: rgba(253, 246, 236, 0.76);
  max-width: var(--measure);
}

@media (max-width: 620px) {
  .beat { grid-template-columns: 1fr; gap: 0.3rem; }
}

.agenda {
  list-style: none;
  margin: clamp(2.5rem, 5vw, 4rem) 0 0;
  padding: 0;
  /* capped so the track column stays next to the segment name instead of being
     flung to the far edge of a 1440px shell */
  max-width: 62rem;
}

.slot {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) 9rem;
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: baseline;
  border-top: 1px solid var(--rule-soft);
  padding-block: clamp(1.1rem, 2.2vw, 1.75rem);
  transition: background-color 0.3s ease;
}

.agenda .slot:last-child { border-bottom: 1px solid var(--rule-soft); }
.slot:hover { background: rgba(123, 47, 247, 0.06); }

.slot__time {
  font-family: var(--mono);
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  color: var(--purple-lift);
  font-variant-numeric: tabular-nums;
  margin: 0;
}

.slot__body { min-width: 0; }

.slot__name {
  font-size: clamp(1.3rem, 2.4vw, 2.05rem);
  margin: 0;
}

.slot__note {
  margin: 0.4rem 0 0;
  font-size: 0.95rem;
  color: rgba(253, 246, 236, 0.75);
  max-width: 52ch;
}

.slot__tag {
  justify-self: start;
  align-self: center;
  white-space: nowrap;
  border: 1px solid var(--rule);
  color: var(--purple-lift);
  font-family: var(--mono);
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.4rem 0.75rem;
}

/* breaks and holding slots read quieter than programmed segments */
.slot--break .slot__name { color: rgba(253, 246, 236, 0.62); }
.slot--break .slot__time { color: rgba(253, 246, 236, 0.45); }

/* ---------- footer ---------- */

.site-footer {
  background: var(--bone);
  color: var(--ink);
  padding-top: var(--stack);
  overflow: hidden;
}

.site-footer a { color: var(--ink); }

.footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: clamp(2rem, 5vw, 4rem);
}

.footer__grid h2,
.footer__grid h3 { font-size: 1.15rem; margin-bottom: 1rem; }

.fineprint { font-size: 0.85rem; margin-top: 1rem; }
.sitemap { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.socials { display: flex; gap: 1.25rem; margin: 0; padding: 0; list-style: none; }
.socials a { text-decoration: none; }
.socials svg { width: 24px; height: 24px; fill: currentColor; display: block; }

.colophon {
  border-top: 1px solid rgba(8, 8, 8, 0.3);
  padding-block: 1.25rem;
  font-size: 0.8rem;
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  /* the stage sits directly behind the CTAs on narrow screens — keep it faint */
  /* No room to sit the KV beside the copy on a phone, so it takes its own band
     above it and keeps full opacity. */
  .hero { padding-block: clamp(20rem, 88vw, 25rem) clamp(2rem, 4vw, 3rem); }
  .hero__video {
    top: 0;
    transform: translate(-50%, 0);
    width: min(84vmin, 430px);
  }
  .hero__glow { top: 0; transform: translate(-50%, -18%); }
  .pass { grid-template-columns: 1fr; align-items: start; }
  .slot { grid-template-columns: 1fr; gap: 0.5rem; }
  .slot__tag { justify-self: start; align-self: start; margin-top: 0.35rem; }
  .afterparty__scrim { background: rgba(8, 8, 8, 0.72); }

  /* Touch targets. The sitemap links were 22px tall and the social icons 24px,
     against a 44px minimum. Both grow by padding only, so the footer keeps its
     spacing and a pointer keeps the tighter desktop layout. */
  .sitemap { gap: 0; }
  .sitemap a { display: flex; align-items: center; min-height: 44px; }
  .socials { gap: 0.5rem; }
  .socials a { display: flex; padding: 0.625rem; }
}

@media (min-width: 900px) {
  /* matches the ScrollTrigger.matchMedia breakpoint that pins the rail */
  .rail {
    overflow: hidden;
    scroll-snap-type: none;
  }
}
