/* ==========================================================================
   never in time — neverintime.de
   Alle Werte stammen aus DESIGN.md. Nichts hier ohne Eintrag dort.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Schriften — selbst gehostet, kein Google-CDN (DSGVO, siehe datenschutz.html)
   Genau die vier Schnitte, die im Stylesheet vorkommen. Welche Dateien
   gebraucht werden und woher sie kommen: fonts/README.md
   Fehlen die Dateien, greifen die Fallbacks weiter unten — die Seite bleibt
   benutzbar, trifft aber die Skala aus DESIGN.md §4 nicht.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-900.woff2") format("woff2");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fira Sans";
  src: url("../fonts/fira-sans-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fira Mono";
  src: url("../fonts/fira-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fira Mono";
  src: url("../fonts/fira-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Farben — aus Video und Logo gemessen, siehe DESIGN.md §3 */
  --wall:  #D9D7D2;
  --void:  #0C0B0B;
  --blood: #78100A;
  --flare: #C81E10;
  --chalk: #EDEAE4;
  --dust:  #8A8580;

  /* Drittes Rot, ausschließlich für KLEINE rote Schrift auf --void.
     Notwendig, nicht kosmetisch: --blood erreicht dort 1,7:1 und --flare 3,4:1,
     beides unlesbar bei 13 px. Dieser Ton kommt auf 4,74:1 und erfüllt AA.
     Auf hellem Grund werden Labels stattdessen --blood (7,9:1). */
  --label: #E63A22;

  /* ------------------------------------------------------------------------
     --lit: Helligkeit des Seitengrundes. 0 = dunkel, 1 = hell.
     Das JS schreibt es fortlaufend aus der Scrollposition; die Abschnitte
     wechseln sich ab. Ausgangswert 1, weil der Hero an der hellen Wand
     beginnt — so stimmt schon der erste Frame, bevor JS läuft.

     Alles Farbige auf der Seite hängt an den vier Tokens darunter, nicht an
     den Rohfarben. Das ist der einzige Weg, bei wechselndem Grund keine
     unlesbaren Stellen zu bekommen: --dust auf Putz käme auf 2,5:1,
     --label gilt nur für dunklen Grund.
     ------------------------------------------------------------------------ */
  --lit: 1;

  --ground:      color-mix(in oklab, var(--void), var(--wall) calc(var(--lit) * 100%));
  --ink:         color-mix(in oklab, var(--chalk), var(--void) calc(var(--lit) * 100%));
  --ink-soft:    color-mix(in oklab, var(--dust), #5C5854 calc(var(--lit) * 100%));
  --accent-text: color-mix(in oklab, var(--label), var(--blood) calc(var(--lit) * 100%));
  --hairline:    color-mix(in oklab, #3A3634, #B4B0AA calc(var(--lit) * 100%));

  --font-display: "Archivo", "Arial Black", "Helvetica Neue", Helvetica, sans-serif;
  --font-body: "Fira Sans", "Segoe UI", system-ui, sans-serif;
  --font-mono: "Fira Mono", ui-monospace, Consolas, monospace;

  /* Typo-Skala — DESIGN.md §4 */
  --t-display-l:  clamp(2.25rem, 7vw, 6rem);
  --t-display-m:  clamp(1.5rem, 4vw, 3rem);
  --t-lead:       clamp(1.125rem, 1.6vw, 1.5rem);
  --t-body:       1.0625rem;
  --t-small:      0.9375rem;
  --t-meta:       0.8125rem;

  /* Raster — DESIGN.md §5 */
  --gutter: clamp(1.25rem, 5vw, 6rem);
  --maxw: 1440px;
  --section-y: clamp(6rem, 12vh, 12rem);

  /* Geometrie der Logo-Übergabe. Am Frame bei 2,300 s gemessen (DESIGN.md §8).
     Prozentwerte beziehen sich auf den Videorahmen. Kalibrierknopf: wenn der
     Crossfade sichtbar springt, hier justieren — nicht im JS. */
  --handoff-left: 33.96%;
  --handoff-top: 52.67%;
  --handoff-width: 41.60%;

  /* Zielzustand des fixierten Logos */
  --brand-width: clamp(120px, 14vw, 200px);
  --brand-top: 12px;

  /* ------------------------------------------------------------------------
     Abstand, den jedes Sprungziel nach oben freihält. Das Logo ist FIXIERT:
     Ohne diesen Abstand landet jede Sektion mit ihrer Überschrift genau
     darunter, und das Logo deckt sie zu — bei Ankerklicks wie bei der Fahrt
     zum Introtext.

     Kein runder Daumenwert, sondern die tatsächliche Unterkante des Logos plus
     Luft. Die 0,492 sind das Seitenverhältnis aus dem viewBox des Logos
     (433 / 880); das SVG bekommt nur eine Breite, die Höhe folgt daraus.
     Wird --brand-width angefasst, wandert dieser Abstand von selbst mit.

     Gemessen am 24.08.2026: Logounterkante 71 px auf dem Handy, 110 px auf dem
     Desktop. 30 px, wie zuerst überlegt, hätten also nicht gereicht.
     ------------------------------------------------------------------------ */
  --anchor-offset: calc(var(--brand-top) + var(--brand-width) * 0.492 + 1rem);

  /* Vom JS gesetzt: 0 = an der Wand, 1 = durch die Wand */
  --through: 0;
}

*, *::before, *::after { box-sizing: border-box; }

/* Folgt dem Grund wie der body. Vorher stand hier --wall: Beim Überscrollen
   am Seitenende zog man auf iOS den hellen Putz hinter den dunklen Footer. */
html {
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
  /* clip, nicht hidden: hidden macht das Element zum Scrollcontainer, und dann
     verliert position:fixed auf Android Chrome seinen Bezug — das Logo klebte
     mobil nicht mehr oben, sondern kam nur beim Hochscrollen zurück. clip
     klemmt ab, ohne einen Scrollcontainer zu erzeugen. */
  overflow-x: clip;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--ink);
  background: var(--ground);
  overflow-x: clip;
}

/* Die Rechtsseiten haben keinen wechselnden Grund — sie stehen fest auf
   dunkel, damit sie nicht davon abhängen, ob ein Skript läuft. */
.ground-dark { --lit: 0; }



:focus-visible {
  outline: 2px solid var(--flare);
  outline-offset: 3px;
}

img, svg, video { display: block; max-width: 100%; }

/* Im Dokument, aber nicht im Bild. Gebraucht bei den vergangenen Events im
   Eventkalender: Der Durchstrich sagt „vorbei" ausschließlich optisch — ohne
   diesen Zusatz liest eine Vorlesesoftware sie wie kommende Termine vor.
   clip-path statt display:none, weil Ausgeblendetes gar nicht vorgelesen wird. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   Fixiertes Logo
   ========================================================================== */

.masthead {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
}

.brand {
  display: block;
  width: var(--brand-width);
  margin-block-start: var(--brand-top);
  pointer-events: auto;
  /* Folgt dem Grund, nicht dem Hero: Oxblood auf hell, Chalk auf dunkel.
     Gilt genauso für den hellen Abschnitt 152. DESIGN.md §3 */
  color: color-mix(in oklab, var(--chalk), var(--blood) calc(var(--lit) * 100%));
}

.brand__mark {
  width: 100%;
  height: auto;
  fill: currentColor;
}

/* Das Logo bleibt immer an seinem Platz im Fluss — seine Ruhelage IST die
   Zielposition. Während der Übergabe verschiebt und skaliert es nur ein
   transform, das auf der Identität endet; der fixierte Zustand ist dadurch
   pixelscharf und nicht interpoliert. Kein Wechsel des Positionierungsmodus. */
.js .masthead[data-state="handoff"] .brand {
  transform-origin: 0 0;
  will-change: transform;
}

.js .masthead[data-state="hidden"] .brand { opacity: 0; }

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
  position: relative;
  height: 100svh; /* ohne JS: ein Bildschirm, Poster steht */
}

/* svh und nicht vh: auf dem Handy ist vh die GROSSE Ansicht, also die ohne
   Adressleiste. Die Bühne wäre damit höher als das Sichtbare, der Inhalt läge
   zu tief, und der Scrollweg würde beim Ein- und Ausfahren der Leiste
   springen — genau das braucht der Scrub-Modus auf dem Handy nicht. */
.js .hero { height: 300svh; }
.js .hero[data-mode="play"],
.js .hero[data-mode="static"] { height: 100svh; }

.hero__stage {
  position: sticky;
  inset-block-start: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1.5rem, 4vh, 3rem);
  /* Derselbe Grund wie die ganze Seite, kein eigener aus --through. Vorher
     liefen beide auseinander: Die Bühne war schon schwarz, während der Grund
     darunter bereits aufhellte — an der Hero-Kante entstand dadurch eine harte
     Linie statt eines Verlaufs. Im Hero gilt --lit = 1 - --through, der Wert
     ist also derselbe wie zuvor, nur aus einer Quelle. */
  background-color: var(--ground);
}

/* Feste Größe, kein Zoom beim Scrollen mehr: Die Quelle ist 1178 px breit,
   jedes Pixel darüber war Hochskalierung — genau die Unschärfe, die am
   wachsenden Rahmen gestört hat. 1178 px ist deshalb harte Obergrenze; wird
   das Video jemals größer gerendert, hier UND in aspect-ratio anheben.

   Die Deckkraft fällt 3,3-mal schneller als der Grund dunkel wird (--through
   erreicht 1 nach der halben Phase B, die Deckkraft schon nach knapp einem
   Drittel). Nur so ist der Schriftzug im Video weg, bevor das DOM-Logo voll
   da ist — sonst stehen beide gleichzeitig im Bild. */
.hero__frame {
  position: relative;
  width: min(92vw, 1178px);
  aspect-ratio: 1178 / 786;
  opacity: clamp(0, calc(1 - var(--through) * 1.65), 1);
}

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Der Bandname steht als echter Text im Dokument, sichtbar nur ohne JS.
   Screenreader und Suchmaschinen finden ihn immer. */
.hero__name {
  position: absolute;
  inset-block-end: 6%;
  inset-inline: 0;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 5vw, 4rem);
  font-weight: 900;
  letter-spacing: -0.02em;
  line-height: 0.96;
  text-align: center;
  color: var(--blood);
}

.js .hero__name {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Verschwindet mit der Wand. Kein Rot auf Schwarz stehen lassen — und
   ausgeblendete Links dürfen nicht mehr im Tab-Fokus liegen, deshalb
   schaltet das JS bei --through ≈ 1 zusätzlich die Sichtbarkeit ab. */
.hero__next {
  text-align: center;
  opacity: calc(1 - var(--through));
}

.hero[data-past="true"] .hero__stage { visibility: hidden; }

.hero__gig {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(0.9375rem, 1.4vw, 1.125rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--blood);
}

.hero__gig-label { text-transform: uppercase; }
.hero__gig > span[aria-hidden] { padding-inline: 0.4em; }

.hero__more {
  display: inline-block;
  margin-block-start: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.06em;
  color: var(--void);
  text-underline-offset: 0.3em;
  text-decoration-color: var(--blood);
}

.hero__more:hover,
.hero__more:focus-visible { color: var(--blood); }

/* "zur Band" — nur auf dem Handy. Dort scrollt niemand freiwillig drei
   Bildschirme durch einen Effekt, den er noch nicht kennt; der Knopf fährt den
   Weg selbst (js/hero.js, Abschnitt "zur Band"). Auf dem Desktop ist er
   überflüssig: da liegt der Durchbruch ohnehin unter der Hand am Rad.
   Das Aussehen kommt vollständig von .jump — der Knopf IST ein Weiterweg
   durch die Seite, nur eben im Hero. Hier steht deshalb nur, wer ihn sieht und
   wo er sitzt. Die Grenze ist dieselbe wie sonst im Stylesheet.

   Der Selektor trägt ABSICHTLICH die Elternklasse mit: .jump setzt selbst
   `display: inline-block` und steht weiter unten im Stylesheet. Mit einer
   einzigen Klasse hätte es dieselbe Spezifität und würde bei Gleichstand
   gewinnen — der Knopf wäre auf dem Desktop sichtbar geblieben.

   `block` und nicht `inline-block`: sonst legt sich der Knopf neben
   "Weitere Termine anzeigen" in dieselbe Zeile. */
.hero__next .hero__band { display: none; }

@media (max-width: 767px) {
  .hero__next .hero__band {
    display: block;
    width: fit-content;
    margin: 1.25rem auto 0;
  }
}

/* ==========================================================================
   Abschnitte
   ========================================================================== */

/* Jede Sektion füllt nahezu den Bildschirm, Inhalt senkrecht zentriert.
   Längere Abschnitte wachsen darüber hinaus. Der Abstand zwischen den
   Sektionen bleibt konstant, egal wie viel Inhalt drin steht — deshalb sitzt
   er an genau einer Regel und nicht an den Sektionen selbst. DESIGN.md §5 */
.section {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100svh;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: var(--section-y);
}

/* Jedes Sprungziel hält Abstand zum fixierten Logo. `scroll-margin-top` ist
   genau dafür gebaut und wirkt bei ALLEN Wegen zum Ziel: beim Ankerklick, beim
   Laden mit #fragment in der Adresse und bei scrollIntoView(). Deshalb steht
   der Wert hier und nicht im Skript — die Fahrt zum Introtext liest ihn von
   hier ab (js/hero.js), damit es genau eine Quelle gibt.
   Ohne das landete jede Überschrift unter dem Logo. */
main[id],
section[id] { scroll-margin-top: var(--anchor-offset); }

.lead {
  max-width: 46ch;
  margin-block: clamp(2rem, 5vh, 3.5rem) 0;
  font-size: var(--t-lead);
  color: var(--ink-soft);
}

/* Auf kleinen Schirmen ist --section-y der Engpass, nicht der Inhalt: Mit
   96 px oben und unten passt „Die Truppe" (rund 569 px Inhalt) nicht in ein
   667-px-Fenster. Hier zurückgenommen, damit jede Sektion auf dem Handy ganz
   in den Blick passt. Innerhalb einer Bildschirmklasse bleiben die Abstände
   untereinander gleich — das war die Anforderung. */
@media (max-width: 767px) {
  :root { --section-y: clamp(2.5rem, 6vh, 4rem); }
}

/* ==========================================================================
   Bausteine der Abschnitte
   ========================================================================== */

/* Versalien. Bricht die frühere Kleinschreib-These des Manuals bewusst:
   Der Bandname flüstert klein, die Abschnitte schreien. line-height hier
   NICHT unter 1.02 — das Ü in „FÜR VERANSTALTER" sitzt über Versalhöhe und
   stößt sonst an die Zeile darüber. DESIGN.md §4 */
.section__title {
  margin: 0 0 clamp(1.5rem, 4vh, 2.5rem);
  font-family: var(--font-display);
  font-size: var(--t-display-l);
  font-weight: 900;
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  padding-block-start: 0.06em;
}

.subhead {
  margin: clamp(3rem, 7vh, 5rem) 0 1.25rem;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
}

.prose { max-width: 62ch; }
.prose p { margin: 0 0 1.1em; }
.prose p:last-child { margin-block-end: 0; }

/* ==========================================================================
   Wer wir sind
   Verhält sich wie jeder andere Abschnitt: Der Inhalt scrollt normal durch,
   nur der Seitengrund wechselt onScroll auf hell. Vorher war der Abschnitt
   angeheftet und 200 vh hoch — der Text blieb stehen, während darunter
   weitergescrollt wurde. Grund, Textfarbe und Logo hängen weiter gemeinsam an
   --lit, damit nichts auseinanderläuft.
   ========================================================================== */

.invert__stage {
  display: flex;
  align-items: center;
  min-height: 100svh;
}

/* Deutlich weniger senkrechter Abstand als bei den normalen Abschnitten:
   --section-y wären bis zu 192 px oben UND unten, damit wird der Block höher
   als der Bildschirm und `align-items: center` kann nichts mehr zentrieren. */
.invert__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding: clamp(2rem, 5vh, 3.5rem) var(--gutter);
}

/* Bild links, Text rechts, beide senkrecht mittig zueinander. */
.invert__cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.invert__figure { margin: 0; }
.invert__figure .photo { aspect-ratio: 16 / 9; }
.invert__figure .photo img { height: 100%; object-fit: cover; }

.invert__text { max-width: 34rem; }

/* Eine Größe für den ganzen Block — Anschlagzeile und Fließtext gleich groß.
   Die Überschrift ist trotzdem eine Überschrift, nur nicht größer. */
.invert__claim,
.invert__text p {
  margin: 0 0 1.1em;
  font-family: var(--font-body);
  font-size: var(--t-lead);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: -0.005em;
}

/* ==========================================================================
   Sprungmarke zur nächsten Sektion — eine Klasse für alle
   ========================================================================== */

/* Umgekehrt zur üblichen Logik: in Ruhe gefüllt rot, beim Überfahren
   verschwindet die Fläche und nur der Rahmen bleibt.
   Füllung ist --flare, nicht --blood: Oxblood auf --void käme als Fläche auf
   1,7:1 gegen den Grund, der Knopf wäre auf dunklem Grund kaum als Fläche zu
   erkennen. --flare erreicht 3,4:1 auf dunkel und 4,0:1 auf hell und erfüllt
   damit die 3:1 für Bedienelemente. Chalk darauf: 4,8:1. */
.jump {
  display: inline-block;
  align-self: flex-start;
  margin-block-start: clamp(2rem, 5vh, 3rem);
  padding: 0.85rem 1.5rem;
  border: 1px solid var(--flare);
  background: var(--flare);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--chalk);
  transition: background-color 180ms ease, color 180ms ease;
}

.jump:hover,
.jump:focus-visible {
  background: transparent;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Zwei Buttons am Sektionsende: der Weiterweg durch die Seite und die
   Abkürzung zur Anfrage. Der Abstand nach oben sitzt an der Reihe, nicht mehr
   an den Buttons — sonst zöge jeder von beiden seinen eigenen.
   -------------------------------------------------------------------------- */

.jumps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-self: flex-start;
  margin-block-start: clamp(2rem, 5vh, 3rem);
}

.jumps .jump { margin-block-start: 0; }

/* Die Umkehrung der Umkehrung: in Ruhe nur Rahmen, beim Überfahren gefüllt.
   Zwei gleich laute rote Flächen nebeneinander würden sich gegenseitig
   entwerten — und der Weiterweg durch die Seite ist der häufigere Klick.
   Der Rahmen allein erfüllt die 3:1 für Bedienelemente: --flare kommt auf
   3,4:1 gegen --void und 4,0:1 gegen --wall. */
.jump--ghost {
  background: transparent;
  color: var(--ink);
}

.jump--ghost:hover,
.jump--ghost:focus-visible {
  background: var(--flare);
  color: var(--chalk);
}

/* Die Farbe kommt schon über --ink vom Grund; hier nur der engere Abstand. */
.invert__text .jumps { margin-block-start: 1.5rem; }


/* Unter 900 px übereinander: Bild oben, Text darunter. */
@media (max-width: 900px) {
  .invert__cols { grid-template-columns: 1fr; gap: clamp(1.5rem, 4vh, 2.5rem); }
  .invert__text { max-width: none; }
}

/* ==========================================================================
   Bildbehandlung — DESIGN.md §6
   Vereinheitlicht die sieben Handy-Livefotos mit ihren völlig verschiedenen
   Bühnenlichtern zu einer Familie: schwarze Schatten, Putz in den Lichtern,
   ein Anflug Oxblood, Korn darüber.
   opacity-Werte an den echten Bildern justieren, das sind Startwerte.
   ========================================================================== */

.photo {
  position: relative;
  background: var(--wall);
  isolation: isolate; /* ohne das mischt das Bild mit dem Seitenhintergrund */
  overflow: hidden;
}

.photo img {
  width: 100%;
  height: auto;
  filter: grayscale(1) contrast(1.15);
  mix-blend-mode: multiply;
}

.photo::after {              /* Oxblood-Anflug */
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--blood);
  mix-blend-mode: color;
  opacity: 0.18;
  pointer-events: none;
}

.photo::before {             /* Korn. Feste Pixelgröße, sonst sieht es
                                wie Kompressionsartefakte aus. */
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  mix-blend-mode: overlay;
  opacity: 0.4;
  pointer-events: none;
}

/* ==========================================================================
   170 — Der Sound: Bands statt Songtitel
   ========================================================================== */

.repertoire {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: clamp(2.5rem, 6vh, 4rem) clamp(2rem, 4vw, 4rem);
  margin-block-start: clamp(3rem, 8vh, 5rem);
}

.repertoire__label {
  margin: 0 0 1.25rem;
  padding-block-end: 0.75rem;
  border-block-end: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
}

.repertoire__bands {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Fünfzehn bekannte Bandnamen, groß gesetzt — der stärkste typografische
   Block der Seite, und für einen Veranstalter das schnellste Argument. */
.repertoire__bands li {
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 2.4vw, 2rem);
  font-weight: 900;
  line-height: 1.14;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* ==========================================================================
   184 — Die Truppe
   ========================================================================== */

/* Ohne JS: einfaches Raster mit allen fünf Texten. Ein Coverflow ohne
   Skript wäre unbedienbar, also gibt es dann gar keinen. */
.crew__stage {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  margin-block-start: clamp(2rem, 5vh, 3rem);
}

.crew__photo { aspect-ratio: 3 / 4; }
.crew__photo img { height: 100%; object-fit: cover; }

.crew__name {
  margin: 1.25rem 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2.125rem);
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

.crew__role {
  margin: 0.4rem 0 0.9rem;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-text);
}

.crew__line { margin: 0; font-size: var(--t-small); color: var(--ink-soft); }

/* --- Coverflow, sobald JS läuft -------------------------------------------
   Die Karten liegen übereinander und werden per --off (Abstand zur aktiven
   Karte) auseinandergefahren. Kein Seitwärtsscrollen, gesteuert über die
   Pfeilknöpfe, die Pfeiltasten und Klick auf eine Nachbarkarte. */

/* Kartenbreite an einer Stelle — die Stapelhöhe leitet sich daraus ab.
   Vorher war die Höhe ein eigener clamp und dadurch bis zu 120 px größer als
   die Karten: genau der Leerraum, der Bild und Name auseinandergerissen hat. */
.crew.is-flow { --card-w: clamp(200px, 26vw, 300px); }

.crew.is-flow .crew__stage {
  position: relative;
  display: block;
  /* clip, sonst ragen die äußeren Karten (bis 340 px aus der Mitte) auf dem
     Handy aus dem Bild und die ganze Seite bekommt einen Seitwärtsscroll —
     der zog auch das fixierte Logo aus der Mitte. */
  overflow: clip;
  height: calc(var(--card-w) * 4 / 3);   /* Portraits sind 3:4 */
  margin-block-start: clamp(1.5rem, 4vh, 2.5rem);
  perspective: 1400px;
  transform-style: preserve-3d;
}

.crew.is-flow .crew__card {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  width: var(--card-w);
  margin-inline-start: calc(var(--card-w) / -2);
  /* --off und --dist setzt das JS pro Karte. Rückfallwerte, damit der Stapel
     auch im Bruchteil vor dem ersten paint() nicht auseinanderfliegt. */
  transform:
    translateX(calc(var(--off, 0) * clamp(120px, 15vw, 190px)))
    translateZ(calc(var(--dist, 0) * -220px))
    scale(calc(1 - var(--dist, 0) * 0.12));
  opacity: calc(1 - var(--dist, 0) * 0.42);
  z-index: calc(10 - var(--dist, 0));
  transition: transform 520ms cubic-bezier(.22,.7,.2,1), opacity 520ms ease;
}

.crew.is-flow .crew__card[data-hidden="true"] { opacity: 0; pointer-events: none; }

.crew.is-flow .crew__photo { height: 100%; }
.crew.is-flow .crew__text { display: none; }

/* Nur die aktive Karte zeigt volle Farbe, die Nachbarn treten zurück. */
.crew.is-flow .crew__card[data-dist="0"] .crew__photo::after { opacity: 0.08; }

/* Nur diese Sektion ist mittig — die Ausrichtung folgt dem Inhalt, und
   Karussell wie Personentext stehen zentriert. Die übrigen Sektionen haben
   linksbündigen Inhalt und behalten linksbündige Überschriften. */
#truppe { align-items: center; text-align: center; }
#truppe .section__title { align-self: center; }
#truppe .crew { width: 100%; }

.crew__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  margin-block-start: clamp(1.25rem, 3vh, 2rem);
}

.crew__btn {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  background: var(--flare);
  color: var(--chalk);
  font-size: 1.125rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 180ms ease;
}

/* Wird beim Überfahren dunkler statt leer: Ein Pfeil ohne Fläche wäre auf
   hellem Grund nicht mehr zu sehen — anders als bei den Textknöpfen. */
.crew__btn:hover, .crew__btn:focus-visible { background: var(--blood); }

.crew__counter {
  margin: 0;
  min-width: 4.5em;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* Knapper Abstand zum Karussell: Bild und Name sollen erkennbar
   zusammengehören, und die Sektion muss auf dem Handy ganz in den Blick passen. */
.crew__active {
  max-width: 44ch;
  margin: clamp(0.5rem, 1.2vh, 0.75rem) auto 0;
  text-align: center;
}

.crew__active .crew__name { margin-block-start: 0; }

@media (prefers-reduced-motion: reduce) {
  .crew.is-flow .crew__card { transition: none; }
}

/* ==========================================================================
   Eventkalender
   Kommende und vergangene Events in EINER Liste. Die eigene Formatierung für
   „Bisherige Abrisse" (.played) ist damit ersatzlos gelöscht — zwei Listen mit
   fast gleicher Optik und verschiedenen Klassennamen waren derselbe Baustein
   zweimal.
   ========================================================================== */

.dates { margin: 0; padding: 0; list-style: none; }

.dates__item {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  align-items: baseline;
  padding-block: 1.4rem;
  border-block: 1px solid var(--hairline);
}

.dates__item + .dates__item { border-block-start: 0; }

.dates__date {
  font-family: var(--font-mono);
  font-size: clamp(1.125rem, 2vw, 1.5rem);
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}

/* Normale Schreibweise. Versalien standen hier ohne Grund — die gleichrangigen
   Namen der vergangenen Events waren schon immer normal gesetzt. */
.dates__event {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.2vw, 1.75rem);
  font-weight: 900;
  letter-spacing: -0.015em;
}

/* Rechts außen, gleicher Grad wie der Veranstaltungsname, in Rot. --label und
   nicht --flare: Zwei fast gleich große rote Textelemente auf einer Seite
   sollen nicht zwei verschiedene Rottöne haben. */
.dates__place {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: clamp(1.25rem, 2.2vw, 1.75rem);
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}

/* --------------------------------------------------------------------------
   Vergangene Events
   Durchgestrichen wird spaltenweise, nicht die ganze Zeile: Ein Strich über
   die volle Breite läuft auch durch die Lücke zwischen Name und Ort und sieht
   dort wie ein Fehler aus, nicht wie eine Auszeichnung.
   Datum und Ort verlieren zusätzlich das Rot. Sie stehen sonst genauso laut
   da wie beim kommenden Termin, und der ist der einzige, auf den es ankommt.
   --ink-soft erreicht auf --void 5,2:1 und erfüllt damit AA.
   -------------------------------------------------------------------------- */

.dates__item--past .dates__date,
.dates__item--past .dates__event,
.dates__item--past .dates__place {
  text-decoration: line-through;
  text-decoration-thickness: from-font;
}

.dates__item--past .dates__date,
.dates__item--past .dates__place { color: var(--ink-soft); }

/* Leerzustand ohne JavaScript: bleibt die Liste leer, verschwindet sie und
   der Hinweis erscheint. Autorenregel schlägt das [hidden] des Browsers. */
.dates:empty { display: none; }

.dates:empty + .dates__empty {
  display: block;
  max-width: 46ch;
  margin: 0;
  padding-block: 1.4rem;
  border-block: 1px solid var(--hairline);
  font-size: var(--t-lead);
}

/* Der Text steht in einem eigenen <span>, damit er auf dem Handy als
   Flex-Element die ganze Zeile belegen kann — ein nackter Textknoten lässt
   sich nicht auf 100 % Breite setzen, und ohne das kleben die Icons hinter
   dem Text und rutschen einzeln um. */
.dates__note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin-block-start: 1.5rem;
  font-size: var(--t-small);
  color: var(--ink-soft);
}

/* Gleiche Umkehrung wie .jump, nur kleiner — sonst kippt die Terminzeile.
   align-self: center, weil die Zeile auf `align-items: baseline` steht: Die
   Grundlinie des Knopfes ist die seiner Schrift, und mit dem Innenabstand
   darunter saß er dadurch sichtbar zu tief. Ein Knopf hat keine Grundlinie,
   die zum Text passt — er wird zentriert. */
.dates__tickets {
  flex: none;
  align-self: center;
  padding: 0.5rem 1rem;
  border: 1px solid var(--flare);
  background: var(--flare);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--chalk);
  transition: background-color 180ms ease, color 180ms ease;
}

.dates__tickets:hover,
.dates__tickets:focus-visible { background: transparent; color: var(--ink); }

/* --------------------------------------------------------------------------
   Social-Icons — ein Format für Termine-Hinweis und Footer
   -------------------------------------------------------------------------- */

.icon { width: 20px; height: 20px; }

/* In der Hinweiszeile doppelt so groß. Der Innenabstand für die
   Trefferfläche entfällt dort — 40 px sind von sich aus über den 24 px aus
   WCAG 2.5.8. Senkrecht mittig sitzen sie über das align-items: center der
   Zeile, nicht über eine eigene Regel. */
.dates__note .icon { width: 40px; height: 40px; }
.dates__note .social { padding: 0; }

/* 20 px Icon plus 2 px Innenabstand ergibt die 24 px Trefferfläche aus
   WCAG 2.5.8. Im Footer muss dafür .footer a überstimmt werden, deshalb steht
   der Selektor dort noch einmal mit einer Klasse mehr. */
.social,
.footer .social {
  display: inline-flex;
  padding: 2px;
  color: inherit;
  text-decoration: none;
}

.social:hover,
.social:focus-visible,
.footer .social:hover,
.footer .social:focus-visible { color: var(--flare); }

/* --------------------------------------------------------------------------
   Listen auf dem Handy: gestapelt und linksbündig
   Vorher brachen die Spalten einfach um, sobald der Platz fehlte — das sah
   nach Fehler aus, nicht nach Absicht.
   Dieser Block MUSS hinter .dates stehen: Die Regeln haben
   dieselbe Spezifität wie die dortigen (align-items, margin-inline-start),
   und bei Gleichstand gewinnt die spätere. Im Media-Block oben bei
   --section-y stand er zu früh und griff nicht.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
  .dates__item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
  }

  .dates__place { margin-inline-start: 0; }

  /* In der Spalte gibt es keine Grundlinie mehr, an der etwas zu zentrieren
     wäre — der Knopf steht links unter dem Ort. */
  .dates__tickets {
    align-self: flex-start;
    margin-block-start: 0.75rem;
  }

  /* Text nimmt die ganze Zeile, die Icons brechen darunter um und stehen
     linksbündig unter ihm. */
  .dates__note { align-items: flex-start; }
  .dates__note-text { flex: 1 0 100%; }
}

.dates__note a, .dates__empty a { color: var(--ink); text-underline-offset: 0.2em; }
.dates__note a:hover, .dates__empty a:hover,
.dates__note a:focus-visible, .dates__empty a:focus-visible { color: var(--flare); }

/* ==========================================================================
   206 — Für Veranstalter
   ========================================================================== */

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  /* Engerer Abstand als bei freistehenden Spalten. Mit hinterlegten Panels
     sind bis zu 48 px Lücke zu viel: Die drei Schritte sind eine Folge und
     sollen als Gruppe lesbar bleiben, nicht als drei Einzelkästen. */
  gap: clamp(1rem, 2vw, 1.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

/* Leicht hinterlegt, damit die drei Schritte als Panels lesbar sind.
   Der Ton wird aus dem Grund selbst gemischt und nicht gesetzt — damit trägt
   er auf hellem wie auf dunklem Grund, ohne zweite Regel, und er läuft beim
   Grundwechsel onScroll automatisch mit. 6 % Beimischung sind knapp über der
   Wahrnehmungsgrenze; genau das war die Vorgabe: leicht.
   Kein Schatten. Die Seite hat keinen, und ein einzelner wirkte aufgeklebt.
   Der Kontrast ändert sich dadurch kaum: --ink-soft kommt auf dem getönten
   Panel noch auf 5,0:1 (dunkler Grund) und bleibt damit über AA. */
.steps__item {
  padding: clamp(1.25rem, 3vw, 1.75rem);
  background: color-mix(in oklab, var(--ground), var(--ink) 6%);
  border: 1px solid var(--hairline);
}

.steps__num {
  display: block;
  margin-block-end: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  color: var(--accent-text);
}

.steps__name {
  margin: 0 0 0.5rem;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 1.8vw, 1.5rem);
  font-weight: 900;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}

.steps__item p { margin: 0; font-size: var(--t-small); color: var(--ink-soft); }

.contact {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-block-start: clamp(3rem, 7vh, 5rem);
}

/* Gleiche Umkehrung wie bei .jump */
.contact__button {
  display: inline-block;
  padding: 0.9rem 1.5rem;
  border: 1px solid var(--flare);
  background: var(--flare);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--chalk);
  transition: background-color 180ms ease, color 180ms ease;
}

.contact__button:hover,
.contact__button:focus-visible { background: transparent; color: var(--ink); }

/* ==========================================================================
   Einblendung der Abschnitte — einmalig, DESIGN.md §10
   ========================================================================== */

/* Die Klasse `reveal` setzt das JS erst, wenn IntersectionObserver wirklich
   vorhanden ist. Sonst blieben die Abschnitte unsichtbar — ein Ausblenden,
   das niemand wieder einschaltet, ist schlimmer als keine Einblendung. */
.reveal .section { opacity: 0; transform: translateY(16px); }

.reveal .section.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 500ms cubic-bezier(.2,.7,.2,1), transform 500ms cubic-bezier(.2,.7,.2,1);
}

@media (prefers-reduced-motion: reduce) {
  .reveal .section { opacity: 1; transform: none; }
}

/* Beim Drucken wird nicht gescrollt, der Observer feuert also nie — ohne diese
   Regel käme die Seite leer aus dem Drucker. Betrifft Veranstalter, die sich
   die Booking-Angaben ausdrucken. */
@media print {
  .reveal .section { opacity: 1 !important; transform: none !important; }
}

/* ==========================================================================
   Rechtstexte (Impressum, Datenschutz)
   ========================================================================== */

.legal {
  max-width: 72ch;
  margin-inline: auto;
  padding: clamp(5rem, 14vh, 9rem) var(--gutter) 0;
}

.legal__back {
  display: inline-block;
  margin-block-end: clamp(2rem, 6vh, 3.5rem);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.06em;
  color: var(--dust);
  text-underline-offset: 0.25em;
}

.legal__back:hover,
.legal__back:focus-visible { color: var(--chalk); }

.legal h1 {
  margin: 0 0 clamp(2rem, 5vh, 3rem);
  font-family: var(--font-display);
  font-size: var(--t-display-m);
  font-weight: 900;
  line-height: 0.98;
  letter-spacing: -0.02em;
  padding-block-start: 0.08em;
}

.legal h2 {
  margin: clamp(2.5rem, 6vh, 4rem) 0 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dust);
}

.legal p, .legal ul { margin: 0 0 1rem; }
.legal ul { padding-inline-start: 1.25rem; }
.legal li { margin-block-end: 0.35rem; }

.legal a { color: var(--chalk); text-decoration-color: var(--dust); text-underline-offset: 0.2em; }
.legal a:hover, .legal a:focus-visible { text-decoration-color: var(--flare); }

.legal address { font-style: normal; }

/* Offene Angabe, die noch eingesetzt werden muss. Fällt im Text auf — Absicht. */
.legal .todo {
  padding: 0.1em 0.35em;
  background: color-mix(in oklab, var(--void), var(--flare) 22%);
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* ==========================================================================
   Abschlussbild
   Liegt fixiert hinter dem Inhalt und wird zum Seitenende weich eingeblendet.
   `contain`, damit es komplett zu sehen ist — `cover` würde beschneiden.
   Bewusst ein eigenes Element und nicht background-attachment: fixed, das
   ist auf iOS unbrauchbar.
   ========================================================================== */

.finale {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-color: var(--void);
  background-image: url("../images/hero-1600.jpg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  opacity: var(--finale, 0);
  pointer-events: none;
}

/* Auf dem Handy ist das Bild NICHT fixiert. Fixiert ging nicht auf: Das
   Querformat belegt bei voller Breite nur rund ein Viertel eines Hochformats,
   darüber blieb eine große schwarze Fläche stehen, die wie ein Versehen wirkte.
   Hier steht es stattdessen statisch direkt unter dem Footer und wird über
   --finale eingeblendet — das Foto ist damit das Letzte auf der Seite und das
   Logo bleibt oben angeheftet wie überall sonst. */
@media (max-width: 767px) {
  .finale {
    position: static;
    aspect-ratio: 1600 / 938;
    background-size: cover;
  }

  /* Der Scrollweg für das fixierte Bild wird nicht mehr gebraucht. */
  .finale-space { display: none; }
}

/* Inhalt liegt über dem Bild, sonst verdeckt es die Seite.
   ACHTUNG: Der Masthead darf hier NICHT mit aufgeführt werden — er ist
   `position: fixed`, und `relative` hätte dieselbe Spezifität und würde ihn
   in den Fluss zurückholen. Genau das war der schwarze Balken am Seitenanfang
   und der Grund, warum das Logo verschwand. */
.hero, main, .footer { position: relative; z-index: 1; }

/* Der Footer steht ÜBER dem Bild, nicht darauf: eigener deckender Grund,
   damit das eingeblendete Bild nicht durchscheint. */
.footer--over { background: var(--ground); }

/* Danach nur noch das Bild. Diese Fläche gibt ihm den Scrollweg, in dem es
   allein im Bildschirm steht — sie ist leer und durchsichtig. */
.finale-space {
  position: relative;
  z-index: 0;
  height: 100svh;
}

/* ==========================================================================
   Footer
   ========================================================================== */

.footer {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem clamp(1.5rem, 4vw, 3rem);
  /* center, nicht baseline: Die Social-Icons sind ein inline-flex ohne Text
     und haben deshalb keine Grundlinie — der Browser nimmt ihre UNTERKANTE als
     Grundlinie und stellte sie damit gegenüber dem Text daneben versetzt.
     Bei center liegen Icon- und Textmitte auf derselben Linie; für die
     Textzeilen selbst ändert sich nichts, sie haben dieselbe Zeilenhöhe. */
  align-items: center;
  max-width: var(--maxw);
  margin-inline: auto;
  /* Oben und unten gleich, und deutlich knapper als die Abschnitte:
     vorher standen hier --section-y (bis 192 px) gegen bis 96 px. */
  padding: clamp(1.5rem, 4vh, 2.25rem) var(--gutter);
  border-block-start: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  color: var(--ink-soft);
}

.footer__band { margin: 0; margin-inline-end: auto; }

.footer__nav { display: flex; gap: clamp(1rem, 3vw, 2rem); }

/* Trefferfläche: 13 px Schrift bei Zeilenhöhe 1,6 ergibt rund 21 px und
   verfehlt damit die 24 px aus WCAG 2.5.8 (AA). Der Innenabstand hebt alle
   kleinen Textlinks über die Grenze. */
.footer a,
.dates__note a,
.dates__empty a,
.legal__back {
  display: inline-block;
  padding-block: 0.35rem;
}

.footer a {
  color: var(--ink-soft);
  text-decoration-color: var(--hairline);
  text-underline-offset: 0.25em;
  transition: color 180ms ease, text-decoration-color 180ms ease;
}

.footer a:hover,
.footer a:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--flare);
}

/* ==========================================================================
   Bewegung aus
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .js .hero { height: 100vh; }
  /* Nur die bewegten Eigenschaften stilllegen. Ein globaler Abriss aller
     Übergänge nähme auch das nützliche Feedback: Hover- und Fokusfarben
     würden hart umschalten. Reduzierte Bewegung heißt keine Bewegung,
     nicht keine Rückmeldung. Die Farbübergänge bleiben deshalb.
     CSS-Animationen gibt es in diesem Stylesheet keine. */
  .crew.is-flow .crew__card,
  .reveal .section { transition: none !important; }
}
