/* ============================================================
   HOUSE CIRCUIT — HOME
   Stage (hero with the circuit title + upcoming nights ⇄ The Name,
   crossfading over one wave field), the circuit wrapper, the stop map
   and the ticket rail. The sections below live in home-stops.css.
============================================================ */

/* ------------------------------------------------------------
   STAGE — the hero and The Name share one pinned screen. home.js
   sets, from scroll progress through the stage: --hero-a (hero
   opacity) and --next-a / --next-t (The Name's fade + settle, 0..1).
   Nothing moves up the screen: the layers crossfade (the hero comes
   back on the way up; The Name's settle happens once). The scroll
   length is the crossfade (first 75%, XF_SHARE in home.js) plus a
   short hold on The Name. Without motion it is the hero followed by The Name.
------------------------------------------------------------ */
.stage {
  --xf-d: 96svh;   /* scroll length: ~72svh crossfade + ~24svh hold on The Name */
  position: relative;
  background: var(--ink);
  overflow-x: clip;   /* the settle zoom never widens the page (clip keeps sticky working) */
}
.stage-pin { position: relative; }

.motion .stage::after {
  content: '';
  display: block;
  height: var(--xf-d);
}
.motion .stage-pin {
  position: sticky;
  top: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);   /* the ticket rail must not widen the stage */
}
.motion .stage-pin > .hero,
.motion .stage-pin > .stage-next { grid-area: 1 / 1; }

/* The crossfade values are set (home.js) on the layers that use them and
   registered as non-inherited, so a scroll frame restyles those layers,
   not the hero's or The Name's whole contents (unregistered where
   @property isn't supported: still correct, just less economical). */
@property --hero-a { syntax: '<number>'; inherits: false; initial-value: 1; }
@property --next-a { syntax: '<number>'; inherits: false; initial-value: 0; }

/* The crossfading layers get their own compositor layers, so the fade is
   a GPU blend instead of a repaint of the big shadowed title each frame. */
.motion .stage .hero-media { opacity: var(--hero-a, 1); will-change: opacity; }
.motion .stage .hero-inner {
  opacity: var(--hero-a, 1);
  transform: scale(calc(1 - (1 - var(--hero-a, 1)) * 0.04));
  will-change: opacity, transform;
}
.motion .stage-next {
  will-change: opacity, transform;
  opacity: var(--next-a, 0);
  transform: scale(calc(1.05 - 0.05 * var(--next-t, 0)));
  transform-origin: 50% 40%;
}
/* The hidden layer never takes clicks. */
.motion .stage:not(.is-next) .stage-next,
.motion .stage.is-next .hero { pointer-events: none; }

/* The Name's pieces settle in with the fade (scrubbed, reversible). */
.motion .stage-next .xf-1,
.motion .stage-next .xf-3 {
  transform: translate3d(0, calc((1 - var(--next-t, 0)) * 24px), 0);
}
.motion .stage-next .xf-2 .line > span {
  transform: translate3d(0, calc((1 - var(--next-t, 0)) * (70% + var(--i, 0) * 30%)), 0);
}
.motion .stage-next .xf-card {
  --kt: clamp(0, calc(var(--next-t, 0) * 1.6 - var(--k, 0) * 0.2), 1);
  opacity: var(--kt);
  transform: translate3d(0, calc((1 - var(--kt)) * 48px), 0);
}

.stage-next {
  position: relative;
  z-index: 3;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: safe center;   /* never push tall content up under the header */
  padding-top: calc(var(--header-h) + clamp(20px, 4vh, 48px));
  padding-bottom: clamp(32px, 6vh, 64px);
}
html:not(.motion) .stage-next { min-height: 0; }

/* ------------------------------------------------------------
   HERO
------------------------------------------------------------ */
.hero {
  position: relative;
  min-height: max(100svh, 560px);
  overflow: hidden;
  background: var(--ink);
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;   /* own stacking context: its tint/shade layers stay under the waves */
  transform-origin: 50% 40%;
}
.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  opacity: 0;
  filter: grayscale(1) contrast(1.2) brightness(0.62);
  transition: opacity 1.4s ease;
}
.hero-video.is-active { opacity: 1; }
.hero-video::-webkit-media-controls,
.hero-video::-webkit-media-controls-start-playback-button { display: none !important; -webkit-appearance: none; }

/* Duotone: green multiplied over greyscale footage, then shaded. */
.hero-media::before,
.hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.hero-media::after { z-index: 2; }
.hero-media::before {
  background: linear-gradient(160deg, #39ff7f 0%, #0d6b33 55%, #03170a 100%);
  mix-blend-mode: multiply;
  opacity: 0.85;
}
.hero-media::after {
  background:
    linear-gradient(180deg, rgba(5, 7, 6, 0.55) 0%, rgba(5, 7, 6, 0) 28%, rgba(5, 7, 6, 0.1) 55%, rgba(5, 7, 6, 0.94) 100%),
    linear-gradient(90deg, rgba(5, 7, 6, 0.55) 0%, rgba(5, 7, 6, 0) 60%);
}

/* 3D wave field (waves.js): fills the hero between the video and the
   text. waves.js dims the lines behind every text block, so they never
   compete with the copy. */
.hero-waves {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  width: 100%;
  height: max(100svh, 560px);
  pointer-events: none;
}

/* Layout: the title and pitch centred on the left; the HCE logo with
   the upcoming nights under it, centred on the right. Phones and
   tablets (<960px): the title alone, centred, with the upcoming nights
   under it (the header already carries the logo). */
.hero-inner {
  position: relative;
  z-index: 2;
  min-height: max(100svh, 560px);
  display: grid;
  padding: calc(var(--header-h) + clamp(12px, 3vh, 32px)) var(--gutter) clamp(20px, 4vh, 48px);
}

.hero-main {
  display: grid;
  align-content: center;
  gap: clamp(22px, 4vh, 44px);
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
}
.hero-left {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: clamp(18px, 3vh, 32px);
  text-align: center;
}
.hero-right {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: clamp(12px, 2.4vh, 28px);
  min-width: 0;
}
@media (min-width: 960px) {
  .hero-main {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    align-items: center;
    column-gap: clamp(32px, 5vw, 96px);
  }
}
/* The two columns dissolve into one centred column: the title, then the
   upcoming nights. No logo here: the header shows it. --hero-f (the
   title inherits it) comes from the width (the mark spans ~86% of it)
   and the height (17svh), so landscape phones still fit. */
@media (max-width: 959px) {
  .hero-left,
  .hero-right { display: contents; }
  .hero-main {
    --hero-f: clamp(40px, min(calc((100vw - 2 * var(--gutter)) * 0.86 / 2.94), 17svh), 150px);
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }
  .hero-logo { display: none; }
  .hero-main .hero-title { --hero-f: inherit; --events-min: 12px; }
}

/* The logo (desktop): over the upcoming nights. */
.hero-logo {
  height: auto;
  -webkit-user-select: none;
  user-select: none;
  filter: drop-shadow(0 0 24px rgba(57, 255, 127, 0.35)) drop-shadow(0 2px 12px rgba(0, 0, 0, 0.8));
}
@media (min-width: 960px) {
  .hero-logo { width: auto; height: clamp(110px, 25svh, 320px); }
}

/* Legibility over the moving waves and the video. */
.hero-pitch .lead,
.up-title { text-shadow: 0 2px 24px rgba(0, 0, 0, 0.75), 0 0 2px rgba(0, 0, 0, 0.6); }

/* The title: "House / Circuit" drawn as a white circuit like the logo's
   (.hero-mark: no fill, white traces with ring pads inside the letters,
   a softly glowing white edge; green sparks of signal run round it),
   "Events" under it. --hero-f is the size the name would have
   as type; the mark is sized from it (its 500×290 viewBox holds two
   lines of 170-unit caps), so the layout scales exactly as before. */
.hero-title {
  --hero-f: clamp(56px, min(21vw, 15svh), 300px);
  display: grid;
  justify-self: stretch;   /* full column width: the mark sizes from it */
  justify-items: center;
  margin: 0;
  font-size: var(--hero-f);
  line-height: 1;
}
@media (min-width: 960px) {
  .hero-title { --hero-f: clamp(80px, min(13.5vw, 26svh), 340px); }
}
.hero-mark {
  position: relative;
  display: block;
  width: min(100%, calc(var(--hero-f) * 2.94));
  aspect-ratio: 500 / 290;
}
.hero-mark svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.hm-base { filter: drop-shadow(0 2px 18px rgba(0, 0, 0, 0.7)); }
.hm-spark { will-change: transform; pointer-events: none; }   /* own layer: only the sparks repaint */
.hm-text {
  font-family: var(--f-display);
  font-weight: 900;
  font-size: 170px;
}
.hm-traces { fill: url(#hm-traces); }
.hm-copper,
.hm-ring { fill: none; stroke: rgba(233, 243, 236, 0.86); stroke-width: 2; stroke-linejoin: round; }
.hm-edge {
  fill: none;
  stroke: var(--text);
  stroke-width: 3;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 6px rgba(233, 243, 236, 0.35));
}
.hm-pulse { fill: none; }   /* motion only (below) */

/* Load-in: the white edge draws itself in, the traces light, then
   sparks of signal run round the letters. The
   sparks pause once The Name has taken over or the hero is off screen
   (.is-offscreen, site.js). The sparks are signal green, so they read
   against the white edge. */
.motion .hm-edge { animation: hm-draw 2.4s var(--ease-in-out) 0.15s backwards; }
.motion .hm-traces { animation: hm-fade 1.4s ease 1.1s both; }
.motion .hm-pulse {
  stroke: var(--signal);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 10 250;
  animation: hm-fade 0.8s ease 2.4s both, hm-pulse 3.2s linear infinite;
}
.motion .stage.is-next .hm-pulse { animation-play-state: paused; }
/* The dash exists only while the edge draws in (fill-mode backwards):
   left on, WebKit (iPhone), which dashes text outlines differently from
   Chrome, showed gaps in the longer letter outlines. */
@keyframes hm-draw {
  from { stroke-dasharray: 720; stroke-dashoffset: 720; }
  to   { stroke-dasharray: 720; stroke-dashoffset: 0; }
}
@keyframes hm-fade {
  from { opacity: 0; }
}
@keyframes hm-pulse {
  to { stroke-dashoffset: -260; }
}
/* "Events": a wide-tracked mono line under the name, scaled with it,
   between two signal lines. */
.hero-events {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55em;
  margin-top: 0.34em;
  font-family: var(--f-mono);
  font-size: max(var(--events-min, 16px), 0.17em);
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.6em;
  text-transform: uppercase;
  color: var(--text);
}
.hero-events::before,
.hero-events::after {
  content: '';
  width: 2.2em;
  height: 2px;
  background: var(--signal);
  box-shadow: 0 0 12px var(--signal-glow);
}
.hero-events::after { margin-left: -0.6em; }   /* the tracking after the last letter */

.hero-pitch { display: grid; justify-items: center; max-width: 520px; }
.hero-pitch .lead { font-size: clamp(17px, 1.3vw, 22px); color: var(--text); }
@media (max-width: 959px) {
  .hero-pitch { display: none; }   /* phones: the tickets take the room */
}

/* Upcoming nights inside the hero: at every width a window exactly one
   ticket wide, centred under the title (desktop: under the logo); the
   arrows page through the rest and the count says where you are. No
   ticket is ever cut off at the screen edge. */
.hero-upcoming {
  --poster-h: clamp(128px, 24svh, 300px);
  --card-w: calc(var(--poster-h) * 0.8 + 236px);
  display: grid;
  gap: 12px;
  min-width: 0;
  width: min(100%, var(--card-w));
}
.hero-main .hero-upcoming { justify-self: center; }
.up-head { display: flex; align-items: center; justify-content: space-between; gap: 12px 20px; }
.up-title { color: var(--text); }
.hero-upcoming .rail-controls { align-items: center; margin-top: 0; }
.hero-upcoming .rail-btn { width: 42px; height: 42px; font-size: 16px; background: rgba(5, 7, 6, 0.6); }
.rail-count {
  min-width: 4.2em;
  font-family: var(--f-mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-align: center;
  color: var(--text-2);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.8);
}
.rail-count b { font-weight: 500; color: var(--signal); }
@media (min-width: 960px) {
  .hero-upcoming { --poster-h: clamp(150px, 26svh, 330px); --card-w: calc(var(--poster-h) * 0.8 + 250px); }
}
.hero-upcoming:has(.empty) { width: min(100%, 560px); }
/* Near-opaque instead of a backdrop blur: the waves under them change
   every frame, and a blur would be recomputed every frame too. */
.hero-upcoming .ticket,
.hero-upcoming .empty { background: rgba(10, 14, 12, 0.9); }
.hero-upcoming .empty { max-width: 560px; justify-self: center; justify-items: center; text-align: center; }

/* Short phones: everything still fits one screen. */
@media (max-width: 959px) and (max-height: 720px) {
  .hero-inner {
    padding-top: calc(var(--header-h) + 10px);
    padding-bottom: 14px;
  }
  .hero-main { gap: 12px; }
  .hero-upcoming { gap: 8px; }
  .hero-upcoming { --poster-h: 118px; }
  .hero-upcoming .rail-btn { width: 36px; height: 36px; }
}

/* Load-in: pure CSS (no JS needed), only under html.motion. */
.motion [data-intro] {
  animation: intro-rise 1.2s var(--ease-out) both;
}
.motion [data-intro="1"] { animation-delay: 0.1s; }
.motion [data-intro="2"] { animation-delay: 0.22s; }
.motion [data-intro="3"] { animation-delay: 0.5s; }
.motion [data-intro="4"] { animation-delay: 0.62s; }
.motion [data-intro="5"] { animation-delay: 0.78s; }
@keyframes intro-rise {
  from { opacity: 0; transform: translate3d(0, 36px, 0); }
}

/* ------------------------------------------------------------
   THE CIRCUIT — everything after the hero, slid up over it.
------------------------------------------------------------ */
.circuit {
  position: relative;
  z-index: 1;
  background: var(--ink);
}
.circuit > .tape { z-index: 3; }

/* ------------------------------------------------------------
   THE LINE — fixed stop map (desktop). Collapsed to the dots and
   the track so it stays out of the way; hovering it (or tabbing
   into it) slides the labels out over a dark panel. Dots + labels
   are greyed until reached (.is-reached), the current stop glows
   (.is-current), and the track fills between stops (home.js).
------------------------------------------------------------ */
.line-map { display: none; }

@media (min-width: 1100px) {
  .line-map {
    --lm-gap: clamp(26px, 5vh, 48px);
    --lm-row: 26px;
    display: block;
    position: fixed;
    z-index: 40;
    left: clamp(20px, 2.2vw, 40px);
    top: 50%;
    transform: translateY(-50%);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.45s var(--ease-out), visibility 0s linear 0.45s;
  }
  .line-map.is-shown {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.45s var(--ease-out), visibility 0s;
  }
  .line-map ol {
    position: relative;
    display: grid;
    gap: var(--lm-gap);
  }
  .line-map a {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: var(--lm-row);
    font-family: var(--f-mono);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.14em;
    line-height: 1.2;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--text-3);
    opacity: 0.55;
    transition: color var(--t-mid), opacity var(--t-mid);
  }
  .lm-dot {
    position: relative;
    z-index: 1;
    flex-shrink: 0;
    width: 13px;
    height: 13px;
    border: 1px solid var(--text-3);
    border-radius: 50%;
    background: var(--ink);
    transition: background-color var(--t-mid), border-color var(--t-mid), box-shadow var(--t-mid), transform var(--t-mid) var(--ease-out);
  }
  /* Collapsed: labels take no room and no pointer; open on hover/focus. */
  .lm-label {
    display: inline-block;
    max-width: 0;
    overflow: hidden;
    opacity: 0;
    transform: translate3d(-6px, 0, 0);
    transition: max-width 0.35s var(--ease-out) 0.12s, opacity 0.2s ease, transform 0.35s var(--ease-out);
  }
  .line-map a { gap: 0; transition: color var(--t-mid), opacity var(--t-mid), gap 0.35s var(--ease-out) 0.12s; }
  .line-map:hover a,
  .line-map:focus-within a { gap: 14px; transition-delay: 0s; }
  .line-map:hover .lm-label,
  .line-map:focus-within .lm-label {
    max-width: 16em;
    opacity: 1;
    transform: none;
    transition: max-width 0.4s var(--ease-out), opacity 0.3s ease 0.08s, transform 0.4s var(--ease-out);
  }
  /* The panel behind the open labels */
  .line-map::before {
    content: '';
    position: absolute;
    z-index: -1;
    inset: -18px -22px -18px -16px;
    border: 1px solid var(--line-2);
    background: rgba(5, 7, 6, 0.84);
    -webkit-backdrop-filter: blur(12px);
            backdrop-filter: blur(12px);
    opacity: 0;
    transform: scaleX(0.3);
    transform-origin: left center;
    transition: opacity 0.25s ease 0.1s, transform 0.4s var(--ease-out) 0.1s;
  }
  .line-map:hover::before,
  .line-map:focus-within::before { opacity: 1; transform: none; transition-delay: 0s; }

  /* Track runs from the first dot's centre to the last one's. */
  .lm-track {
    position: absolute;
    left: 6px;
    top: calc(var(--lm-row) / 2);
    bottom: calc(var(--lm-row) / 2);
    width: 1px;
    background: var(--line-2);
  }
  .lm-fill {
    position: absolute;
    inset: 0;
    background: var(--signal);
    box-shadow: 0 0 10px var(--signal-glow);
    transform-origin: top;
    transform: scaleY(var(--lm-p, 0));
  }
  /* The spark rides a full-height carrier moved with a transform (a %
     of its own height = the track's), so the fill never triggers layout. */
  .lm-spark {
    position: absolute;
    left: 0;
    top: 0;
    width: 1px;
    height: 100%;
    transform: translate3d(0, calc(var(--lm-p, 0) * 100%), 0);
    opacity: var(--lm-spark, 0);
  }
  .lm-spark::before {
    content: '';
    position: absolute;
    left: -3px;
    top: -3.5px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #e3ffec;
    box-shadow: 0 0 8px #fff, 0 0 18px var(--signal);
  }

  .line-map a.is-reached { color: var(--text-2); opacity: 1; }
  .line-map a.is-reached .lm-dot {
    border-color: var(--signal);
    background: var(--signal);
    box-shadow: 0 0 10px var(--signal-glow);
  }
  .line-map a.is-current { color: var(--signal); }
  .line-map a.is-current .lm-dot { transform: scale(1.35); }
  .line-map a:hover,
  .line-map a:focus-visible { color: var(--text); opacity: 1; }
  .line-map a:hover .lm-dot { border-color: var(--signal); }

  /* Content clears the collapsed map (the open one floats over it). */
  .circuit .wrap,
  .stage-next .wrap { padding-left: max(var(--gutter), 88px); }
}

/* Jump landing (home.js): the target section is shown in its final
   state, no reveal mid-flight. */
.is-instant,
.is-instant * { transition: none !important; }

/* ------------------------------------------------------------
   UPCOMING RAIL (in the hero)
------------------------------------------------------------ */
.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;   /* one ticket per view */
  gap: 16px;
  overflow-x: auto;
  overflow-y: hidden;        /* sideways only */
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-bottom: 4px;
  scrollbar-width: none;     /* the arrows and the count page through it */
}
.rail::-webkit-scrollbar { display: none; }
.rail:focus-visible { outline-offset: -2px; }
.rail-item { scroll-snap-align: center; scroll-snap-stop: always; }
.rail-item .ticket { height: 100%; }

.ticket-poster-empty {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at center, rgba(57, 255, 127, 0.12), transparent 60%),
    repeating-linear-gradient(0deg, transparent 0 23px, var(--line) 23px 24px),
    repeating-linear-gradient(90deg, transparent 0 23px, var(--line) 23px 24px);
}
.ticket-poster-empty img { width: 40%; opacity: 0.8; }

.rail-controls { display: flex; gap: 8px; margin-top: 20px; }
.rail-btn {
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-2);
  border-radius: 50%;
  background: none;
  color: var(--text);
  font-size: 18px;
  transition: border-color var(--t-fast), color var(--t-fast), background-color var(--t-fast);
}
.rail-btn:hover { border-color: var(--signal); color: var(--signal); background: var(--signal-soft); }
.rail-btn:disabled { opacity: 0.3; pointer-events: none; }

.empty {
  display: grid;
  gap: 16px;
  justify-items: start;
  max-width: 560px;
  padding: clamp(28px, 4vw, 48px);
  border: 1px dashed var(--line-2);
}
.empty-line { color: var(--text-2); }

/* The ticket: the poster in a portrait frame beside the details, one
   clean card. A signal line runs along its top (the live wire); the
   date reads like a calendar leaf (the day big, month and year beside
   it); title, venue, then the two actions pinned to the bottom, so every
   card lines up the same however long its title is. */
.rail .ticket {
  grid-template-columns: calc(var(--poster-h) * 0.8) minmax(0, 1fr);   /* a portrait frame, like most posters */
  grid-template-rows: none;
  overflow: hidden;
  border: 1px solid var(--line-2);
  box-shadow: 0 22px 44px -24px rgba(0, 0, 0, 0.9);
}
.rail .ticket::before {
  content: '';
  position: absolute;
  inset: -1px 0 auto;
  z-index: 3;
  height: 2px;
  background: linear-gradient(90deg, var(--signal) 0%, var(--signal) 38%, rgba(57, 255, 127, 0) 100%);
  box-shadow: 0 0 10px var(--signal-glow);
}
.rail .ticket-poster {
  height: auto;
  min-height: var(--poster-h);   /* the full card height, even when the details run taller */
  aspect-ratio: auto;
  border: 0;
  border-right: 1px solid var(--line);
}
.rail .ticket-poster::before,
.rail .ticket-poster::after { content: none; }
/* Pinned to the frame, so the poster's own shape never sets the card height. */
.rail .ticket-poster-img { position: absolute; inset: 0; }
.rail .ticket-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  padding: 16px;
}
.rail .ticket-date {
  display: grid;
  justify-items: start;
  grid-template-columns: auto auto;
  justify-content: start;
  align-items: center;
  column-gap: 9px;
  min-width: 0;
  padding: 0 0 10px;
  border: 0;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  line-height: 1.25;
}
.rail .ticket-date strong { grid-row: 1 / 3; grid-column: 1; font-size: 38px; line-height: 0.85; }
.rail .ticket-date span { grid-column: 2; }
.rail .ticket-date span:last-child { color: var(--text-3); }
.rail .ticket-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 2px;
  font-size: clamp(20px, 1.6vw, 26px);
}
.rail .ticket-meta { display: flex; align-items: center; gap: 7px; min-width: 0; }
.rail .ticket-meta::before {
  content: '';
  flex: none;
  width: 6px;
  height: 6px;
  border: 1.5px solid var(--signal);
  border-radius: 50%;
}
.rail .ticket-actions { display: grid; gap: 8px; margin-top: auto; padding-top: 6px; }
.rail .ticket-actions .btn { width: 100%; min-height: 42px; white-space: nowrap; padding-inline: 10px; gap: 6px; }   /* one line, even on narrow phones */
@media (max-width: 420px) {
  .rail .btn-get { display: none; }   /* "Tickets ↗": the narrow details column stays narrow */
}
@media (max-width: 1099px) {
  .rail .ticket-body { padding: 14px; }
  .rail .ticket-title { font-size: clamp(19px, 5vw, 24px); }
}
@media (min-width: 1100px) {
  /* The footer clears the stop map on the home page. */
  .page-home .footer-top,
  .page-home .footer-bottom { padding-left: max(0px, calc(88px - var(--gutter))); }
}

/* Browsers without small-viewport units: the key svh sizes in vh. */
@supports not (height: 1svh) {
  .stage { --xf-d: 96vh; }
  .stage-next { min-height: 100vh; }
  .hero,
  .hero-inner { min-height: max(100vh, 560px); }
  .hero-waves { height: max(100vh, 560px); }
  .hero-title { --hero-f: clamp(56px, min(21vw, 15vh), 300px); }
  .hero-upcoming { --poster-h: clamp(128px, 24vh, 300px); }
  @media (max-width: 959px) {
    .hero-main { --hero-f: clamp(40px, min(calc((100vw - 2 * var(--gutter)) * 0.86 / 2.94), 17vh), 150px); }
  }
  .track-title { font-size: clamp(19px, min(1.6vw, 3.2vh), 28px); }
  @media (min-width: 960px) {
    .hero-title { --hero-f: clamp(80px, min(13.5vw, 26vh), 340px); }
    .hero-logo { height: clamp(110px, 25vh, 320px); }
    .hero-upcoming { --poster-h: clamp(150px, 26vh, 330px); }
  }
}
