/* Reset */

*, *::before, *::after{box-sizing:border-box;}
/* a{text-decoration:none; color:inherit; cursor:pointer;} */
button{background-color:transparent; color:inherit; border-width:0; padding:0; cursor:pointer;}
body, figure, th {margin:0;padding:0;}
input::-moz-focus-inner {border:0; padding:0; margin:0;}
ul, ol, dd, dl, th {margin:0; padding:0; list-style:none;}
h1, h2, h3, h4, h5, h6 {margin:0; font-size:inherit; font-weight:inherit;}
/* Lange Wörter (z. B. "Arbeiter:innenklassenpoetik" bei 28 px auf einem
   320-px-Viewport = ca. 334 px bei 280 px verfügbarer Breite) sollen
   umbrechen bzw. trennen statt zu horizontalem Scrollen oder abgeschnit-
   tenem Text zu führen (WCAG 1.4.10 Reflow). Nur Notumbruch ohne
   Trennstrich: ein Wort bricht erst dann mitten im Wort um, wenn es
   nicht in die Zeile passt (bewusst kein hyphens: auto, siehe
   Git-Historie). */
h1, h2, h3, h4, .text, .card-title, .card-title--bold, .buttons__label,
.beschreibungsliste__zeile, .accordion__label, .button {
    overflow-wrap: anywhere;
}
p{margin:0;}
cite {font-style:normal;}
th {font-weight:normal;}
td, th {vertical-align: top; text-align: left;}
fieldset{border-width:0; padding:0; margin:0;}
table {-webkit-border-horizontal-spacing: 0;-webkit-border-vertical-spacing: 0;}
table {width: 100%;}
img {max-width: 100%;height: auto;display: block;}
/* Reset für ein <hr> (z. B. aus Redaktionstext): kein Block "Linie" mehr
   im Blocksystem (bis 2026-09-21 Kirby-Standardblock "line", siehe
   Git-Historie), frühere Trennlinien-Divs (.linien-block) sind einem
   doppelten Überschriften-Abstand gewichen, siehe .heading--sektion
   weiter unten. margin: 0: der Abstand kommt komplett vom umgebenden
   row-gap (.grid/.stack). Ersetzt die Browser-Default-Optik (3D-Rahmen,
   eigene Höhe) durch eine einzelne 1px-Linie in --color-quaternary. */
hr {
    height: 0;
    margin: 0;
    border: none;
    border-top: 1px solid var(--color-quaternary);
}
/* Zentrale Link-Basis: --color-secondary + Unterstreichung, die schon im
   Default State sichtbar ist (nicht erst bei Hover) – gilt für JEDEN
   Link, der keine eigene, ausdrücklich abweichende Regel hat. Bewusste
   Ausnahmen: .card__link (eigene Farbe --color-primary UND ganz ohne
   Unterstreichung im Default State, damit Kartentitel wie eine
   Überschrift statt wie ein gewöhnlicher Link wirken – bei Hover
   trotzdem durchgezogen unterstrichen, siehe dort), .button
   (eigenständige Komponente mit eigener Deklaration) und .site-title
   (keine Unterstreichung). Einheitlich für Navbar, Footer, Breadcrumbs,
   Textblöcke, Sitemap usw. – ersetzt die früher in .desktop-nav a,
   .footer-menu a, .mobile-overlay ul a und .breadcrumb a jeweils
   einzeln wiederholten, identischen Deklarationen.
   Alle Unterstreichungen der Website sind gepunktet (dotted, bis
   2026-10-12 gestrichelt) und in
   der Farbe des jeweiligen Texts (currentColor), in jedem Zustand –
   einzige Ausnahme ist die Hauptnavigation (durchgezogen, siehe
   .desktop-nav a). text-decoration-style steht NACH dem
   Shorthand, der ihn sonst auf solid zurücksetzt (dasselbe gilt für
   .button weiter unten). */
a {
    color: var(--color-secondary);
    text-decoration: underline;
    text-decoration-style: dotted;
    text-decoration-color: currentColor;
    text-underline-offset: 0.3em;
    transition: color 150ms ease, text-decoration-color 150ms ease;
}
a:hover {
    color: var(--color-primary);
}
/* Unterstreichung von Links in Fließtext, Breadcrumb, Beschreibungsliste-
   Links [Downloads, Videolink, Bildcredit in Galerie/Video],
   Bildunterschrift/-credit und der Festivalmitwirkung-Zeile: in der
   Textfarbe des Links (bis 2026-10-12 fest --color-secondary), immer
   exakt 1px dünn (text-decoration-thickness statt Browser-Default
   "auto", das sonst mit der Schriftgröße/-art skaliert). Bei Hover
   verschwindet sie per Farb-Transition (text-decoration-color, siehe
   transition beim zentralen "a" oben) – EINE Regel statt einzelner,
   identischer Deklarationen an jeder Stelle.
   .beschreibungsliste__zeile a statt nur der spezifischeren
   .beschreibungsliste--downloads a: deckt dieselbe Optik einheitlich auch
   für andere über dieses Snippet gerenderte Links ab (z. B. "Videolink"
   auf der Video-Detailseite).
   .card__link/.accordion__label bewusst NICHT hier gelistet: die haben im
   Default State keine Unterstreichung (siehe dort), bei Hover erscheint
   sie in der Textfarbe (Regel weiter unten). .button--tertiary
   bewusst NICHT hier gelistet: .button setzt weiter unten
   text-decoration: underline (Shorthand, setzt Farbe/Stärke zurück) – bei
   gleicher Spezifität gewinnt in der Kaskade die spätere Regel, hier also
   .button. .button--tertiary bekommt dieselbe Optik daher direkt in
   seiner eigenen, später stehenden Regel (siehe dort). */
.text a,
.breadcrumb a,
.beschreibungsliste__zeile a,
.caption-text a,
.caption-credit a {
    text-decoration-color: currentColor;
    text-decoration-thickness: 1px;
}
.text a:hover,
.breadcrumb a:hover,
.beschreibungsliste__zeile a:hover,
.caption-text a:hover,
.caption-credit a:hover {
    text-decoration-color: transparent;
}
/* Die Textfarbe dieser Links bleibt bei Hover unverändert (statt des
   --color-primary vom zentralen "a:hover" oben): nur die Unterstreichung
   verschwindet. .text a/.beschreibungsliste__zeile a/.button--tertiary
   erben ihre Farbe ohnehin auch bei Hover (siehe dort). */
.breadcrumb a:hover,
.caption-text a:hover,
.caption-credit a:hover {
    color: var(--color-secondary);
}
/* Links im Fließtext (.text) sowie in Beschreibungslisten (Block
   Beschreibungsliste, Downloads, Eckdaten – alle über
   .beschreibungsliste__zeile) haben keine eigene Farbe: sie erben die
   Textfarbe des umgebenden Texts (statt der --color-secondary des
   zentralen "a" oben) und unterscheiden sich davon nur durch die
   Unterstreichung. Tertiary-Buttons ebenso, siehe .button--tertiary
   weiter unten. Steht nach dem zentralen "a"/"a:hover" (gleiche
   Spezifität), gilt daher auch bei Hover. */
.text a,
.beschreibungsliste__zeile a {
    color: inherit;
}
/* .card__link/.accordion__label: Unterstreichung im Default State
   unsichtbar (siehe dort, text-decoration-color: transparent statt
   text-decoration-line: none) – bei Hover blendet sie sich per
   Farb-Transition sanft ein statt abrupt zu erscheinen, dieselbe Technik
   wie der Farbwechsel beim zentralen "a" oben (text-decoration-line
   selbst lässt sich nicht transitionieren, text-decoration-color als
   Farbwert dagegen schon). .programm-card:hover .card__link zusätzlich
   gelistet: der "Stretched Link" macht die ganze Programmpunkt-Card
   klickbar (siehe .card__link::after weiter unten), der Hover-Zustand
   soll daher schon greifen, sobald irgendwo auf der Card gehovert wird,
   nicht erst bei direktem Hover über dem (optisch kleinen) Titeltext
   selbst. */
.card__link:hover,
.programm-card:hover .card__link,
.accordion__summary:hover .accordion__label {
    text-decoration-color: currentColor;
}
/* Basis-Optik für alle Icons (siehe snippets/icon.php, assets/js/site.js
   linkIconName()) – Inline-SVG statt der früheren Icon-Font (siehe
   Git-Historie), diese Regel übernimmt deren Rolle: inline-block wie
   zuvor die Icon-Font-Basis, width/height "1em" lässt das Icon wie ein
   Schriftzeichen mit der jeweiligen font-size mitskalieren (dasselbe
   Verhalten wie zuvor) – vorausgesetzt, die font-size an der jeweiligen
   Einsatzstelle stimmt tatsächlich mit dem danebenstehenden Text überein
   (reine Vererbung reicht dafür nicht immer, siehe z. B. .accordion__icon
   weiter unten, wo Icon und Titel Geschwister statt verschachtelt sind).
   Die stroke-Farbe kommt direkt aus dem SVG-Markup selbst (stroke=
   "currentColor", siehe icon.php) und folgt damit automatisch der
   geerbten Textfarbe, ganz ohne eigene color-Regel hier. vertical-align:
   text-bottom statt des Inline-Default "baseline" (bis 2026-09-2X
   "middle", zwischenzeitlich "text-top", siehe Git-Historie) – ein
   inline-block sitzt sonst mit seiner UNTERKANTE auf der
   Textgrundlinie, wodurch das Icon sichtbar zu hoch wirkt (die
   Oberkante ragt um die volle Icon-Höhe über die Grundlinie hinaus);
   "text-bottom" richtet die Icon-Unterkante stattdessen an der
   Unterkante des umgebenden Texts aus. */
.icon {
    display: inline-block;
    width: 0.9em;
    height: 0.9em;
    /* Seit 2026-10-19 etwas kleiner und mit festem Versatz unter die
       Grundlinie statt text-bottom: optisch mittiger zum Text. */
    vertical-align: -0.1em;
}
/* Icon NACH bzw. VOR dem Text (Anker-, externe und Download-Links, siehe
   assets/js/site.js linkIconName()): einheitlich --space-xxs Abstand zum
   Text, je Icon-Art über einen eigenen Modifier (siehe .link-icon--anker,
   --external-link, --download unten). */
/* external-link (siehe .link-icon--external-link weiter unten) braucht
   einen eigenen Abstand: seine Glyphe (.import/Icons SVG/
   external-link.svg) hat links kaum Leerraum innerhalb der 24x24-Box,
   ohne margin-left wirkt es, als klebe es am Text. */
.link-icon--external-link {
    margin-left: var(--space-xxs);
}
/* Sprunglinks (Pfeil ↓/↑ nach dem Text, siehe assets/js/site.js
   linkIconName(): Inhaltsverzeichnis, "Zum Inhalt", Anker im Buttons-
   Block) – derselbe Abstand zum Text wie bei externen Links und
   Downloads (seit 2026-10-14, vorher ohne Abstand). */
.link-icon--anker {
    margin-left: var(--space-xxs);
}
/* Letztes Wort + nachgestelltes Icon als untrennbare Einheit (siehe
   assets/js/site.js iconAnLetztesWort(), seit 2026-10-19): der Link
   bricht weiterhin normal um, das Icon rutscht aber nie allein in die
   nächste Zeile. */
.link-icon-gruppe {
    white-space: nowrap;
}
/* Download-Icon steht als einziges VOR dem Label statt danach (siehe
   assets/js/site.js, dort insertAdjacentHTML "afterbegin" nur für
   iconName "download"). margin-right statt padding: padding würde bei
   globalem box-sizing: border-box (siehe *-Regel oben) den Content-Box-
   Anteil und damit die sichtbare Icon-Größe verkleinern, exakt der
   Bug, den margin hier vermeidet. */
.link-icon--download {
    margin-right: var(--space-xxs);
}
/* Die Unterstreichung des Links (siehe z. B. .beschreibungsliste__zeile
   a) soll beim Download-Icon nicht auch durch die Lücke zwischen Icon
   und Label laufen – ein margin-Abstand liegt außerhalb jeder Kind-Box
   und lässt sich nicht gezielt von der Linie ausnehmen. Stattdessen
   zeichnet nicht mehr das <a> selbst die Linie, sondern nur noch der
   Label-Wrapper um den reinen Text (siehe assets/js/site.js, wickelt
   dafür den ursprünglichen Linktext in ein <span class="link-label">).
   text-decoration-*: inherit am Label übernimmt Farbe/Strichart/Stärke
   dynamisch von der jeweils geltenden a-Regel (z. B. secondary, bei Hover
   transparent bei .beschreibungsliste__zeile a), ohne sie hier zu
   duplizieren. */
a:has(> .link-icon--download) {
    text-decoration-line: none;
}
a:has(> .link-icon--download) .link-label {
    text-decoration-line: underline;
    text-decoration-color: inherit;
    text-decoration-style: inherit;
    text-decoration-thickness: inherit;
    transition: text-decoration-color 150ms ease;
}
/* Einheitlicher, projektweiter Fokusring (WCAG 2.4.7 Focus Visible) –
   EINE Regel statt der früher sechs einzelnen, fast identischen
   :focus-visible-Regeln pro Komponente (Navbar, Buttons, Cards, Galerie-
   Trigger, Lightbox-Buttons, Select), gilt automatisch für jedes
   fokussierbare Element. Eigene Farbe (--color-focus, siehe :root)
   statt outline-style: auto, damit der Ring browser-
   übergreifend identisch aussieht statt je nach Browser leicht
   unterschiedlich blau zu sein. */
:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
/* Fokusring am umgebenden Container statt am (per Stretched Link/
   position: absolute) bündig sitzenden Link: overflow: hidden schneidet
   nur Nachfahren eines Elements ab, NICHT dessen eigenen Outline – der
   Ring liegt hier daher vollständig sichtbar um die ganze Card bzw. das
   ganze Video-Cover (bis 2026-09-2X saß er am Link selbst und wurde von
   overflow: hidden abgeschnitten, WCAG 2.4.7/2.4.11, siehe Git-
   Historie). */
.card:has(.card__link:focus-visible),
.programm-card:has(.card__link:focus-visible),
.video-cover:has(.video-cover__link:focus-visible) {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.card__link:focus-visible,
.video-cover__link:focus-visible {
    outline: none;
}
/* Skip-Link (siehe snippets/header.php): für Maus-Nutzer:innen unsichtbar,
   bei Tastaturfokus über der sticky Navbar eingeblendet (WCAG 2.4.1). */
.skip-link {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2000;
    padding: var(--space-xs) var(--space-sm);
    background-color: var(--color-background);
    color: var(--color-primary);
    transform: translateY(-100%);
}
.skip-link:focus {
    transform: translateY(0);
}
/* Sprungziel des Skip-Links: programmatisch fokussierbar (tabindex="-1"),
   aber kein interaktives Element – kein Ring um den gesamten Inhalt. */
main:focus {
    outline: none;
}
/* Nur für Screenreader lesbar, optisch verborgen (z. B. Positionsansage
   der Lightbox). */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
/* Lightbox und Menü-Overlay setzen beim Öffnen den Fokus auf ihren
   Schließen-Button (siehe assets/js/site.js). Seit 2026-10-19 ohne
   erzwungenen Fokusring: der Browser zeigt ihn über :focus-visible (siehe
   oben) bei Tastaturbedienung, nach Maus-/Touch-Bedienung nicht (WCAG
   2.4.7 verlangt die Sichtbarkeit nur bei Tastaturbedienung). */
/* WCAG 2.4.11 Focus Not Obscured (Minimum, neu in WCAG 2.2): Ein
   fokussiertes Element darf nicht vollständig hinter eigenen Inhalten
   verschwinden – hier relevant wegen der sticky Navbar (siehe
   .site-header, position: sticky, ~46px hoch auf Desktop). Browser
   scrollen ein per Tab fokussiertes Element automatisch in den
   sichtbaren Bereich, wissen aber nichts von der per CSS überlagernden
   Navbar – ohne scroll-margin-top gilt ein Element bereits als "sichtbar
   genug", sobald es geometrisch im Viewport liegt, auch wenn die Navbar
   optisch darüberliegt (live nachgewiesen: elementFromPoint traf dort
   auf .navbar-bar statt den fokussierten Link). Dasselbe Prinzip wie
   .heading weiter unten (dort für Anker-Link-Ziele), hier zusätzlich für
   jedes fokussierbare Element. */
a, button, input, select, textarea, summary, [tabindex] {
    /* Navbar-Höhe (siehe --navbar-height in :root) statt eines festen
       Werts: die tatsächliche Navbar ist höher als die früher verwendeten
       --space-xl, ein per Tab angesprungenes Element konnte dadurch mit
       seinem Fokusring teilweise unter dem sticky Header liegen
       (Pre-Launch-Audit 2026-09-24). summary (Akkordeon) ergänzt. */
    scroll-margin-top: calc(var(--navbar-height) + var(--space-md));
}
picture {display: block;}

/* Platzhalter für per loading="lazy" verzögerte Bilder (siehe snippets/
   picture.php): "picture" passt sich der intrinsischen Größe des
   enthaltenen <img> an (dessen width/height-Attribute reservieren den
   Platz bereits, siehe Kommentar dort zu CLS) – bis das Bild eingetroffen
   ist, zeigt diese Fläche --color-quinary statt leeren/weißen
   Freiraums. Sobald das Bild geladen ist, überdeckt es die Fläche
   deckend, keine weitere Logik (kein JavaScript) nötig. Gilt bewusst für
   ALLE Bilder der Seite (auch die wenigen eager geladenen, z. B. Hero) –
   eine Fallunterscheidung wäre unnötige Komplexität, bei eager-Bildern ist
   die Fläche ohnehin nur für Sekundenbruchteile sichtbar. Ausnahmen -
   beide zeigen ihr Bild seitenverhältnistreu per object-fit: contain
   (nicht cover), füllen die Box also NICHT immer vollständig aus: bei
   nicht passendem Seitenverhältnis (.lightbox__picture) bzw. IMMER (.
   gallery__image--logo, absichtlich zusätzlich mit Padding, siehe dort)
   bliebe die Fläche dauerhaft (auch nach dem Laden) sichtbar statt nur
   bis zum Laden:
   - .lightbox__picture (siehe snippets/lightbox.php) – dort übernimmt
     ohnehin die eigene Sofort-Vorschau (siehe .lightbox__image--preview)
     dieselbe Aufgabe.
   - .gallery__image--logo (siehe blocks/gallery.yml "Logo-Darstellung"):
     Logos füllen ihren 3:2-Rahmen so gut wie nie vollständig aus (eigenes
     Seitenverhältnis, zusätzlich eingerückt durch das Padding) – die
     Fläche bliebe dort dauerhaft als sichtbarer Rahmen um jedes Logo
     stehen, nicht nur während des Ladens. */
picture:not(.lightbox__picture):not(.gallery__image--logo) {
    background-color: var(--color-quinary);
}

/* Webfonts */

/* --font-heading (vormals --font-sans): unverändert wie zuvor, eigene
   Familie mit nur einem Schnitt. */
@font-face {
font-family: 'T-Star-Heavy';
src: local('T-Star-Heavy'), url('/assets/fonts/T-Star-Heavy.woff2') format('woff2'),
url('/assets/fonts/T-Star-Heavy.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}

/* --font-subheading: vorläufiger Name (siehe :root-Kommentar zur
   Variable) – Schnitt "T-Star-Bold" liegt zwischen --font-body (T-Star
   Medium, 400) und --font-heading (T-Star-Heavy, 700) und war bislang
   ungenutzt (Schriftdatei lag bereits in assets/fonts/, siehe
   T-Star-Bold.woff2/.woff). Nur der reguläre Schnitt registriert,
   T-Star-BoldItalic bleibt vorerst ungenutzt, solange kein konkreter
   Einsatzzweck feststeht (keine unnötige Komplexität ohne Bedarf). */
@font-face {
font-family: 'T-Star-Bold';
src: local('T-Star-Bold'), url('/assets/fonts/T-Star-Bold.woff2') format('woff2'),
url('/assets/fonts/T-Star-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}

/* --font-body (vormals --font-serif, EB Garamond): eine Familie mit drei
   Schnitten statt separater Familiennamen pro Schnitt – dadurch lösen
   strong/b (font-weight:700) und em/i (font-style:italic) automatisch
   den richtigen Schnitt auf, ohne dass jede Stelle im CSS die Datei
   einzeln benennen müsste (genau wie EB Garamond bisher aufgebaut war:
   Regular/Bold/Italic unter einem gemeinsamen Familiennamen). */
@font-face {
font-family: 'T-Star';
src: local('T-Star-Medium'), url('/assets/fonts/T-Star-Medium.woff2') format('woff2'),
url('/assets/fonts/T-Star-Medium.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'T-Star';
src: local('T-Star-Medium Italic'), url('/assets/fonts/T-Star-MediumItalic.woff2') format('woff2'),
url('/assets/fonts/T-Star-MediumItalic.woff') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'T-Star';
src: local('T-Star-Heavy'), url('/assets/fonts/T-Star-Heavy.woff2') format('woff2'),
url('/assets/fonts/T-Star-Heavy.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}

/* Metrik-angeglichene Fallback-Schriften gegen Layout Shift beim Font-
   Wechsel (font-display: swap, Lighthouse-Audit 2026-09-24: CLS 1.0 auf
   der Programmseite, Verursacher: die drei Webfonts). Jede Fallback-Face
   zeigt lokal Arial (bzw. Arial Bold/Italic) mit size-adjust/ascent-/
   descent-/line-gap-override, sodass Zeichenbreite und Zeilenmaße der
   Ersatzschrift denen des jeweiligen T-Star-Schnitts entsprechen – Zeilen-
   umbrüche und Texthöhen ändern sich beim Nachladen dadurch kaum bis gar
   nicht. Werte berechnet aus den echten Schriftdateien (Durchschnittsbreite
   über eine gewichtete deutsche Buchstabenhäufigkeit, hhea-Metriken), NICHT
   geschätzt; bei neuen/getauschten Schriftdateien neu berechnen. Wird nur
   verwendet, solange der Webfont noch nicht geladen ist (siehe die
   --font-*-Stacks in :root, jeweils direkt hinter dem Webfont). */
@font-face {
font-family: 'T-Star Fallback';
src: local('Arial'), local('ArialMT');
font-weight: 400;
font-style: normal;
size-adjust: 94.44%;
ascent-override: 98.15%;
descent-override: 22.98%;
line-gap-override: 0%;
}
@font-face {
font-family: 'T-Star Fallback';
src: local('Arial Italic'), local('Arial-ItalicMT');
font-weight: 400;
font-style: italic;
size-adjust: 94.49%;
ascent-override: 98.10%;
descent-override: 22.96%;
line-gap-override: 0%;
}
@font-face {
font-family: 'T-Star Fallback';
src: local('Arial Bold'), local('Arial-BoldMT');
font-weight: 700;
font-style: normal;
size-adjust: 88.19%;
ascent-override: 105.11%;
descent-override: 24.61%;
line-gap-override: 0%;
}
@font-face {
font-family: 'T-Star-Heavy Fallback';
src: local('Arial Bold'), local('Arial-BoldMT');
font-weight: 700;
font-style: normal;
size-adjust: 88.19%;
ascent-override: 105.11%;
descent-override: 24.61%;
line-gap-override: 0%;
}
@font-face {
font-family: 'T-Star-Bold Fallback';
src: local('Arial Bold'), local('Arial-BoldMT');
font-weight: 700;
font-style: normal;
size-adjust: 88.07%;
ascent-override: 105.26%;
descent-override: 24.64%;
line-gap-override: 0%;
}

/* Animation */

/* Bewegungs-Tokens: eine gemeinsame Zeit-/Kurven-Skala für Übergänge auf
   dieser Seite (Seitenwechsel unten, künftige Komponenten-Animationen) –
   ein Wert je Stufe statt verstreuter Einzelwerte, damit sich das
   Bewegungstempo der Seite an einer Stelle austarieren lässt.
   --ease-standard/--ease-enter sind aktuell identisch (beide für "sanft
   ankommen"), bewusst als zwei Namen geführt: ein künftiges
   Auseinanderziehen bleibt dadurch möglich, ohne Aufrufstellen
   umzubenennen. */
:root {
    --motion-standard: 320ms;
    --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-enter: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Keine Übergänge (transition) während des Ladens: Kommt eine Seite sehr
   schnell an (statischer Cache, siehe config.php "cache"), berechnet der
   Browser Elemente mitunter zuerst ohne die Stilregeln und "animiert"
   danach von seiner Standard-Darstellung zur richtigen – sichtbar als
   kurz aufblitzende Unterstreichung in der Hauptnavigation (2026-10-12).
   Die Klasse steht im HTML (snippets/header.php) und wird von
   assets/js/site.js nach dem ersten Zeichnen entfernt. Betrifft nur
   transition, nicht die Einblende-Animation von main (animation). */
.laedt *,
.laedt *::before,
.laedt *::after {
    transition: none !important;
}

@keyframes page-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Seitenwechsel: reines Einblenden als CSS-Enter-Animation auf dem frisch
   geladenen Dokument selbst – läuft bei JEDER Navigation automatisch
   erneut, weil jede Seite ein eigener, frischer Dokument-Request ist,
   ganz ohne JavaScript und ohne eigenen Client-Router. Bis 2026-09-30
   zusätzlich eine Cross-Document View Transition (@view-transition,
   siehe Git-Historie): endgültig entfernt, nachdem ein zweiter
   Einführungsversuch (samt ::view-transition { pointer-events: none; }
   gegen einen früheren, unbestätigten Verdacht) in Chrome reproduzierbar
   zu "Uncaught (in promise) InvalidStateError: Transition was aborted
   because of invalid state. Page already revealed" führte – ein
   dokumentierter Fehler in Chromes eigener Verwaltung schneller
   aufeinanderfolgender Navigationen (begünstigt durch den aktiven
   Kirby-Seiten-Cache, siehe config.php), nicht durch Code dieses
   Projekts verursacht oder behebbar. Nur main blendet ein, Navbar und
   Breadcrumb stehen sofort (der Breadcrumb ist auf jeder Seite fast
   identisch – ein Einblenden wirkte als kurzes Aufblitzen). */
main {
    animation: page-in var(--motion-standard) var(--ease-enter) both;
}

/* Ausnahme Mediathek/Mitwirkende: dort ändert eine Filterung (eigene
   Pfade je Filter, siehe site/plugins/filterseiten, templates/
   mediathek.php/mitwirkende.php) nur den Ergebnisbereich –
   Breadcrumb, H1 und Filter-Buttons bleiben inhaltlich identisch. Ein
   Enter-Keyframe auf dem ganzen main faded zwangsläufig ALLES darin mit,
   ungeachtet dessen, was sich tatsächlich geändert hat. Da ohne
   JavaScript nicht unterscheidbar ist, ob eine Navigation zu dieser
   Seite von einem Filter-Klick oder von woanders kam (jede Seite ist ein
   eigener, zustandsloser Dokument-Request), gilt diese Regel bewusst für
   JEDEN Aufruf dieser Templates: main selbst bekommt keine Animation
   mehr (erscheint sofort), stattdessen fadet
   gezielt nur der als .filter-content markierte Ergebnisbereich (siehe
   dort in den Templates) – "nur das Veränderte bewegt sich". */
body.page-mediathek main,
body.page-mitwirkende main {
    animation: none;
}
body.page-mediathek .filter-content,
body.page-mitwirkende .filter-content {
    animation: page-in var(--motion-standard) var(--ease-enter) both;
}

/* Bei reduzierter Bewegungspräferenz entfällt die Einblend-Animation auf
   main/.filter-content vollständig. Der Seitenwechsel selbst (der
   eigentliche Zustandswechsel) bleibt erhalten, nur die nicht-
   essenzielle Bewegung/Überblendung entfällt: Inhalt steht sofort in
   voller Deckkraft an seiner finalen Position, statt nur schneller/
   kürzer zu animieren. */
@media (prefers-reduced-motion: reduce) {
    main,
    .filter-content {
        animation: none;
    }
}

/* Variables */

:root {
    /*
     * Spacing-Skala: T-Shirt-Größen statt px-Benennung (Werte in rem).
     * xxs/xs bleiben auf allen Breakpoints gleich, sm/md/lg/xl passen
     * sich über die @media-Regeln weiter unten pro Breakpoint an (siehe
     * dort für die einzelnen Stufen) – --space-120 (Sektion-zu-Footer-
     * Abstand) hat keine eigene Stufe mehr, sondern setzt sich aus xl + lg
     * zusammen (siehe .section), um den bisherigen 1:2-Rhythmus zu
     * Sektionsabständen (jetzt xl) exakt zu erhalten.
     */
    --space-xxs: 0.25rem;  /* 4px */
    --space-xs:  0.5rem;   /* 8px */
    --space-sm:  1rem;  /* 16px */
    --space-md:  1.5rem;  /* 24px */
    --space-lg:  2rem;   /* 32px */
    --space-xl:  3rem;   /* 48px */

    /* Einheitlicher linker/rechter Außenabstand für .grid (padding-inline)
       UND alle Bars außerhalb des Grids (Navbar, Breadcrumb, Footer,
       Mobile-Overlay – siehe dort) – ersetzt das frühere
       spaltenbasierte --grid-indent-1col: das Grid nutzt seine Spalten
       inzwischen komplett randlos aus (kein freier "1 Spalte"-Einzug
       mehr, siehe .span-sm/md/lg weiter unten), der Außenabstand kommt
       jetzt vollständig aus diesem einen Wert statt aus einer Mischung
       aus Spaltenbreite + Padding. Default = --space-lg (Tablet-Portrait/
       Tablet-Landscape/Desktop/Screen – bewusst NICHT mehr identisch mit
       dem column-gap von .grid auf Tablet-Landscape/Desktop/Screen, siehe
       dort: der Außenabstand soll dort kleiner sein als der Abstand
       zwischen den Spalten). Gilt auf allen Breakpoints, auf Mobile/
       Tablet-Portrait mit dem dort kleineren --space-lg (24px). */
    --content-margin: var(--space-lg);

    /* Abstand unter der Navbar bis zur Breadcrumb-Bar (siehe
       .breadcrumb-bar padding-top): --space-lg, auf Mobile --space-sm
       (siehe @media bei --content-margin weiter unten; seit 2026-10-16,
       davor u. a. --space-xl bzw. --space-md/--space-sm).
       Grundlage für --abstand-bis-inhalt unten. */
    --abstand-unter-navbar: var(--space-lg);

    /* Abstand unter der Navbar bis zur ersten Überschrift einer Seite
       MIT Breadcrumb: --abstand-unter-navbar + eine Breadcrumb-Zeile
       (--text-sm × --leading-sm, siehe .breadcrumb ol) + Abstand zum
       Inhalt (--space-xs, siehe .breadcrumb-bar padding-bottom).
       Gemeinsam für .section--no-breadcrumb (Startseite) und
       .mobile-overlay__content (Burger-Menü), seit 2026-10-16: h1 bzw.
       erster Menüpunkt stehen auf derselben Höhe wie die h1 einer Seite
       mit Breadcrumb. Ändert sich die Breadcrumb-Bar, hier mitziehen. */
    --abstand-bis-inhalt: calc(var(--abstand-unter-navbar) + var(--text-sm) * var(--leading-sm) + var(--space-xs));

    /* Deckelt die Inhaltsbreite auf sehr großen Bildschirmen (siehe
       .grid/.navbar-bar/.breadcrumb-bar/.footer, jeweils max-width +
       margin-inline: auto) statt sie unbegrenzt mit dem Viewport
       mitwachsen zu lassen – siehe ausführlichen Kommentar bei .grid für
       die Begründung. EINE zentrale Variable statt vier Wiederholungen
       desselben Werts, damit eine künftige Änderung nur hier nötig ist. */
    --content-max-width: 1800px;

    /* Schrift von Seitentitel, Desktop-Navigation und Menü-Button
       (siehe .site-title/.desktop-nav/.menu-toggle): eine gemeinsame
       Stufe, unter 370px eine Stufe kleiner (siehe @media weiter
       unten; unter 340px zusätzlich das Festivalakronym statt des
       vollen Titels, siehe .site-title__kurz). Als eigene Variablen, damit --navbar-height direkt
       darunter automatisch mitrechnet. */
    --navbar-text: var(--text-md);
    --navbar-leading: var(--leading-md);
    --navbar-tracking: var(--tracking-md);

    /* Höhe der sticky Navbar (siehe .site-header/.navbar-bar), berechnet
       statt geschätzt: zweimal deren padding-block (--space-xs) plus die
       Zeilenhöhe der Navbar-Schrift (--navbar-text/--navbar-leading).
       Bleibt dadurch automatisch korrekt, auch wenn sich diese Werte je
       Breakpoint ändern – u. a. für den Scroll-Offset von Anker-Links
       verwendet (siehe .block weiter unten). */
    --navbar-height: calc(var(--space-xs) * 2 + var(--navbar-text) * var(--navbar-leading));

    /* Border-Radius: Cards UND Pillen (.card-label--pills) sind
       mittlerweile eckig (kein Radius mehr) – --radius-md bleibt als
       einzige verbliebene Stufe für Buttons (.button), komplett rund bei
       üblicher Button-Höhe. */
    --radius-md: 32px; /* für Buttons */

    /* Schriftgrößen: gleiches Benennungsschema wie Spacing. Tablet/Mobile
       passen md/lg/xl an (siehe @media weiter unten), xs/sm bleiben auf
       allen Breakpoints gleich. */
    --text-xs: 0.75rem; /* 12px */
    --text-sm: 1rem;    /* 16px – Navigation, Buttons, Utility, h4 */
    --text-md: 1.25rem;  /* 20px – h3, Text, Default */
    --text-lg: 1.75rem;  /* 28px – h2, Großtext */
    --text-xl: 3rem;    /* 48px – h1 */

    /* Letter-Spacing: ein gemeinsamer Satz für --font-body UND
       --font-heading (bisher pro Rolle getrennte Werte) und ohne eigene
       Breakpoint-Anpassung mehr – der Unterschied wäre bei den jetzt
       gröberen Stufen kaum wahrnehmbar gewesen. */
    --tracking-xs: 0.03em;
    --tracking-sm: 0.02em;
    --tracking-md: 0.01em;
    --tracking-lg: 0em;
    --tracking-xl: -0.01em;

    /* Line-Height, ebenfalls ohne eigene Breakpoint-Anpassung */
    --leading-xs: 1.5;
    --leading-sm: 1.5;
    --leading-md: 1.45;
    --leading-lg: 1.35;
    --leading-xl: 1.15;

    /* Schriftarten: "serif/sans" ergab keinen Sinn mehr, seit EB Garamond
       durch T-Star ersetzt wurde – Rollen-Namen (body/heading) statt
       Stilart-Namen, Werte selbst unverändert gegenüber vorher. */
    --font-body: 'T-Star', 'T-Star Fallback', Arial, Helvetica, sans-serif;         /* Fließtext (vormals --font-serif, war EB Garamond) */
    --font-heading: 'T-Star-Heavy', 'T-Star-Heavy Fallback', Arial, Helvetica, sans-serif; /* Überschriften/UI-Chrome (vormals --font-sans, unverändert) */
    /* --font-subheading: Zwischenstufe zwischen --font-body und
       --font-heading – Card-Titel (.card-title--bold, .card-title bei
       Person/Programmpunkt), Akkordeon-Label und Downloads (siehe
       jeweilige Regeln). */
    --font-subheading: 'T-Star-Bold', 'T-Star-Bold Fallback', Arial, Helvetica, sans-serif;

    /* Farben */
    --color-primary: #222;    /* Schwarzwert (kein Reinschwarz) – Default für allen Text */
    --color-secondary: #707070; /* Dunkelgrau – 4,95:1 auf Weiß (WCAG AA) und deutlich
                                    unterscheidbar von --color-primary; Auszeichnungs-/Utility-Text */
    --color-tertiary: #909090; /* Grau für Elemente, die nur das WCAG-1.4.11-
                                   Kontrastverhältnis (3:1 zu --color-background)
                                   statt des Text-Kontrasts (4.5:1) erfüllen
                                   müssen – aktuell alle Card-Border (siehe .card).
                                   3,19:1 zu Weiß, mit etwas Sicherheitsabstand
                                   über der Mindestanforderung. */
    --color-quaternary: #ddd; /* Helles Grau, nur für rein dekorative Elemente
                                   ohne jedes Kontrastverhältnis (hr, siehe
                                   dort) – WCAG 1.4.11 gilt nur für UI-Komponenten/
                                   inhaltstragende Grafiken, nicht für rein
                                   dekorative. */
    --color-quinary: #e6e6e6; /* Sehr helles Grau, nur für die Platzhalter-Fläche
                                   hinter noch ladenden Bildern (siehe "picture")
                                   – rein dekorativ, wie --color-quaternary. */
    --color-background: #fff; /* Seiten-/Button-Hintergrund */
    --color-error: #c0392b; /* Rotton für Fehlerzustände (z. B. templates/
                                error.php) – 5,44:1 auf Weiß (WCAG AA für
                                Text, Mindestanforderung 4,5:1), mit
                                Sicherheitsabstand wie --color-secondary/
                                -focus oben. */
    --color-focus: #005fcc; /* Fokusring-Blau (siehe :focus-visible weiter unten) –
                                bewusst eigens definiert statt outline-style: auto,
                                damit die Farbe browserübergreifend identisch bleibt.
                                5,99:1 zu --color-background (Weiß). */
}

/* Vier benannte Breakpoint-Stufen, jede nach ihrer Zuständigkeit statt
   nach Ordnungszahl benannt (frühere "Tablet 1"/"Tablet 2"-Bezeichnung
   war nicht eindeutig genug – beide Namen wurden für unterschiedliche,
   einander teils überlappende Bereiche verwendet). Eine einzige
   gemeinsame Schwelle für Layout (Grid/Spans, siehe .grid weiter unten)
   UND Navigation (Hauptnav → Burger-Menü, siehe @media (min-width:
   1080px) weiter unten) statt zweier unabhängiger Schwellen wie zuvor:
   – Mobile (≤ 500px) UND Tablet-Portrait (501–720px): kleinste Schrift-/
     Spacing-Stufe (Tablet-Portrait mit eigenen, etwas größeren Werten
     als Mobile, siehe die jeweiligen @media-Regeln direkt unten),
     Column-Gap/--content-margin --space-lg (24px), Burger-Menü aktiv. 2-Spalten-
     Grid nur auf Mobile, Tablet-Portrait bleibt 4-spaltig (siehe :root
     "Seitenraster" weiter unten).
   – Tablet-Landscape (721–1079px): 4-Spalten-Grid mit Burger-Menü,
     Column-Gap/--content-margin --space-lg (32px), gemeinsame Schrift-/
     Spacing-Stufe mit Desktop/Screen bis auf eine eigene, kleinere
     --text-xl-Stufe (siehe @media-Regeln direkt unten).
   – Desktop (1080–1535px): Default-Werte (kein eigenes @media nötig,
     siehe :root Basiswerte), 8-Spalten-Grid, Column-Gap/
     --content-margin --space-lg (32px), Hauptnav statt Burger-Menü.
   – Screen (≥ 1536px, seit 2026-10-19; 2026-10-16 kurz ab 1440px): wie Desktop, aber größere
     Schrift (--text-md/-lg/-xl, --text-xs/-sm bleiben) und größeres
     --space-xl (siehe @media direkt unten) – auf großen Bildschirmen
     angenehmere Zeilenlängen statt nur mehr Leerraum. Navbar-Schrift
     (--navbar-text = --text-md) und Logo (--text-xl) wachsen mit. (Bis
     2026-09-2X gab es schon einmal eine Screen-Stufe nur für --text-xl,
     siehe Git-Historie.)
   Spaltenzahl und Span-Breiten (--grid-cols/--span-*) folgen demselben
   Schema, siehe :root "Seitenraster".
   Row-Gap von .grid/.card-grid ist bewusst die EINZIGE Ausnahme ohne
   eigene Breakpoint-Stufe: bleibt durchgehend bei --space-lg (siehe .grid
   weiter unten), unabhängig vom Breakpoint – anders als Column-Gap/
   --content-margin oben, die weiterhin wie gewohnt je Stufe wechseln. */
@media only screen and (min-width: 1536px) {
    :root {
        --text-md: 1.375rem; /* 22px */
        --text-lg: 2rem;     /* 32px */
        --text-xl: 3.25rem;  /* 52px */
        --space-xl: 4rem;    /* 64px */
    }
}
@media only screen and (min-width: 501px) and (max-width: 1079px) {
    :root {
        --text-md: 1.25rem; /* 20px */
        --text-lg: 1.5rem;    /* 24px */
        --text-xl: 2.75rem;    /* 44px */
    }
}
/* Tablet-Portrait (501–720px, siehe :root-Breakpoint-Kommentar weiter
   oben und die entsprechende --span-md-Ausnahme bei "Seitenraster"
   weiter unten): --text-md/-lg/-xl etwas kleiner als auf Tablet-
   Landscape, auf den schmaleren Portrait-Breiten wirken sie sonst
   überproportional groß im Verhältnis zur Spaltenbreite. */
@media only screen and (min-width: 501px) and (max-width: 720px) {
    :root {
        --text-md: 1.125rem; /* 18px */
        --text-lg: 1.375rem; /* 22px */
        --text-xl: 2.25rem; /* 36px */
    }
}
@media only screen and (max-width: 500px) {
    :root {
        --text-md: 1.125rem; /* 18px */
        --text-lg: 1.375rem; /* 22px */
        --text-xl: 1.875rem;  /* 30px */
    }
}

/* Sehr schmale Smartphones (unter 370px): Seitentitel und Menü-Button
   in --text-sm statt --text-md (siehe --navbar-text im :root), sonst
   wird es in der Navbar zu eng. Eigene Schwelle nur für die Navbar. */
@media only screen and (max-width: 369px) {
    :root {
        --navbar-text: var(--text-sm);
        --navbar-leading: var(--leading-sm);
        --navbar-tracking: var(--tracking-sm);
    }
}

/* Spacing-Stufe von Mobile gilt jetzt auch für Tablet-Portrait (bis
   2026-09-2X eigene, 500px-Schwelle wie --text-* oben, siehe Git-
   Historie) – auf den schmaleren Portrait-Breiten wirkte der sonst
   größere Tablet/Desktop-Wert (--space-lg/--space-xl etc.) zu groß im
   Verhältnis zur jetzt ebenfalls verkleinerten Schrift dort. */
@media only screen and (max-width: 720px) {
    :root {
        --space-sm:  0.75rem;  /* 12px */
        --space-md:  1.25rem;   /* 20px */
        --space-lg:  1.5rem;   /* 24px */
        --space-xl:  2rem;   /* 32px */
    }
}

/* --content-margin (siehe :root oben) ist auf allen Breakpoints
   --space-lg – auch auf Mobile/Tablet-Portrait (dort über die Spacing-
   Stufe oben 24px statt 32px); bis 2026-10 überschrieben diese beiden
   gezielt auf --space-md. --content-margin ist zugleich Seitenrand UND
   Column-Gap von .grid/.card-grid/Footer-Spalten (siehe dort). */
@media only screen and (max-width: 500px) {
    :root {
        --abstand-unter-navbar: var(--space-sm);
    }
}

/* Seitenraster: EINE zentrale Quelle für Spaltenzahl und Span-Breiten je
   Breakpoint (Anzahl Spalten, nicht Pixel) – .grid, .span-sm/md/lg, die
   Card-Grids (.card-grid), "Alle Tage" der Programm-Übersicht und die
   Footer-Spalten lesen alle dieselben vier Werte, statt jeweils eigene
   Breakpoint-Regeln mit hartcodierten Spaltenzahlen zu pflegen.
   – Desktop/Screen (≥ 1080px): 8 Spalten; span-sm 2, span-md 4, span-lg 8.
   – Tablet-Landscape (721–1079px): 4 Spalten; span-sm 2, span-md 3, span-lg 4.
   – Tablet-Portrait (501–720px): 4 Spalten; span-sm 2, span-md 4 (volle
     Breite statt 3-von-4, siehe eigene Regel unten – bewusst die einzige
     Ausnahme von der sonst einheitlichen 1080px-Tablet-Schwelle, siehe
     :root-Breakpoint-Kommentar weiter oben), span-lg 4.
   – Mobile (≤ 500px): 2 Spalten; alle Spans 2 (volle Breite).
   Column-Gap ist überall --content-margin (siehe oben), Row-Gap steuert
   jede Komponente selbst. */
:root {
    --grid-cols: 8;
    --span-sm: 2;
    --span-md: 4;
    --span-lg: 8;
}
@media only screen and (max-width: 1079px) {
    :root {
        --grid-cols: 4;
        --span-md: 3;
        --span-lg: 4;
    }
}
/* Tablet-Portrait (siehe Kommentar oben): span-md nimmt hier die volle
   Rasterbreite ein (4 von 4 Spalten) statt der 3-von-4-Breite auf
   Tablet-Landscape – auf den schmaleren Portrait-Breiten wirkt eine auf
   3 Spalten begrenzte Karte/Spalte unproportional schmal. */
@media only screen and (min-width: 501px) and (max-width: 720px) {
    :root {
        --span-md: 4;
    }
}
@media only screen and (max-width: 500px) {
    :root {
        --grid-cols: 2;
        --span-md: 2;
        --span-lg: 2;
    }
}

/* Typo */

html {
    font-size: 16px;
    /* Reserviert die Breite der Scrollbar dauerhaft, damit das Öffnen des
       Mobile-Menüs (unterdrückt Scrollen) die Viewport-Breite nicht
       verändert – sonst verschieben sich Navbar/Overlay sichtbar. */
    scrollbar-gutter: stable;
    /* Weiches Scrollen zu Anker-Links (z. B. Programm-Seite), ganz ohne
       JavaScript. Deaktiviert für Nutzer:innen mit reduzierter
       Bewegungspräferenz, siehe @media (prefers-reduced-motion) unten. */
    scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
}
body {
    font-family: var(--font-body);
    font-size: var(--text-md);
    font-weight: 400;
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
    color: var(--color-primary);
    text-align: left;
    font-kerning: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
strong, b {
    font-weight: 700; /* löst automatisch den T-Star-Heavy-Schnitt auf, siehe @font-face */
}
em, i {
    font-style: italic; /* löst automatisch den T-Star-Medium-Italic-Schnitt auf, siehe @font-face */
}
/* Basiswert für JEDES <nav>-Element der Seite (Footer-/Rechtliches-/
   Social-Media-Navigation UND Hauptnavigation, siehe snippets/menu.php –
   sowie .site-header/.site-title, die diesen Wert direkt bzw. per
   Vererbung übernehmen): --font-heading/--text-sm, durchgehend auf jedem
   Breakpoint. */
.site-header,
nav {
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
}
/* Ausnahme von --text-sm oben: Seitentitel und Hauptnavigation der
   Navbar (nicht die übrigen <nav>-Elemente wie Footer-/Rechtliches-/
   Social-Media-Navigation, die bleiben bei --text-sm) bekommen eine
   größere Schrift, --navbar-text (--text-md, auf Mobile --text-sm) –
   line-height/letter-spacing MÜSSEN dabei explizit auf --leading-md/
   --tracking-md mitgeführt werden (bis 2026-09-2X blieben sie fälschlich
   bei den von "nav" oben geerbten sm-Werten hängen, siehe Git-Historie).
   Eine Klasse (.site-title/.desktop-nav) hat höhere Spezifität als der
   Typselektor "nav" oben, gewinnt daher unabhängig von der Reihenfolge
   im Code. Der Menu-Toggle-Button bekommt denselben Wert direkt in
   seiner eigenen Regel (siehe .menu-toggle weiter unten), nicht hier:
   er setzt dort selbst font: inherit (Shorthand, setzt auch font-size
   zurück), eine Regel hier würde bei gleicher Spezifität durch die
   spätere Stelle im Code wieder verworfen. */
.site-title,
.desktop-nav {
    font-size: var(--navbar-text);
    line-height: var(--navbar-leading);
    letter-spacing: var(--navbar-tracking);
}
/* Hauptnavigation in --font-body (seit 2026-10-15; 2026-10-14 kurz
   --font-subheading, davor die von "nav" geerbte --font-heading) –
   ebenso der Menü-Button und die Einträge im Burger-Menü (siehe
   .menu-toggle, .mobile-overlay ul a).
   Der Seitentitel bleibt in --font-heading. */
.desktop-nav {
    font-family: var(--font-body);
}
nav li {
    display: inline-block;
}
nav li:not(:last-child) {
    margin-right: var(--space-sm);
}
/* footer selbst trägt bewusst KEIN eigenes Spacing/Layout mehr (weder
   Flexbox noch Padding/Margin) – reiner Landmark-Wrapper um die Footer-
   Sektion (siehe snippets/footer-inhalt.php: erste Zeile drei Spalten
   Festival/Service/Social Media, zweite Zeile die Footerbar mit
   Rechtliches). */

/* Gemeinsame Klasse für alle Fußzeilen-Navigationen (Festival/Service/
   Social Media/Rechtliches, siehe snippets/nav-liste.php) – das Gap hier
   ist faktisch der VERTIKALE Abstand zwischen den Einträgen innerhalb
   der Festival-/Service-/Social-Media-Spalten (siehe .footer-spalte
   .footer-menu weiter unten, höhere Spezifität überschreibt dort die
   Richtung auf column): kleiner als der sonst übliche Abstandsrhythmus,
   da hier viele kurze Einträge kompakt untereinanderstehen sollen. Für
   die weiterhin horizontale Footerbar (Rechtliches) gilt stattdessen ein
   eigener, größerer Wert (siehe .footer-bar .footer-menu weiter unten).
   flex-wrap: wrap verhindert horizontales Überlaufen bei längeren Listen
   (z. B. Festival-Navigation mit mehreren Einträgen) auf schmaleren
   Desktop-/Tablet-Breiten – ab Mobile übernimmt ohnehin die eigene,
   komplett vertikale Anordnung. */
.footer-menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  /* Abweichend vom geerbten --font-heading (siehe generisches "nav"
     weiter oben): Footer-Links laufen bewusst in --font-body statt der
     Heading-Schrift. Klasse schlägt das geerbte "nav" dank höherer
     Spezifität. */
  font-family: var(--font-body);
}
.footer-menu li:not(:last-child) {
  margin-right: 0;
}
/* Farbe kommt zentral vom "a"-Reset oben, nur diese eine Abweichung:
   Footer-Links bewusst ohne Unterstreichung – wie die Navbar (siehe
   .desktop-nav a unten). */
.footer-menu a {
  text-decoration: none;
}

/* Navbar */

[x-cloak] { display: none !important; }

.site-header {
  /* sticky statt fixed: bleibt im normalen Textfluss, die Seite braucht also
     kein manuelles padding-top mehr, das die Höhe der Bar erraten müsste */
  position: sticky;
  top: 0;
  z-index: 1000;
  background: var(--color-background);
}
/* Bei geringer Bildschirmhöhe (Smartphone im Querformat, starker
   Browser-Zoom) läuft die Navbar nicht mit: sie würde dort dauerhaft
   einen spürbaren Teil des sichtbaren Bereichs verdecken (WCAG 1.4.10
   Reflow). Sprungziele behalten ihren Abstand oben (scroll-margin-top),
   das ist dort nur etwas mehr Luft. */
@media (max-height: 499px) {
  .site-header { position: static; }
}

.navbar-bar {
  display: flex;
  align-items: center;
  /* Seitentitel links, Hauptnavigation (bzw. auf Mobile der Menu-Toggle-
     Button) rechts – die beiden jeweils sichtbaren Elemente wandern über
     space-between an die Außenränder, kein manueller Abstand nötig.
     Reine Flexbox (kein .grid-Container mehr, siehe Kommentar bei
     padding-left unten) – gap bleibt als Mindestabstand bestehen, falls
     space-between bei sehr wenig Platz sonst zu wenig Raum ließe. */
  justify-content: space-between;
  gap: var(--space-lg);
  /* Links UND rechts --content-margin (siehe :root) – dieselbe Custom
     Property wie .grid selbst nutzt (siehe dort), ändert sich über deren
     eigene @media-Kaskade automatisch je Breakpoint, keine eigene
     Tablet-/Mobile-Regel hier mehr nötig. Seitentitel/Hauptnav bleiben
     rein Flexbox-positioniert (kein eigener .grid-Container). */
  padding-left: var(--content-margin);
  padding-right: var(--content-margin);
  padding-block: var(--space-xs);
  /* Deckelt die Inhaltsbreite wie .grid (siehe dort für die Begründung) –
     .site-header (der sticky-positionierte Vollbreite-Rahmen darum,
     siehe dort) behält dabei seinen eigenen, bis zum Viewport-Rand
     reichenden Hintergrund, nur der Inhalt hier wird zentriert. */
  max-width: var(--content-max-width);
  margin-inline: auto;
}

/* Seitentitel (Link zur Startseite): bewusst ohne Unterstreichung und
   ohne Hover-Effekt (die Marke ist kein Menüpunkt, siehe Git-Historie:
   2026-10-12 kurzzeitig mit Hover-Linie). Auf der Startseite nur
   aria-current="page" für Screenreader (siehe snippets/header.php). */
.site-title {
  text-decoration: none;
  color: inherit;
}
/* Festivalakronym statt vollem Seitentitel unter 340px (siehe snippets/
   header.php $seitentitel, Panel: Site → Festivalakronym; seit
   2026-10-16, davor zweizeiliger Umbruch): darüber nur der volle Titel
   (.site-title__lang, unter 370px in --text-sm, siehe --navbar-text),
   darunter nur das Akronym sichtbar – der volle Titel bleibt für
   Screenreader im Link (wie .visually-hidden), damit der Linkname auch
   den sichtbaren Text enthält (WCAG 2.5.3). Gemessen 2026-10-16: der
   volle Titel passt in --text-sm neben "Schließen" ab 314px einzeilig
   (Chrome), 340px lässt Puffer für andere Browser. */
.site-title__kurz { display: none; }
@media only screen and (max-width: 339px) {
  .site-title__kurz { display: inline; }
  .site-title__lang {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

.menu-toggle {
  background: none;
  border: 0;
  /* .menu-toggle ist Kind von .navbar-bar/.mobile-overlay-bar, nicht von
     nav – Größe/Family/Zeilenhöhe/Letter-Spacing eigens gesetzt statt der
     von .site-header geerbten --text-sm-Stufe: --text-md/--leading-md/
     --tracking-md in --font-body (seit 2026-10-15; 2026-10-14 kurz
     --font-subheading, davor --font-heading, bis 2026-09-21 ebenfalls
     --font-body, siehe
     Git-Historie; line-height/letter-spacing bis 2026-09-2X fälschlich
     nicht mitgeführt, blieben bei den geerbten sm-Werten hängen, siehe
     Git-Historie). font: inherit direkt darunter setzt alle vier sonst
     auf die geerbten Werte zurück, müssen daher NACH diesem Shorthand
     stehen, um zu gewinnen. */
  font: inherit;
  font-size: var(--navbar-text);
  font-family: var(--font-body);
  line-height: var(--navbar-leading);
  letter-spacing: var(--navbar-tracking);
  cursor: pointer;
  padding: 0;
}
/* Desktop-Navigation: horizontale Liste */
.desktop-nav { display: none; }
.desktop-nav ul {
  display: flex;
  /* .desktop-nav selbst sitzt bereits rechtsbündig (space-between auf
     .navbar-bar, siehe dort) – kein eigenes justify-content hier mehr
     nötig, die Box ist ohnehin genau so breit wie diese Liste. */
  gap: var(--space-lg);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Reine Gap-Steuerung statt zusätzlichem margin-right (siehe "nav li:not
   (:last-child)" oben) – sonst addieren sich Gap und margin-right. */
.desktop-nav li:not(:last-child) {
  margin-right: 0;
}
/* Hauptnavigation (Navbar UND Burger-Menü, siehe snippets/menu.php):
   Text in --color-primary statt der --color-secondary des zentralen "a".
   Unterstreichung wie bei Card-Titeln (siehe .card__link): im Default
   State unsichtbar, bei Hover per Farb-Transition eingeblendet (siehe
   transition beim zentralen "a"), 1px dünn, Abstand 0.3em – beim
   aktiven Eintrag fest sichtbar. Einzige Abweichung von den übrigen
   Unterstreichungen: durchgezogen statt gepunktet. */
.desktop-nav a,
.mobile-overlay ul a {
  color: var(--color-primary);
  text-decoration-style: solid;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
}
.desktop-nav a:hover,
.desktop-nav a.active,
.mobile-overlay ul a:hover,
.mobile-overlay ul a.active {
  text-decoration-color: currentColor;
}

/* Vollflächiges Mobile-Overlay */
.mobile-overlay {
  position: fixed;
  inset: 0;
  z-index: 1100; /* über der Navbar, deckt sie vollständig ab */
  background: var(--color-background);
  overflow-y: auto; /* eigener Scrollbereich, falls die Liste länger als der Viewport ist */
}

.mobile-overlay-bar {
  /* Padding bytegleich zu .navbar-bar (siehe dort für die ausführliche
     Begründung) – bewusst KEINE eigene min-height: die Höhe soll sich bei
     beiden Bars gleichermaßen natürlich aus demselben, identischen
     .site-title-Kind ergeben, statt sie mit einer erzwungenen Zahl
     nachzubilden. Links UND rechts --content-margin (siehe :root), wie
     bei .navbar-bar. */
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-left: var(--content-margin);
  padding-right: var(--content-margin);
  padding-block: var(--space-xs);
}

.mobile-overlay ul {
  list-style: none;
  margin: 0;
  padding-top: 0;
  padding-bottom: 0;
  padding-left: var(--content-margin);
  padding-right: var(--content-margin);
}

/* Überschreibt das von "nav li" (siehe oben) geerbte "display: inline-block"
   und "margin-right": die Landmark-<nav>-Wrapper um die Listen (siehe
   header.php, für Screenreader-Struktur nötig) machen die Einträge sonst
   fälschlich zu horizontalen Inline-Elementen statt einer gestapelten
   Liste. Ohne Trennlinien (bis 2026-10-12 Linien zwischen den
   Einträgen, siehe Git-Historie). */
.mobile-overlay li,
.mobile-overlay ul li {
  display: block;
  margin-right: 0;
}

.mobile-overlay ul a {
  display: block;
  width: 100%;
  /* Abstand zwischen den Einträgen als padding am Link statt margin:
     er bleibt dadurch Teil der klickbaren Fläche, die Einträge grenzen
     lückenlos aneinander (größere Touch-Zielfläche, WCAG 2.5.8). Der
     erste Eintrag ohne padding-top (siehe unten), er beginnt direkt
     unter der Overlay-Leiste. */
  padding-block: var(--space-sm);
  padding-inline: 0;
  /* --text-xl mit passender Zeilenhöhe/Laufweite in --font-body (seit
     2026-10-15; 2026-10-14 kurz --font-subheading wie die
     Hauptnavigation). */
  font-family: var(--font-body);
  font-size: var(--text-xl);
  line-height: var(--leading-xl);
  letter-spacing: var(--tracking-xl);
}
.mobile-overlay ul li:first-child a {
  padding-top: 0;
}
/* Abstand zwischen Overlay-Bar und dem ersten Menüpunkt – derselbe
   kalkulierte Abstand wie auf der Startseite (siehe --abstand-bis-inhalt
   in :root, seit 2026-10-16): der erste Menüpunkt steht auf Höhe der h1
   einer Seite mit Breadcrumb. */
.mobile-overlay__content {
  padding-top: var(--abstand-bis-inhalt);
}

/* Ohne JavaScript (scripting: none) kann das Burger-Menü (Alpine) nicht
   öffnen: unter 1080 px wäre die Hauptnavigation sonst nirgends
   erreichbar. Stattdessen die Desktop-Navigation zeigen (umbrechend, falls
   der Platz nicht reicht) und den wirkungslosen Menü-Button ausblenden
   (Progressive Enhancement, Pre-Launch-Audit 2026-09-24). */
@media (scripting: none) {
    .desktop-nav { display: block; }
    .desktop-nav ul { flex-wrap: wrap; gap: var(--space-xs) var(--space-md); }
    .navbar-bar { flex-wrap: wrap; }
    .menu-toggle { display: none; }
}

/* Desktop-Schwelle (siehe :root-Breakpoint-Kommentar oben) – dieselbe
   1080px-Schwelle wie das Inhalts-Layout, seit dem Breakpoint-Umbau eine
   einzige gemeinsame Schwelle statt einer eigenen, unabhängigen
   Navigations-Schwelle (vormals 920px). Muss mit dem Breakpoint in
   assets/js/nav.js (window.matchMedia) übereinstimmen. */
@media (min-width: 1080px) {
  .menu-toggle { display: none; }
  .desktop-nav { display: block; }
  .mobile-overlay { display: none !important; } /* Sicherheitsnetz bei hängendem State */
}

/* Opacity-Fade (siehe header.php x-transition.opacity.duration.150ms)
   für Nav-Liste UND beide Menu-Toggle-Buttons (Menü/Schließen, siehe
   dort für die Begründung, warum beide ein eigenes x-show mit eigener
   Transition bekommen) – Alpine setzt die Transition dafür selbst per
   Inline-Style, daher hier keine eigene Transition-Deklaration nötig,
   nur die reduced-motion-Abschaltung. */
@media (prefers-reduced-motion: reduce) {
  .mobile-overlay__content,
  .menu-toggle {
    transition: none !important;
  }
}

/* Galerie-Grid */

/* Galeriebilder nutzen dasselbe Card-Grid wie die Cards (siehe .card-grid/
   .card-grid > .span-sm) statt eines eigenen Rasters – gleiches
   Verhalten (4 pro Zeile Desktop/Screen, 2 Tablet, 1 Mobile, linksbündig
   bei unvoller letzter Zeile) und derselbe Abstand. snippets/gallery.php
   rendert dafür <ul class="card-grid"> mit je <li class="gallery__item
   span-sm">. */
.gallery__trigger {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
}
.gallery__image img {
    width: 100%;
    height: auto;
    display: block;
}
/* Einheitliches 3:2-Format im Raster wie bei den Cards – nur ab Tablet-Breite
   (Mobile bleibt bei den natürlichen Bildproportionen, siehe @media unten).
   Alle Bilder werden gefüllt (object-fit: cover), Hoch- wie Querformat.
   Dadurch deckt das Bild die ganze Fläche ab: Link und Hintergrund reichen
   genau so weit wie das Bild. Logos ausgenommen (eigene Regel
   .gallery__image--logo). */
@media (min-width: 501px) {
    .gallery__image:not(.gallery__image--logo) {
        display: block;
        aspect-ratio: 3 / 2;
        overflow: hidden;
    }
    .gallery__image:not(.gallery__image--logo) img {
        height: 100%;
        object-fit: cover;
    }
}
/* Galerie-Hover: derselbe sehr zurückhaltende Zoom wie bei den Cards
   (siehe .card-media--lg img weiter oben). Ausgelöst wird er über dem
   Link (.gallery__trigger), also genau dort, wo das Bild klickbar ist und
   die Lightbox öffnet. overflow: hidden am Bildcontainer begrenzt das
   skalierte Bild auf seinen Rahmen. Logos haben keinen Link und daher
   keinen Hover. */
.gallery__image:not(.gallery__image--logo) {
    overflow: hidden;
}
.gallery__image:not(.gallery__image--logo) img {
    transform: scale(1);
    transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
    .gallery__trigger:hover .gallery__image:not(.gallery__image--logo) img {
        transform: scale(1.04);
    }
}
@media (prefers-reduced-motion: reduce) {
    .gallery__image:not(.gallery__image--logo) img {
        transition: none;
    }
    .gallery__trigger:hover .gallery__image:not(.gallery__image--logo) img {
        transform: none;
    }
}
/* Bilder weich einblenden, sobald sie geladen sind (siehe assets/js/site.js,
   Abschnitt "Bilder weich einblenden"). Der unsichtbare Zustand wird erst
   per JavaScript gesetzt, ohne JavaScript bleiben alle Bilder sichtbar.
   Ausgeblendet wird nur das <img>, nicht das <picture>: so bleibt die
   Platzhalterfläche auf <picture> (siehe oben, --color-quinary) während
   des Ladens sichtbar, statt mit ausgeblendet zu werden (bis 2026-10-08
   saß die Transparenz auf <picture>, dadurch erschien vor dem Laden nur
   Weiß). Eingeblendet wird per Animation statt per transition, damit die
   bestehenden transform-Übergänge für den Zoom beim Hover (siehe
   .card-media img, .gallery__image img) nicht überschrieben werden.
   Nur Transparenz, keine Verschiebung: kein Layout Shift. */
@keyframes bild-einblenden {
    from { opacity: 0; }
    to   { opacity: 1; }
}
picture.bild-unsichtbar img {
    opacity: 0;
}
picture.bild-einblenden:not(.bild-unsichtbar) img {
    animation: bild-einblenden 400ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
    picture.bild-einblenden:not(.bild-unsichtbar) img {
        animation: none;
    }
}

/* Logo-Darstellung (siehe blocks/gallery.yml "Logo-Darstellung",
   snippets/gallery.php $logos) statt der sonst freien Bildhöhe oben:
   für Sponsoren-/Partner-Logos, die im Gegensatz zu Fotos meist stark
   uneinheitliche Formate/Formen mitbringen – ein fester 3:2-Rahmen mit
   Innenabstand macht das Raster ruhig, object-fit: contain (statt des
   sonst bei Cards üblichen cover) zeigt dabei jedes Logo unbeschnitten
   in voller eigener Proportion.
   aspect-ratio (Ratio + Rahmenfarbe) und padding/object-fit bewusst auf
   ZWEI verschiedenen Elementen (<picture> bzw. <img> darin) statt beidem
   zusammen auf <picture> (frühere Fassung): dieselbe Trennung wie beim
   bereits bewährten .card-media--lg/--sm (dort auf einem eigenen
   Wrapper statt <picture> selbst, hier reicht <picture> als Wrapper).
   Mit Padding UND aspect-ratio auf demselben Element (<picture>, das mit
   eingebettetem <img>/object-fit eine komplexere eigene Größenberechnung
   hat als ein einfacher <div>) verzerrte sich die Ratio sichtbar – <img>
   trägt daher das Padding, <picture> bleibt dafür unverändert sauber
   3:2. */
.gallery__image--logo {
    aspect-ratio: 3 / 2;
    border: 1px solid var(--color-quaternary);
}
.gallery__image--logo img {
    width: 100%;
    height: 100%;
    padding: var(--space-xl);
    object-fit: contain;
}

/* Lightbox */

.lightbox {
    position: fixed;
    inset: 0;
    /* Gehört zur selben Ebenen-Skala wie .site-header (1000) und
       .mobile-overlay (1100, siehe dort) – deckt beide zuverlässig ab,
       statt eines beliebigen, unverbunden hohen Werts. */
    z-index: 1200;
    /* Kein display: flex mehr nötig: Backdrop/Content/Buttons sind alle
       position: absolute/fixed (siehe jeweils dort) und damit ohnehin aus
       dem normalen Fluss genommen – eine Flex-Zentrierung hätte auf keins
       von ihnen noch einen Effekt. */
}
/* opacity statt eines rgba()-Werts, da .lightbox__backdrop ein leerer,
   rein dekorativer Layer ohne eigenen Inhalt ist – beides wäre optisch
   identisch, opacity kommt aber ohne Farb-zu-rgba-Umrechnung aus. */
.lightbox__backdrop {
    position: absolute;
    inset: 0;
    background-color: var(--color-background);
    opacity: 0.9;
}
.lightbox__content {
    position: absolute;
    /* Fester Rand auf allen vier Seiten statt vorher 90vw/90vh: die
       beiden ergaben je nach Bildschirm-Seitenverhältnis unterschiedlich
       große (und nie wirklich --space-md große) Ränder oben/unten vs.
       links/rechts. inset sorgt für exakt denselben, festen Abstand
       ringsum, unabhängig vom Viewport – zugleich bündig mit den Buttons
       (siehe .lightbox__close/__prev/__next unten, dieselbe Variable). */
    inset: var(--space-md);
    z-index: 1;
}
/* .lightbox__image füllt Breite UND Höhe zu 100 % aus (siehe dort),
   object-fit: contain skaliert das eigentliche Foto darin seitenverhält-
   nistreu – sowohl verkleinernd als auch vergrößernd, je nachdem, was
   die tatsächlich verfügbare Fläche hergibt. .lightbox__figure selbst
   übernimmt hier nur die reine Größenvorgabe (100 % × 100 %, height
   braucht dafür eine ECHTE, nicht nur automatische Höhe beim
   Elternelement, sonst wird sie vom Browser ignoriert).
   ZWEI frühere Fassungen erwiesen sich live jeweils als fehlerhaft,
   deshalb ausführlich dokumentiert:
   (1) .lightbox__image mit width/height: auto + max-width/max-height:
   100 % + align-items: stretch (Flexbox-Default) auf .lightbox__figure:
   bei einem Replaced Element mit width/height: auto berechnet "stretch"
   zuerst die Höhe (immer auf die volle Fläche gestreckt) und leitet die
   Breite über das Seitenverhältnis davon ab; überschreitet diese
   abgeleitete Breite die verfügbare Breite, kappt max-width sie zwar,
   die bereits auf volle Höhe gestreckte Höhe bleibt dabei aber
   UNVERÄNDERT – das Bild füllte dann die komplette Box aus,
   seitenverhältnis-verzerrt statt proportional (live nachgestellt: ein
   Querformat-Bild in einem schmal-hohen Viewport rendert auf seine
   ganze 380 × 860px-Box statt auf sein echtes ~380 × 253px-
   Seitenverhältnis).
   (2) Derselbe Aufbau mit align-items: center statt stretch: behebt (1),
   führt aber zu einem NEUEN Bug – ein Replaced Element mit width/height:
   auto wird von max-width/max-height NUR verkleinert, nie vergrößert,
   auch wenn deutlich mehr Platz zur Verfügung steht. Das Bild rendert
   dadurch in seiner intrinsischen Größe (abgeleitet aus dem jeweils vom
   Browser gewählten srcset-Kandidaten, siehe lightbox.php), sobald diese
   kleiner als die verfügbare Fläche ist – live bestätigt: bei höherer
   Pixeldichte wählt der Browser einen anderen (nicht zwingend mit
   proportional größeren Pixelmaßen kodierten) Kandidaten, das Bild
   erschien dadurch spürbar verkleinert statt die verfügbare Fläche
   auszunutzen.
   Die jetzige Fassung (100 % × 100 % + object-fit: contain) skaliert
   dagegen in JEDE Richtung – verkleinernd UND vergrößernd –, unabhängig
   vom gewählten srcset-Kandidaten. (Eine frühere Fassung dieses
   Kommentars diskutierte an dieser Stelle noch einen Kompromiss bei der
   Klickfläche für "Klick neben dem Bild schließt die Lightbox" – dieser
   Mechanismus wurde inzwischen komplett entfernt, siehe snippets/
   lightbox.php: die Lightbox lässt sich nur noch über den Schließen-
   Button bzw. Escape schließen, die Klickfläche des Bilds spielt daher
   keine Rolle mehr.) */
/* Bezugsrahmen für die beiden übereinanderliegenden Bilder (Sofort-
   Vorschau + eigentliches Bild, siehe snippets/lightbox.php) – beide
   Kinder werden per position: absolute + inset: 0 exakt auf dieselbe
   Fläche gelegt (siehe .lightbox__figure picture/.lightbox__image
   weiter unten), position: relative macht .lightbox__figure selbst
   dafür zum Bezugspunkt. */
.lightbox__figure {
    position: relative;
    width: 100%;
    height: 100%;
}
.lightbox__picture,
.lightbox__image {
    position: absolute;
    inset: 0;
}
.lightbox__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}
/* Sofort-Vorschau (siehe snippets/lightbox.php, previewUrl in lightbox.js):
   dasselbe, bereits im Raster geladene Bild, hier nur größer dargestellt –
   keine eigene Optik (Weichzeichner o. Ä.) nötig, es ist bereits eine
   vollständige, nur kleiner aufgelöste Fassung desselben Fotos. x-show
   (siehe lightbox.php) blendet sie unmittelbar aus, sobald das große Bild
   bereit ist – kein Crossfade, da hier zwei unterschiedlich scharfe
   Zustände DESSELBEN Bilds abgelöst werden, kein Wechsel des Bildinhalts. */
/* Für vor-/zurückliegende Bilder im Hintergrund vorgeladen (siehe
   snippets/lightbox.php, prevIndex/nextIndex in lightbox.js) – rein
   dekorativ, nie sichtbar. Dieselbe Clip-Technik wie z. B. bei den
   außerhalb schmaler Viewports versteckten Breadcrumb-Krümeln (siehe
   .breadcrumb li:not(:nth-last-child(2)) weiter oben): display: none
   würde in manchen Browsern das Laden der enthaltenen Bilder verhindern
   (genau das Gegenteil des Zwecks hier), diese Technik nicht. */
.lightbox__preload {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
/* Buttons jetzt relativ zum VOLLEN Viewport (.lightbox), nicht zum Bild.
   Nutzen die schlichten Icons "Close"/"Arrow left"/"Arrow right" (siehe
   snippets/lightbox.php, snippets/icon.php) OHNE eigene Kreiskontur –
   die runde Fläche kommt stattdessen von diesem Button selbst, in
   derselben Farbe/Transparenz wie .lightbox__backdrop (--color-background,
   90 % Opacity). Grund: Die Buttons sitzen fix am Viewport-Rand, nicht
   am Bild – bei einem Bild, das die volle Breite/Höhe des 90vw/90vh-
   Bereichs ausfüllt (kein Letterboxing in dieser Richtung), können sie
   den Bildrand überlappen. Ohne eigene Fläche wäre der Icon-Kontrast dort
   vom (beliebigen) Bildinhalt abhängig – mit der Fläche bleibt der
   Kontrast Icon-zu-Fläche immer hoch (~12–16:1 je nach Bildinhalt
   dahinter, unabhängig davon, was darunterliegt). Gilt gleichermaßen auf
   Mobile: dort ist das Überlappen sogar der Normalfall, da der
   Bildbereich dort noch näher an den Viewport-Rand heranreicht. */
/* width/height explizit UND gleich, statt die Kreisfläche wie zuvor über
   Icon-Glyphe + padding entstehen zu lassen: Icon-Font-Glyphen sind
   nicht zwingend exakt quadratisch (je nach Zeichen leicht
   unterschiedliche Breite/Höhe), wodurch border-radius: 50% dort keinen
   echten Kreis ergab, sondern ein leicht ovales Rechteck. display: flex
   zentriert die Glyphe unabhängig von ihren eigenen Maßen exakt in
   dieser festen quadratischen Fläche. */
.lightbox__close,
.lightbox__prev,
.lightbox__next {
    position: fixed;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    background-color: var(--color-background);
    opacity: 0.9;
    border: none;
    border-radius: 50%;
    color: var(--color-primary);
    font-size: 2rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    transition: opacity 150ms ease;
}
.lightbox__close:hover,
.lightbox__prev:hover,
.lightbox__next:hover {
    opacity: 1;
}
.lightbox__close {
    top: var(--space-md);
    right: var(--space-md);
}
.lightbox__prev {
    left: var(--space-md);
    top: 50%;
    transform: translateY(-50%);
}
.lightbox__next {
    right: var(--space-md);
    top: 50%;
    transform: translateY(-50%);
}

@media (max-width: 767px) {
    .lightbox__close { top: var(--space-sm); right: var(--space-sm); }
    .lightbox__prev  { left: var(--space-xs); }
    .lightbox__next  { right: var(--space-xs); }
    .lightbox__close,
    .lightbox__prev,
    .lightbox__next {
        width: 2.5rem;
        height: 2.5rem;
        font-size: 1.5rem;
    }
}

/* Fade-Transition */
.lightbox-fade {
    transition: opacity 250ms ease;
}
.lightbox-fade--hidden { opacity: 0; }
.lightbox-fade--visible { opacity: 1; }

/* Raster: 8 Spalten (Desktop/Screen), 4 (Tablet), 2 (Mobile) – die
   Spaltenzahl kommt aus --grid-cols (siehe :root, "Seitenraster"), kein
   eigenes @media hier nötig. Navbar/Breadcrumb (siehe header.php) und
   Footer (siehe footer.php/footer-inhalt.php) sind bewusst KEINE Kinder
   von .grid – sie nutzen ihr eigenes padding-inline: var(
   --content-margin), dieselbe Custom Property wie hier (siehe :root),
   dadurch bleibt der linke/rechte Außenabstand seitenweit synchron, ohne
   .grid selbst einzubinden. Der Festival-Hero (siehe snippets/
   festivalausgabe.php) ist dagegen ein ganz normales .grid wie
   Sektionen 2–5 derselben Seite. Column-Gap UND padding-inline sind
   beide --content-margin (ein Wert für Abstand zwischen den Spalten und
   Seitenrand), jeweils automatisch je Breakpoint über :root. Row-Gap ist
   die Ausnahme: bleibt bewusst durchgehend --space-lg auf jedem
   Breakpoint (siehe direkt unten). */
.grid {
    display: grid;
    /* minmax(0, 1fr) statt nacktem 1fr: eine Grid-Spalte hat sonst implizit
       min-width: auto, das heißt sie schrumpft nie unter den Min-Content
       ihres breitesten Kinds – z. B. ein Bild in voller Originalbreite
       (Keyvisual im Festival-Hero, Bild-Block). Ohne die 0 im minmax()
       bleibt die ganze Grid-Zeile ab einer bestimmten Bildschirmbreite bei
       genau dieser Breite hängen (nicht mehr responsiv), statt weiter zu
       schrumpfen – betrifft dann JEDES Element in derselben Spalte
       gleichermaßen, nicht nur das breite Bild selbst. */
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    column-gap: var(--content-margin);
    /* Row-Gap bewusst konstant --space-lg statt der sonst üblichen
       Breakpoint-Stufung von Column-Gap/--content-margin – auf
       ausdrücklichen Wunsch unverändert gelassen. */
    row-gap: var(--space-lg);
    padding-inline: var(--content-margin);
    /* Deckelt die Inhaltsbreite auf sehr großen Bildschirmen (siehe
       Page-Speed-/Responsive-Review 2026-09-08), statt sie unbegrenzt mit
       dem Viewport mitwachsen zu lassen: ohne Deckel würden Card-Grids
       auf z. B. einem 5K-Monitor sehr breit (die Card-Breite ist ein
       Anteil an .grid), während die Schriftgröße (siehe --text-lg/--text-
       xl, nur wenige diskrete Stufen statt stufenlosem Mitwachsen, siehe
       :root) vergleichsweise klein bliebe – das Ergebnis wären
       überdimensionierte, spärlich gefüllte Cards mit vergleichsweise
       kleinem Text darin. margin-inline: auto zentriert
       .grid, sobald der Viewport breiter als max-width ist; darunter
       bleibt das Verhalten unverändert (max-width greift dann nie). Der
       Festival-Hero (siehe snippets/festivalausgabe.php) ist ebenfalls
       ein .grid und wird dadurch gleichermaßen gedeckelt. Navbar/
       Breadcrumb/Footer sind zwar bewusst KEINE Kinder von .grid (siehe
       Kommentar oben), bekommen denselben Deckel aber ebenso, jeweils
       direkt auf .navbar-bar/.breadcrumb-bar/.footer (siehe dort) – nur
       deren jeweiliger Hintergrund (z. B. .site-header) bleibt dabei
       volle Breite, wie bei .grid selbst auch (der Seitenhintergrund
       reicht ja ebenfalls immer bis zum Viewport-Rand). */
    max-width: var(--content-max-width);
    margin-inline: auto;
}
/* Einzelnes Element: sitzt als Block aus N Spalten (N je Breakpoint aus
   --span-sm/--span-md/--span-lg, siehe :root "Seitenraster"), randlos ab
   Spalte 1 (kein freier "1 Spalte"-Einzug – der gesamte Außenabstand
   kommt ausschließlich aus dem padding-inline von .grid, siehe oben) –
   explizit ab Spalte 1 statt nur "span N" ohne Start: erzwingt
   zuverlässig ein Element pro Zeile (jedes weitere Element mit demselben
   expliziten Startpunkt rutscht automatisch in die nächste freie Zeile),
   statt dass die Grid-Auto-Platzierung zwei schmalere Elemente (z. B.
   zwei span-sm) nebeneinander in dieselbe Zeile packen würde. Innerhalb
   eines .card-grid gelten diese Klassen dagegen anders (siehe dort:
   automatische Platzierung, mehrere Cards pro Zeile). Kein eigenes @media
   nötig: die Werte wechseln über :root.
   Zusätzlich setzt jede Klasse --cols auf ihre eigene Spaltenzahl: ein
   verschachteltes .card-grid (siehe dort) erbt darüber, wie viele Spalten
   ihm sein Umschlag (span-md/span-lg) tatsächlich zur Verfügung stellt. */
.span-sm { grid-column: 1 / span var(--span-sm); --cols: var(--span-sm); }
.span-md { grid-column: 1 / span var(--span-md); --cols: var(--span-md); }
.span-lg { grid-column: 1 / span var(--span-lg); --cols: var(--span-lg); }

/* Abstand zur sticky Navbar (siehe .site-header) für Anker-Links, die
   auf einen Block springen (aktuell: snippets/blocks/inhaltsverzeichnis.
   php, href="#<?= $block->id() ?>"). Gehört bewusst auf .block selbst
   (den Wrapper-<div> mit der id, siehe snippets/blocks-grid.php), NICHT
   auf .heading: scroll-margin-top wirkt nur auf das Element, dessen id
   tatsächlich im Anker-Link referenziert wird – eine H2 INNERHALB des
   Blocks zu stylen hätte hier keinerlei Effekt gehabt (bis 2026-09-2X
   stand die Regel fälschlich auf .heading, siehe Git-Historie).
   --navbar-height statt eines geschätzten Pixelwerts: aus denselben
   Werten berechnet, die die Navbar tatsächlich hoch machen (.navbar-bar
   padding-block zweimal + Zeilenhöhe von --text-md/--leading-md, siehe
   .site-header/.site-title, die dort keine eigene Schriftgröße setzen
   und daher die body-Basiswerte erben) – bleibt dadurch automatisch
   korrekt, auch wenn sich --text-md je Breakpoint ändert. Zusätzlich
   --space-lg "Luft" darüber, wie gewünscht. */
/* .sprungziel: dasselbe für Sprungziele außerhalb von Blöcken (siehe
   snippets/inhaltsverzeichnis.php, templates/mitwirkende.php Archiv). */
.block,
.sprungziel {
    scroll-margin-top: calc(var(--navbar-height) + var(--space-lg));
}

/* Tablet-Landscape (721–1079px): "Alle Tage" (.programm-tage-spalten in
   templates/programm.php) bleibt bei voller Breite (span-lg), damit die
   Trennlinie vor dem H2 wie bei Einzeltag über die volle Breite läuft.
   Nur der Cards-Bereich je Zeitgruppe (Uhrzeit-Überschrift + Cards,
   siehe .programm-zeitgruppe dort) wird stattdessen separat auf eine zu
   span-md äquivalente Breite begrenzt: da .programm-zeitgruppe kein
   echtes Grid-Element ist (verschachtelt innerhalb eines normalen
   Blockflusses statt eines direkten .grid-Kindes), lässt sich das nicht
   über grid-column lösen – max-width rechnet stattdessen dieselbe
   3-von-4-Spalten-Breite nach (span-md ist hier 3, siehe :root):
   allgemein ergibt eine Spalte in einem 4-Spalten-Grid mit Column-Gap G
   die Breite (100% - 3G) / 4, drei Spalten mit zwei Lücken dazwischen
   entsprechend das Dreifache davon plus 2G.
   NUR Tablet-Landscape, NICHT Tablet-Portrait (bis 2026-09-2X noch
   501–1079px gemeinsam, siehe Git-Historie): auf Tablet-Portrait ist
   span-md inzwischen 4 (volle Breite, siehe :root-Ausnahme dort) – "Alle
   Tage" soll dort dieselbe volle Breite wie "Einzeltag" zeigen, keine
   eigene Begrenzung mehr. */
@media only screen and (min-width: 721px) and (max-width: 1079px) {
    .programm-tage-spalten .programm-zeitgruppe {
        max-width: calc((100% - 3 * var(--content-margin)) / 4 * 3 + 2 * var(--content-margin));
    }
}

/* Sektion: Abstand zur vorigen/nächsten Sektion – einheitliches Default-
   Spacing (siehe :root-Kommentar zur Spacing-Skala), keine Sonderfälle
   mehr (frühere Verdopplung zum Footer sowie .section-gap/.stack-20/-80/
   .programmpunkt-gruppe--eng für engere Sondergruppierungen entfallen
   ersatzlos – ein einziger Rhythmus für das gesamte vertikale Spacing). */
.section {
    margin-block: var(--space-xl);
}

/* Components */

/* Heading (h1 direkt + Heading-Block h2–h4) */
.heading {
    font-family: var(--font-heading);
    text-align: left;
}
/* margin-bottom kommt zum ohnehin vorhandenen Grid-Row-Gap danach
   addiert dazu (gleiches Prinzip wie .heading--h2s margin-top oben) –
   jede H1 auf der gesamten Website bekommt dadurch denselben zusätzlichen
   Abstand zum nachfolgenden Inhalt, unabhängig vom jeweiligen Template.
   Einzige Ausnahme: der Hero der Festivalausgabe und der Vorschau, siehe
   .heading--ohne-abstand-unten direkt darunter. */
.heading--h1 { font-size: var(--text-xl); line-height: var(--leading-xl); letter-spacing: var(--tracking-xl); margin-bottom: var(--space-xl); }
/* Modifier für eine h1 ohne eigenen Abstand nach unten: nach ihr greift
   nur der normale Grid-Row-Gap. Gesetzt direkt am Element (snippet
   'heading', Parameter 'class'), nicht über einen Kontext-Selektor –
   eingesetzt im Hero der Festivalausgabe (Motto) und der Vorschau
   (Überschrift), siehe snippets/festivalausgabe.php und vorschau.php.
   Steht nach .heading--h1 und gewinnt bei gleicher Spezifität. */
.heading--ohne-abstand-unten { margin-bottom: 0; }
/* Bis 2026-09-14 zusätzlich mit Border-Top + Padding-Top (siehe
   Git-Historie für die frühere Linie-vor-Überschrift-Optik) – jetzt nur
   noch der Abstand VOR der Überschrift selbst (kommt zum ohnehin
   vorhandenen Row-Gap/Stack-Abstand vor diesem Element addiert dazu). */
.heading--h2 {
    font-size: var(--text-lg);
    line-height: var(--leading-lg);
    letter-spacing: var(--tracking-lg);
    margin-top: var(--space-xl);
}
/* margin-top: calc(--space-xl - --space-lg) statt eines vollen --space-xl
   wie bei .heading--h2 oben: h3 steht nie als direktes .grid-/.stack-Kind
   ganz ohne umgebenden Abstand (anders als h2), sondern immer bereits mit
   mindestens --space-lg Basisabstand aus dem jeweiligen Kontext (Row-Gap
   des umgebenden .grid, siehe dort, oder reduziertes Stack-Spacing, siehe
   z. B. .programm-zeitgruppe > .stack weiter unten) – die Differenz
   ergänzt diesen Basisabstand gezielt auf einheitlich --space-xl vor
   jeder H3, ohne ihn zu verdoppeln (Margin Collapsing zwischen dem
   umgebenden Block und dem eigenen margin-top der H3, sofern kein
   Grid-Item dazwischenliegt, siehe MDN "Mastering margin collapsing"). */
.heading--h3 { font-family: var(--font-heading); font-size: var(--text-md); line-height: var(--leading-md); letter-spacing: var(--tracking-md); color: var(--color-primary); margin-top: calc(var(--space-xl) - var(--space-lg)); }
.heading--h4 { font-family: var(--font-heading); font-size: var(--text-sm); line-height: var(--leading-sm); letter-spacing: var(--tracking-sm); }
/* Ausnahme vom zusätzlichen Abstand von .heading--h2/h3 oben (siehe
   dort) – beide Komponenten behalten ihre eigene, kompaktere Optik.
   Position NACH .heading--h2/h3 nötig: gleiche Spezifität (je eine
   Klasse), gewinnt daher nur durch die spätere Stelle im Code
   unabhängig davon, welche Überschriftenebene das Akkordeon/der Button
   im Einzelfall nutzt (siehe blocks/akkordeon.php $level). */
.buttons__label,
.accordion__label {
    margin-top: 0;
}
/* Markiert den Beginn einer neuen Sektion: doppelter Abstand nach oben
   (space-xl + space-xl statt nur der einfachen Stufe von .heading--h2
   oben) – ersetzt die früheren, eigenständigen Trennlinien-Divs
   (.linien-block, siehe Git-Historie), daher rein über Whitespace statt
   eines zusätzlichen Elements (bis 2026-09-2X hieß die Klasse noch
   .heading--nach-linie, obwohl es längst keine Linie mehr gibt, siehe
   Git-Historie). Betrifft snippets/festivalausgabe.php/vorschau.php/
   mediathek-verknuepfungen.php sowie templates/person.php/
   programmpunkt.php. Position NACH .heading--h2 nötig (gleiche Spezifität, siehe Ausnahme oben). */
.heading--sektion {
    margin-top: calc(var(--space-xl) + var(--space-xl));
}
/* Datumsüberschrift der Programm-Übersicht (siehe snippets/
   programm-tag.php): Wochentag und Datum als zwei eigene Zeilen
   innerhalb derselben H2 statt "Wochentag, Datum" in einer Zeile.
   display: block auf beiden Spans lässt sie ohne eigenen Abstand
   dazwischen (kein margin) untereinander stehen – der übliche
   .heading--h2-Abstand zu nachfolgendem Inhalt (siehe .stack) bleibt
   davon unberührt, er sitzt am ganzen Heading-Element, nicht zwischen
   diesen beiden Zeilen. .programm-tag-datum bekommt bewusst keine eigene
   Regel – erbt die normale --text-lg-Optik von .heading--h2 unverändert. */
.programm-tag-wochentag {
    display: block;
    font-family: var(--font-heading);
    font-size: var(--text-lg);
    line-height: var(--leading-lg);
    letter-spacing: var(--tracking-lg);
}
.programm-tag-datum {
    display: block;
}
/* H1 der 404-Seite (siehe templates/error.php) – einzige Stelle, die
   --color-error verwendet. */
.error-heading {
    color: var(--color-error);
}

/* Breadcrumb-Streifen (siehe snippets/breadcrumb.php, wird von header.php
   für jede Seite eingebunden): direkt unter der Navbar, bewusst kein Kind
   von .grid – eigener padding-inline: var(--content-margin) wie Navbar/
   Footer (siehe .grid-Kommentar in style.css), ändert sich automatisch
   über dessen eigene @media-Kaskade (siehe :root), keine eigene Tablet-/
   Mobile-Regel hier mehr nötig. */
.breadcrumb-bar {
    padding-left: var(--content-margin);
    padding-right: var(--content-margin);
    /* Abstand zur Navbar darüber – eigener, größerer Wert als der Abstand
       zum Hauptinhalt darunter (siehe padding-bottom), daher getrennte
       Werte statt eines gemeinsamen padding-block. Gleich wie
       .section--no-breadcrumb auf der Startseite, siehe
       --abstand-unter-navbar in :root. */
    padding-top: var(--abstand-unter-navbar);
    /* Abstand zum Hauptinhalt darunter (siehe .breadcrumb-bar + main >
       :first-child weiter unten, das mains eigenes margin-top auf 0
       setzt – der sichtbare Abstand zum Hauptinhalt kommt dadurch
       ausschließlich aus diesem padding-bottom). Reserviert außerdem
       Platz für die Underline der Links (siehe zentrales "a"):
       text-underline-offset: 0.3em rückt die Linie so weit unter die
       Grundlinie, dass sie sonst außerhalb der eigenen Box endet.
       --space-xs auf allen Breakpoints (seit 2026-10-15, davor
       --space-lg). */
    padding-bottom: var(--space-xs);
    /* Deckelt die Inhaltsbreite wie .grid/.navbar-bar (siehe dort für die
       Begründung). */
    max-width: var(--content-max-width);
    margin-inline: auto;
}
/* Ausnahme vom Default-Spacing (siehe :root-Kommentar zur Spacing-Skala):
   Der Abstand zwischen Breadcrumb und Hauptinhalt kommt komplett aus
   .breadcrumb-bars eigenem padding-bottom (siehe dort, --space-xs) statt
   aus dem sonst üblichen margin-top der ersten Sektion. Zielt auf das
   erste Kind von <main> (.section, siehe snippets/header.php) – höhere
   Spezifität als die generische .section-Regel, gewinnt daher unabhängig
   von deren Reihenfolge im Code. Betrifft nur diesen einen Übergang,
   nicht die Abstände zwischen weiteren Sektionen innerhalb von main. */
.breadcrumb-bar + main > :first-child {
    margin-top: 0;
}
.breadcrumb ol {
    display: flex;
    flex-wrap: wrap;
    list-style: none;
    padding: 0;
    margin: 0;
    /* Überschreibt die von "nav" (siehe dort) geerbte --font-heading –
       Breadcrumbs sollen bewusst in --font-body erscheinen. */
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
}
/* Setzt das von "nav li:not(:last-child)" (siehe dort) geerbte
   margin-right zurück, sonst wirken zwei Abstände gleichzeitig (der vom
   li selbst UND der vom Trennzeichen unten) – die gesamte Abstandslogik
   zwischen den Einträgen liegt bewusst allein beim Trennzeichen. */
.breadcrumb li:not(:last-child) {
    margin-right: 0;
}
/* Trennzeichen aus demselben Icon-Set wie überall sonst (siehe
   snippets/icon.php "chevron-right", eingebunden in snippets/
   breadcrumb.php) statt eines reinen Schriftzeichens oder Icon-Font-
   Glyphs (siehe Git-Historie). Echtes Element statt ::after: liegt zwar
   im Markup INNERHALB desselben <li> wie der vorangehende Link, trägt
   aber eine eigene Klasse statt der generischen .link-icon-Optik (die
   wäre hier zu eng am Text dran, --space-sm statt --space-xxs). Rein
   dekorativ zwischen zwei Krümeln, gehört zu keinem der beiden Links.
   Ausgeglichener Abstand auf beiden Seiten (--space-sm) – der zuvor
   sichtbare, überzählige Abstand DAVOR kam nicht von hier, sondern von
   einem Text-Space-Knoten im Markup (siehe snippets/breadcrumb.php,
   dort behoben). */
.breadcrumb__separator {
    margin: 0 var(--space-sm);
    color: var(--color-tertiary);
}
/* Farbe/Grundunterstreichung der VERLINKTEN Krümel kommen bereits
   vollständig vom zentralen "a" (siehe Reset oben), die Unterstreichung
   (secondary, bei Hover ohne Linie) von der gemeinsamen Regel dort. */
/* Die aktuelle Seite selbst (letztes Element, siehe snippets/
   breadcrumb.php) ist bewusst KEIN Link, braucht die Optik der übrigen
   Krümel daher explizit nachgebildet – --font-body erbt sie zwar bereits
   von .breadcrumb ol oben, --color-secondary aber nicht (kommt bei den
   Links nur über den "a"-Reset), Unterstreichung bewusst weggelassen
   (kein echter Link). */
.breadcrumb__current {
    color: var(--color-secondary);
}
/* Schmale Viewports: wie zuvor nur "← Eltern-Seite" sichtbar (identisch
   zum früheren Backlink) statt des vollen, ggf. mehrzeiligen Trails –
   die übrigen Krümel bleiben im DOM (nur visuell versteckt), Screenreader
   lesen weiterhin den vollständigen Pfad. :nth-last-child(2), NICHT
   :last-child: die aktuelle Seite (siehe snippets/breadcrumb.php) ist
   wieder ein eigenes, letztes <li> – der direkte Elternteil, auf den der
   Pfeil-Backlink zeigen soll, ist damit das VORLETZTE Element der Liste. */
/* Chevron-Icon des Backlinks (siehe snippets/breadcrumb.php – als echtes
   Icon-Element innerhalb des letzten Links gerendert, Teil von dessen
   Klickfläche statt eines davorstehenden ::before). Nur auf schmalen
   Viewports sichtbar (siehe @media unten), im DOM aber immer vorhanden.
   Kein margin-left: das Icon steht am Anfang der Klickfläche, ein
   zusätzlicher Abstand davor wäre nur unnötiger Leerraum vor dem Rand.
   margin-right (--space-xxs, seit 2026-10-14 wie bei allen übrigen
   Icon-Text-Paaren; vorher --space-xs) zum Label. Farbe/Größe erben wie beim
   Iconset selbst von der Umgebung (--font-body hier, da .breadcrumb ol
   das bereits so festlegt). */
.breadcrumb .breadcrumb__icon {
    display: none;
    margin-right: var(--space-xxs);
}
/* Unterhalb von Desktop/Screen (< 1080px: Tablet-Landscape, Tablet-
   Portrait, Mobile, siehe :root-Breakpoint-Kommentar weiter oben) wie
   zuvor nur "← Eltern-Seite" sichtbar (identisch zum früheren Backlink)
   statt des vollen, ggf. mehrzeiligen Trails – die übrigen Krümel
   bleiben im DOM (nur visuell versteckt), Screenreader lesen weiterhin
   den vollständigen Pfad. Dieselbe 1080px-Schwelle wie das übrige
   Layout/die Navigation (bis 2026-09-2X eigene, engere Schwelle bei
   767px, siehe Git-Historie). :nth-last-child(2), NICHT :last-child:
   die aktuelle Seite (siehe snippets/breadcrumb.php) ist wieder ein
   eigenes, letztes <li> – der direkte Elternteil, auf den der
   Pfeil-Backlink zeigen soll, ist damit das VORLETZTE Element der Liste. */
@media only screen and (max-width: 1079px) {
    .breadcrumb li:not(:nth-last-child(2)) {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    /* Trennzeichen (siehe .breadcrumb__separator oben) nur zwischen zwei
       tatsächlich sichtbaren Krümeln sinnvoll – auf schmalen Viewports
       bleibt aber nur der Eltern-Backlink sichtbar (siehe Regel oben),
       die aktuelle Seite danach ist versteckt. Ohne diese Regel hinge
       trotzdem ein überflüssiges Chevron hinter dem Backlink, obwohl kein
       zweiter sichtbarer Krümel mehr folgt. */
    .breadcrumb li:nth-last-child(2) .breadcrumb__separator {
        display: none;
    }
    .breadcrumb .breadcrumb__icon {
        display: inline-block;
    }
}

/* Stack: Abstand zwischen mehreren Elementen EINER Gruppe (siehe
   snippets/festivalausgabe.php), Default-Spacing wie überall sonst auch –
   vormals zwei Varianten (20px/80px) für engere bzw. weitere Gruppierung,
   jetzt einheitlich, da es nur noch das eine Default-Spacing gibt. */
.stack > * + * {
    margin-top: var(--space-xl);
}

/* Anzahl-Zeile über einem Card-Grid (siehe snippets/anzahl.php:
   Mitwirkende-/Mediathek-Übersicht, Sammlung) bzw. über der Bildergalerie
   (siehe templates/galerie.php, dort direkt mit derselben Klasse statt
   über den Snippet-Aufruf, da "Bild"/"Bilder" statt "Eintrag"/"Einträge"
   gebraucht wird). Abstand zum Grid darunter soll --space-md sein statt
   des sonst üblichen Grid-Row-Gaps --space-lg: negatives margin-top auf
   dem nachfolgenden Element stutzt den geerbten Row-Gap-Anteil auf die
   Differenz zurück. Generischer Geschwister-Selektor statt einer Regel
   pro Seite, da alle Einsatzstellen strukturell identisch sind
   (Anzahl-Absatz direkt vor dem Grid). */
.anzahl {
    color: var(--color-secondary);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
    margin-top: var(--space-xl);
}
.anzahl + .card-grid,
.anzahl + .gallery {
    margin-top: calc(var(--space-md) - var(--space-lg));
}
/* Folgt eine H2 oder die Anzahl-Zeile unmittelbar auf eine H1 (z. B.
   templates/team.php "Aktuell" direkt unter dem Seitentitel,
   templates/archiv.php "Anzahl" ohne Zwischeninhalt wie Filter/
   Kurzbeschreibung bei Mitwirkende/Sammlung, siehe oben), bzw. die
   Anzahl-Zeile unmittelbar auf eine H2 (templates/sammlung.php: H2 je
   Medientyp-Gruppe direkt gefolgt von deren Anzahl), bzw. eine H2/H3
   unmittelbar auf eine Pille (siehe .card-label--pills, z. B.
   .mediathek-art/.programmpunkt-art) – kein zusätzliches margin-top: die
   H1/H2/Pille bringt bereits ihr eigenes margin-bottom bzw. die H2/H3
   ihr eigenes margin-top mit (siehe .heading--h1/.heading--h2/.heading--h3),
   zusätzlich zum ohnehin vorhandenen Grid-Row-Gap; ein eigenes margin-top
   würde sich zu allen dreien addieren (Grid-Items collapsen ihre Margins
   nicht). Generischer :has()-Selektor statt einer Modifier-Klasse pro
   Einsatzstelle: H1/H2/H3 stehen jeweils in einem eigenen Wrapper-Div/
   Block (siehe snippets/blocks-grid.php für den Heading-Block-Fall), nur
   die Anzahl selbst ist ein direktes Geschwister ohne eigenen Wrapper
   (siehe snippets/anzahl.php) – ebenso .card-label--pills, das die
   Klasse immer direkt auf dem Grid-Kind selbst trägt, nie auf einem
   zusätzlichen Wrapper darum. */
:has(> .heading--h1) + .anzahl,
:has(> .heading--h2) + .anzahl,
:has(> .heading--h1) + :has(> .heading--h2) > .heading--h2,
.card-label--pills + :has(> .heading--h2) > .heading--h2,
.card-label--pills + :has(> .heading--h3) > .heading--h3 {
    margin-top: 0;
}

/* Zurück/Weiter samt Seitenzahlen (siehe snippets/pagination.php)
   unterhalb eines Card-Grids (Mitwirkende-/Mediathek-Übersicht, Sammlung).
   Linksbündig statt zentriert – dieselbe Ausrichtung wie das Card-Grid und
   der "Anzahl"-Text darüber. Alles eine einzige horizontale Reihe: Zurück,
   die Seitenzahlen (.buttons, siehe dort) und Weiter. flex-wrap nur als
   Sicherheitsnetz für sehr schmale Bildschirme bei vielen Seiten, im
   Alltag (max. wenige Seiten bei Seitengröße 60) bleibt ohnehin alles in
   einer Zeile. */
.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}
/* Abstand zwischen Pfeil-Icon (snippet('icon'), siehe dort) und Label
   "Zurück"/"Weiter" – bewusst als Margin statt nur einem Leerzeichen im
   Markup (dieselbe Lösung wie bei .breadcrumb__icon: ein reiner
   Text-Leerraum-Knoten ergibt keinen zuverlässig gleichmäßigen Abstand).
   Symmetrisch auf beiden Seiten (--space-xxs), damit dieselbe Regel für
   das Icon vor "Zurück" UND das Icon nach "Weiter" passt. */
.pagination .icon {
    margin: 0 var(--space-xxs);
}

/* Startseite (siehe snippets/festivalausgabe.php $noBreadcrumb): dort geht
   der Sektion (Hero-Inhalt + Rest, siehe snippets/festivalausgabe.php –
   eine einzige durchgehende .section) kein .breadcrumb-bar voraus (siehe
   snippets/breadcrumb.php – auf der Startseite gibt es keinen sinnvollen
   Trail). Seit 2026-10-16 steht die h1 trotzdem auf derselben Höhe wie
   die h1 einer Seite MIT Breadcrumb: padding-top bildet die komplette
   Höhe der Breadcrumb-Bar nach (siehe --abstand-bis-inhalt in :root).
   margin-top: 0 ersetzt das sonst übliche margin-top (siehe .section
   oben). */
.section--no-breadcrumb {
    margin-top: 0;
    padding-top: var(--abstand-bis-inhalt);
}
/* Motto (Festivalausgabe) bzw. Überschrift (Vorschau) im Hero, siehe
   snippets/festivalausgabe.php/vorschau.php: die Zeile ist volle Breite
   (span-lg), der Infoblock (.hero-info) steht in einer eigenen Zeile
   darunter. (2026-10-16 kurzzeitig per max-width auf 6 bzw. 2 Spalten
   begrenzt, wieder entfernt.) */
/* Gleichmäßig umbrechende Zeilen im Motto bzw. in der Überschrift (seit
   2026-10-16), wie beim Infoblock (siehe .hero-info) – statt eines
   einzelnen Worts allein in der letzten Zeile. Browser ohne
   Unterstützung brechen wie bisher um. */
.hero-titel h1 {
    text-wrap: balance;
}
/* Hero-Infoblock (siehe snippets/festivalausgabe.php Zeile 3:
   Festivalbeschreibung, Zeitraum, Ort + Stadt, und snippets/
   vorschau.php: Information): eigene Grid-Zeile unter dem Leitbild,
   volle Breite (span-lg, seit 2026-10-16; davor kurz eingerückt). Kein
   eigener Abstand, nur Row-Gap. --text-lg in --font-heading, Textfarbe
   primär. Alle Angaben untereinander (je eine Zeile bzw. ein Absatz,
   p-Margins sind 0). */
.hero-info {
    font-family: var(--font-heading);
    font-size: var(--text-lg);
    line-height: var(--leading-lg);
    letter-spacing: var(--tracking-lg);
    /* Verteilt umbrechende Zeilen gleichmäßig, statt ein einzelnes Wort
       allein in die letzte Zeile zu stellen. Browser ohne Unterstützung
       brechen wie bisher um. */
    text-wrap: balance;
}
/* Motto-Untertitel der Festivalausgabe: steht in derselben Grid-Zeile
   direkt unter der Motto-h1, ohne Abstand dazwischen (die h1 trägt dort
   .heading--ohne-abstand-unten, siehe oben). Zur nächsten Zeile (Info-
   block) greift nur der normale Grid-Row-Gap – daher ohne eigenes
   margin-bottom; ohne Untertitel gilt dasselbe direkt nach der h1. */
.festival-hero__motto-untertitel {
    font-family: var(--font-heading);
    font-size: var(--text-xl);
    line-height: var(--leading-xl);
    letter-spacing: var(--tracking-xl);
}
/* .festival-hero__motto (siehe snippets/festivalausgabe.php) trägt
   selbst keine eigene Typografie mehr: das Motto wird dort über
   snippet('heading', level 1) als echte h1 ausgegeben und bekommt seine
   Optik bereits von .heading--h1 (siehe dort) – dieser Wrapper dient nur
   noch als Custom-Property-Bezugsrahmen für --logo-bg (siehe die
   Durchstreichung im Motto direkt darunter). Bewusst OHNE eigenes
   margin-top: das Motto steht direkt nach dem Logo, ohne zusätzlichen
   Abstand über das normale Row-Gap hinaus. Dasselbe gilt für
   .vorschau-hero__titel (siehe snippets/vorschau.php), den analogen
   Wrapper der Vorschau-Seite (ohne eigene Regel, reiner Markup-Anker). */
/* Durchstreichung im Motto (per Writer-Feld gesetztes <s>) – nur bei
   der Festivalausgabe 2026: Strich in der Logo-Hintergrundfarbe und
   kräftiger. Alle anderen Ausgaben behalten die normale Durchstreichung
   in Textfarbe. --logo-bg kommt als Inline-Custom-Property vom
   umgebenden .festival-hero__motto, die Modifier-Klasse mit dem Slug
   der Ausgabe (siehe snippets/festivalausgabe.php). Nur der Strich wird
   eingefärbt, nicht der Text. */
.festival-hero__motto--festival-2026 s {
    text-decoration-color: var(--logo-bg);
    text-decoration-thickness: 0.12em;
}

/* Text / Großtext / Kleintext */
.text {
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
}
.text p + p {
    margin-top: var(--space-lg);
}
.text :where(ul, ol) {
    padding-inline-start: 1.25em;
    list-style: revert;
}
/* Ungeordnete Listen sollen nur EINE Bullet-Form verwenden (gefüllter
   Punkt, wie ohnehin schon auf der ersten Verschachtelungsebene) –
   list-style: revert oben lässt bei verschachtelten <ul> sonst den
   Browser-Default greifen (Kreis/Quadrat je tieferer Ebene). Bewusst nur
   für Text-Blöcke (siehe snippets/blocks/text.php, .block-type-text kommt
   von snippets/blocks-grid.php) und die Sitemap (siehe templates/
   sitemap.php, Klasse "sitemap" dort) angewendet, nicht sitzweit auf jedes
   .text-Vorkommen. Höhere Spezifität (zwei Klassen + Element) als die
   Basisregel oben, damit sie unabhängig von der Reihenfolge im
   Stylesheet sicher gewinnt; der Selektor selbst trifft dank normalem
   Nachfahren-Kombinator automatisch jede Verschachtelungstiefe. */
.block-type-text .text ul,
.text.sitemap ul {
    list-style: disc;
}
/* Sitemap: Top-Level-Seiten optisch von ihren Unterseiten abheben, damit
   die Struktur bei vielen Haupteinträgen leichter scannbar bleibt –
   Abstand vor jedem Top-Level-Eintrag (außer dem ersten) sowie stärkere
   Schriftgewichtung nur auf den Top-Level-Links selbst. Direktkind-
   Selektoren (>) statt eines generischen .text.sitemap li/a, damit nur
   das äußerste <ul> aus templates/sitemap.php getroffen wird, nicht die
   tiefer verschachtelten <li>/<a> aus snippets/sitemap-tree.php (deren
   Optik bleibt unverändert wie jede andere Fließtext-Liste). */
.text.sitemap > ul > li + li {
    margin-top: var(--space-md);
}
.text.sitemap > ul > li > a {
    font-family: var(--font-heading);
}
/* Zweite Ebene (direkte Unterseiten der Top-Level-Einträge, siehe
   snippets/sitemap-tree.php) in --font-subheading (seit 2026-10-19),
   tiefere Ebenen bleiben in der Fließtextschrift. */
.text.sitemap > ul > li > ul > li > a {
    font-family: var(--font-subheading);
}
/* Farbe/Grundunterstreichung kommen weiterhin vollständig vom zentralen
   "a" (siehe Reset oben), die Unterstreichung (secondary, bei Hover
   ohne Linie) von der gemeinsamen Regel dort – bewusst nur
   für Fließtext (.text) sowie die dort mitgelisteten Stellen, nicht für
   Navbar/Footer/Sitemap, die unverändert durchgezogen bleiben (siehe
   zentrales "a" oben). */
/* --font-body statt der von .text geerbten Standardschrift, dazu größer
   als der Fließtext-Default (--text-lg statt --text-md), Farbe
   --color-primary (Text-Default, siehe body), kein zusätzlicher Abstand
   nach unten – genutzt vom Block "Großtext" (siehe blocks/grosstext.php)
   UND von snippets/kurzbeschreibung.php, die beide dieselbe Optik zeigen
   sollen (bis 2026-09-21 als .text--xl in --text-xl mit margin-bottom,
   siehe Git-Historie). */
.text--lg { font-family: var(--font-body); font-size: var(--text-lg); line-height: var(--leading-lg); letter-spacing: var(--tracking-lg); color: var(--color-primary); }
.text--sm { font-size: var(--text-sm); line-height: var(--leading-sm); letter-spacing: var(--tracking-sm); }
/* Gedämpfte Textfarbe, z. B. Impressum auf der Festivalausgabe-Übersicht
   (siehe snippets/festivalausgabe.php) – funktioniert auf .text UND
   .heading, da beide ihre Farbe sonst nur vom body erben. */
.color-secondary { color: var(--color-secondary); }

/* Akkordeon-Block (siehe blocks/akkordeon.yml, snippets/blocks/
   akkordeon.php): natives <details>/<summary> statt eigenem Button +
   aria-expanded/JS – Öffnen/Schließen, Tastaturbedienbarkeit und
   Screenreader-Semantik ("erweitert"/"reduziert") kommen dadurch
   vollständig vom Browser, ganz ohne JavaScript (Progressive
   Enhancement, siehe CLAUDE.md). Label bleibt dabei ein echtes Heading
   (.heading, siehe dort), direkt in <summary> verschachtelt statt
   danebengestellt – seit HTML5.2 spezifiziert und von gängigen
   Screenreadern unterstützt: zählt gleichzeitig als Überschrift in der
   Dokumentgliederung UND als zugänglicher Name des nativen Disclosure-
   Buttons, ganz ohne eigenes ARIA.
   Ein Block bildet eine ganze Gruppe von Einträgen ab (siehe "Einträge"
   im Blueprint, .akkordeon-liste im Snippet) – jedes <details> trägt
   dafür nur seine eigene Border-Bottom (dasselbe Prinzip wie bei
   .beschreibungsliste__zeile, siehe dort: keine Sonderregel für den
   letzten Eintrag) und steht ohne eigenen Abstand direkt am nächsten,
   die Border-Bottom trennt die Einträge optisch allein. Der erste
   Eintrag bekommt zusätzlich eine eigene Border-Top (siehe
   .akkordeon-liste > .accordion:first-child weiter unten) – die Gruppe
   ist dadurch oben UND unten von einer Linie eingefasst, ohne dass der
   Container selbst (.akkordeon-liste, siehe unten) einen eigenen Rahmen
   bräuchte. Abstände (padding-block auf .accordion__summary, siehe
   weiter unten) gelten ausnahmslos für jeden Eintrag gleichermaßen,
   auch den ersten – keine Sonderbehandlung mehr nötig. */
.accordion {
    margin: 0;
    border-bottom: 1px solid var(--color-quaternary);
}
.akkordeon-liste > .accordion:first-child {
    border-top: 1px solid var(--color-quaternary);
}
.accordion__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    cursor: pointer;
    /* Entfernt das native Dreieck-Icon – list-style deckt Firefox und
       moderne Chromium-Browser ab, ::-webkit-details-marker (siehe
       unten) zusätzlich ältere WebKit-Engines. Ersetzt durch
       .accordion__icon. */
    list-style: none;
    /* WCAG 2.5.8 Target Size (Minimum): --text-sm × --leading-sm (kleinste
       mögliche Label-Ebene, siehe "Ebene" in akkordeon.yml) ergibt exakt
       24px – das eigene Padding hier schafft unabhängig von der
       gewählten Ebene durchgehend spürbar mehr Luft als das reine
       Minimum (dasselbe Prinzip wie bei Button/Navbar/Footer, siehe
       WCAG-2.5.8-Regel weiter unten). Gilt ausnahmslos für JEDEN
       Eintrag inklusive des ersten – die gesamte gepolsterte Fläche ist
       dadurch bei jedem Eintrag gleichermaßen klickbar. --space-sm seit
       2026-10-19 (davor --space-md). */
    padding-block: var(--space-sm);
}
.accordion__summary::-webkit-details-marker {
    display: none;
}
/* Dieselbe Optik wie bei Cards (siehe .card__link weiter unten): im
   Default State unsichtbare Unterstreichung (text-decoration-color:
   transparent statt text-decoration-line: none), die bei Hover per
   Farb-Transition sanft einblendet statt abrupt zu erscheinen (siehe
   die gemeinsame .accordion__summary:hover .accordion__label-Regel
   weiter oben). .accordion__label ist selbst kein Link (<summary>
   übernimmt die Klick-/Link-Rolle als Ganzes, siehe Docblock oben),
   braucht Unterstreichung/-Offset/Transition daher hier explizit (kein
   "a", erbt nichts vom zentralen "a"-Reset) – text-underline-offset:
   0.3em, sonst säße die bei Hover erscheinende Linie näher am Text
   (Browser-Default "auto") als bei den Cards. Hover-Auslöser ist bewusst
   die ganze .accordion__summary-Zeile (inkl. Icon), nicht nur der Text
   selbst – dasselbe Prinzip wie bei .programm-card:hover .card__link. */
.accordion__label {
    /* Überschreibt gezielt das von der jeweiligen Überschriftenebene
       (.heading--h2/h3/h4, siehe Blueprint "Ebene") geerbte
       --font-heading – spätere Position im Stylesheet, gleiche
       Spezifität (eine Klasse), gewinnt daher unabhängig von der
       gewählten Ebene. */
    font-family: var(--font-subheading);
    text-decoration-line: underline;
    text-decoration-style: dotted;
    text-decoration-color: transparent;
    text-underline-offset: 0.3em;
    /* Immer exakt 1px dünn statt des Browser-Defaults "auto", das sonst
       mit der Schriftgröße mitskaliert (dasselbe Prinzip wie bei
       .card__link/der Linkgruppe weiter oben – bei --text-lg-
       Labels [Ebene h2] wäre die Linie sonst deutlich dicker). */
    text-decoration-thickness: 1px;
    transition: text-decoration-color 150ms ease;
}
.accordion__icon {
    flex-shrink: 0;
    transition: transform 200ms ease;
}
/* Icon-Größe an die jeweilige Titel-Ebene angleichen (siehe "Ebene" im
   Blueprint, .heading--h2/h3/h4 in style.css): .accordion__label und
   .accordion__icon sind Geschwister (siehe blocks/akkordeon.php), das
   Icon erbt die font-size der Überschrift daher NICHT automatisch über
   Vererbung (die sitzt direkt auf dem Überschriften-Element selbst) –
   der Adjacent-Sibling-Kombinator (+) greift stattdessen gezielt auf
   genau das Element, das im Markup direkt auf die jeweilige Überschrift
   folgt. Ohne das wirkte das Icon bei --text-lg-Titeln (h2, Blueprint-
   Default) sichtbar zu klein im Verhältnis zum Titel. */
.heading--h2 + .accordion__icon { font-size: var(--text-lg); }
.heading--h3 + .accordion__icon { font-size: var(--text-md); }
.heading--h4 + .accordion__icon { font-size: var(--text-sm); }
/* Ein einzelnes Chevron-Down-Icon dreht sich im geöffneten Zustand um
   180° zu einem Chevron-Up, statt zwischen zwei Icons (Chevron-Down/-Up
   oder Plus/Minus) hin- und herzuwechseln – weniger Markup, dieselbe
   Aussage, zusätzlich animiert statt eines abrupten Wechsels. */
details[open] .accordion__icon {
    transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
    .accordion__icon { transition: none; }
}
.accordion__content {
    /* Kein margin-top: der Inhalt steht dadurch direkt unter der
       Summary-Zeile, sobald geöffnet, ohne zusätzlichen Abstand
       dazwischen. padding-bottom sorgt weiterhin für Abstand zur
       eigenen Border-Bottom (siehe .accordion), nur wirksam, wenn
       geöffnet (padding auf .accordion selbst hätte auch im
       geschlossenen Zustand einen Extra-Innenabstand zur Folge).
       Bewusst OHNE Transition beim Öffnen/Schließen: ein Versuch über
       @starting-style + transition-behavior: allow-discrete (reine CSS-
       Lösung, kein JavaScript) hat sich in echten Tests auf Safari,
       Chrome UND Firefox als nicht funktionierend erwiesen, obwohl
       einzelne isolierte Prüfungen zunächst danach aussahen – zurück auf
       das native, sofortige Auf-/Zuklappen von <details>.
       Seit 2026-10-19 zusätzlich ein kleiner Abstand oben (--space-xs)
       zur Summary-Zeile. */
    padding-top: var(--space-xs);
    padding-bottom: calc(var(--space-sm) * 2);
}

/* Inhaltsverzeichnis-Block (siehe blocks/inhaltsverzeichnis.yml,
   snippets/blocks/inhaltsverzeichnis.php): zusätzlicher Abstand nach
   oben, unabhängig von der sonstigen Grid-Row-Gap – markiert den Block
   optisch als eigenen, vorangestellten Bereich statt als gewöhnlichen
   Abschnitt im Lesefluss.
   .inhaltsverzeichnis-bereich: derselbe Abstand für ein Inhaltsverzeichnis
   außerhalb des Blocks (siehe templates/mitwirkende.php Archiv). */
.block-type-inhaltsverzeichnis,
.inhaltsverzeichnis-bereich {
    margin-top: var(--space-xl);
}
/* Ausnahme: folgt der Block unmittelbar auf die h1 (erster Block einer
   Blocks-Seite, siehe snippets/blocks-grid.php ".block--erstes"), wirkt
   der zusätzliche Abstand oben direkt unter der h1 überflüssig – der
   row-gap des .grid reicht dort bereits. Zwei Klassen = höhere
   Spezifität als die Regel oben, gewinnt unabhängig von der
   Reihenfolge im Stylesheet. */
.block-type-inhaltsverzeichnis.block--erstes {
    margin-top: 0;
}
/* Inhaltsverzeichnis: der Rahmen (wie .beschreibungsliste) umschließt
   Label UND Liste, Innenabstand --space-sm. Die Liste selbst ohne
   eigenen Rahmen/Innenabstand, keine Trennlinien zwischen den Zeilen,
   Abstand zwischen den Zeilen --space-sm. Das horizontale Padding der
   Zeilen entfällt, es übernimmt der Rahmen (sonst doppelt).
   Listenpunkte wie bei der Bulletlist (siehe .text :where(ul, ol) und
   .block-type-text .text ul): disc mit 1.25em Einzug, zusätzlich zum
   Innenabstand der Liste. display: list-item auf den Zeilen ist nötig,
   weil .beschreibungsliste__zeile sonst display: grid setzt – Grid-
   Elemente zeigen keinen Listenpunkt. */
.inhaltsverzeichnis {
    border: 1px solid var(--color-quaternary);
    padding: var(--space-sm);
}
.inhaltsverzeichnis .beschreibungsliste {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    border: 0;
    padding: 0;
    padding-inline-start: 1.25em;
    list-style: disc;
}
.inhaltsverzeichnis .beschreibungsliste__zeile {
    display: list-item;
    border-bottom: 0;
    padding-block: 0;
    padding-inline: 0;
}
/* Beschreibungsliste (siehe blocks/beschreibungsliste.yml): beliebig
   viele Zeilen, jede als eigenes Element mit Layout und Trennlinie –
   entweder ein <div> mit echtem <dt>/<dd>-Paar (Titel/Detail
   nebeneinander, siehe snippets/blocks/beschreibungsliste.php) oder ein
   <li> mit nur einem Wert (einspaltig, siehe .beschreibungsliste--
   einspaltig weiter unten). Desktop/Tablet: beide Spalten gleich breit
   (je 1fr, abzüglich des Gaps dazwischen). Mobile: eine Spalte, Titel
   direkt über dem Detail.
   Visuelle Tabellenstruktur (bis 2026-09-2X ohne umlaufenden Rahmen/
   horizontales Padding, siehe Git-Historie): ein umlaufender Rahmen auf
   dem Container (siehe .beschreibungsliste weiter unten) plus
   horizontales Padding je Zeile lassen die Liste wie eine gerahmte
   Tabelle wirken. Damit dabei KEINE Linie doppelt/dicker erscheint:
   Zeilen tragen weiterhin nur eine Border-Bottom als Trennlinie
   zwischen zwei Einträgen; die letzte Zeile lässt ihre eigene
   Border-Bottom weg (siehe :last-child weiter unten), weil dort bereits
   die Unterkante des Container-Rahmens die Linie zieht – ein
   Zusammentreffen zweier Linien an derselben Stelle ist so
   ausgeschlossen. Row-Gap bleibt effektiv 0 (kein margin/gap zwischen
   den Zeilen nötig): die Border-Bottom sitzt automatisch mittig
   zwischen zwei Einträgen, weil padding-top der einen und padding-
   bottom der vorigen Zeile symmetrisch gleich groß sind (dasselbe
   Prinzip wie bei .card--person/.card-grid--person). Gilt gleichermaßen
   für den Block "Downloads" (dieselbe Klasse, siehe .beschreibungsliste--
   downloads unten) und alle Verwendungen außerhalb des Blocksystems
   (templates/galerie.php, video.php, programmpunkt.php sowie die
   Downloads-Dateien der Festivalausgabe, siehe snippets/downloads.php/
   beschreibungsliste.php). */
.beschreibungsliste {
    border: 1px solid var(--color-quaternary);
}
.beschreibungsliste__zeile {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 0;
    border-bottom: 1px solid var(--color-quaternary);
    padding-block: var(--space-sm);
    /* Horizontales Padding: ohne das würde der Text der ersten/letzten
       Spalte direkt am linken/rechten Rahmen kleben. */
    padding-inline: var(--space-sm);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
}
/* Letzte Zeile ohne eigene Border-Bottom: die Unterkante des Container-
   Rahmens (siehe .beschreibungsliste oben) übernimmt an dieser Stelle
   bereits die Linie – eine zusätzliche Border-Bottom hier würde exakt
   darüber liegen und die Linie doppelt/dicker erscheinen lassen. */
.beschreibungsliste__zeile:last-child {
    border-bottom: 0;
}
.beschreibungsliste__zeile dt {
    font-family: var(--font-heading);
}
/* dd braucht keine eigene font-family: erbt --font-body von body (siehe
   dort) – kein unnötiger Override. */
@media only screen and (max-width: 500px) {
    .beschreibungsliste__zeile {
        grid-template-columns: 1fr;
        gap: 0;
    }
}
/* Einspaltige Varianten (statt Titel/Detail nebeneinander nur eine
   Spalte je Zeile), dieselbe Zeilen-Optik wie oben (Trennlinie,
   Abstände): Block "Downloads" (siehe blocks/downloads.yml, snippets/
   blocks/downloads.php) sowie .beschreibungsliste--einspaltig (siehe
   snippets/beschreibungsliste.php $einspaltig, verwendet von templates/
   programmpunkt.php für Datum/Uhrzeit/Ort/Besuch). Beide ohne
   Titel/Detail-Paar, nur ein einzelner Wert pro Zeile – daher als
   <ul>/<li> statt <dl>/<dt> (ein <dt> ohne zugehöriges <dd> wäre kein
   gültiges Name-Wert-Paar), die font-family kommt deshalb hier direkt
   statt wie oben nur auf .beschreibungsliste__zeile dt (kein dt
   vorhanden) – Downloads und Eckdaten zeigen dabei unterschiedliche
   Schriften (siehe die beiden Regeln direkt danach), das gemeinsame
   grid-template-columns bleibt trotzdem als eine Regel. Zwei Klassen auf
   dem Zeilen-Element selbst, damit die Regel unabhängig von ihrer
   Position im Stylesheet höhere Spezifität als .beschreibungsliste__zeile
   oben hat und die Zwei-Spalten-Regel (auch die von @media oben) sicher
   überschreibt. */
.beschreibungsliste--downloads .beschreibungsliste__zeile,
.beschreibungsliste--einspaltig .beschreibungsliste__zeile {
    grid-template-columns: 1fr;
}
/* Downloads: --font-subheading (bis 2026-09-2X --font-heading wie
   Eckdaten, siehe Git-Historie). */
.beschreibungsliste--downloads .beschreibungsliste__zeile {
    font-family: var(--font-subheading);
}
/* Eckdaten (Datum/Uhrzeit/Ort/Besuch): unverändert --font-heading. */
.beschreibungsliste--einspaltig .beschreibungsliste__zeile {
    font-family: var(--font-heading);
}
/* Einzelne Zeile in --font-body statt --font-heading (z. B. die Einträge
   des Inhaltsverzeichnisses, siehe snippets/inhaltsverzeichnis.php).
   Steht nach der Eckdaten-Regel oben (gleiche Spezifität). */
.beschreibungsliste--einspaltig .beschreibungsliste__zeile--body {
    font-family: var(--font-body);
}
/* Kalender-Zeile am Ende der Eckdaten (siehe templates/programmpunkt.php,
   "Zum Kalender hinzufügen"): in --font-heading wie die übrigen
   Eckdaten. Download-Icon am Ende statt davor (abweichend vom Standard in
   assets/js/site.js, das .ics wie einen Dateidownload behandelt): der Link
   wird per Flex-Umkehr gedreht, der Abstand wandert von rechts nach links. */
.beschreibungsliste__zeile--kalender a:has(> .link-icon--download) {
    display: inline-flex;
    flex-direction: row-reverse;
    align-items: center;
}
.beschreibungsliste__zeile--kalender .link-icon--download {
    margin-right: 0;
    margin-left: var(--space-xxs);
}
/* Downloads-Links: Farbe und Unterstreichung (secondary, bei Hover
   ohne Linie) kommen bereits vollständig von der gemeinsamen Regel
   weiter oben (.beschreibungsliste__zeile a). */

/* Bild-Block (siehe snippets/blocks/image.php) sowie alle übrigen freien
   Inhaltsbilder ohne festes Seitenverhältnis (Person-Portrait, Programm-
   punkt-/Video-Beitragsbild, Vorschau-Leitbild – jeweils über die Klasse
   "bildspalte" an ihrem Wrapper-Div markiert, siehe templates/
   person.php, programmpunkt.php, snippets/vorschau.php;
   .festival-hero__keyvisual/.video-cover haben ohnehin schon eigene,
   eindeutige Klassen): das Bild soll immer die volle Breite seiner
   Spalte einnehmen. Ohne dieses width:100% würde ein Bild, dessen
   Originalauflösung kleiner als der Container ist, nur in seiner
   tatsächlichen Pixelgröße erscheinen (img { max-width: 100% } allein
   deckelt nur nach oben, zieht aber nicht hoch) – es bliebe eine Lücke
   statt der vollen Spaltenbreite, in der zudem der pulsierende Platz-
   halter-Hintergrund von picture (siehe weiter oben) dauerhaft sichtbar
   bliebe statt nur während des Ladens. Card-/Galerie-Bilder betrifft das
   nicht: die haben durch object-fit: cover ohnehin schon immer volle
   Breite/Höhe ihres Rahmens (siehe .card-media--lg/--sm weiter unten). */
.block-type-image img,
.bildspalte img,
.festival-hero__keyvisual img,
.video-cover img {
    width: 100%;
}
/* Hero-Untertitel (seit 2026-10-19): Seitentitel der Festivalausgabe
   (z. B. "Festival 2026") bzw. Feld "Untertitel" der Vorschau, zwischen
   Motto/Überschrift und Leitbild – siehe snippets/festivalausgabe.php/
   vorschau.php. Schlichte Textzeile (kein Heading) in --font-body in der
   Größe der Navbar-Schrift. Abstand oben zum Motto, zusätzlich zum
   Row-Gap: --space-xl plus eine je Breakpoint eigene Feinstufe
   (Desktop/Tablet Landscape und Mobile --space-xxs, Tablet Portrait
   --space-md, Screen ohne Zusatz); zum Leitbild darunter nur das
   Row-Gap. Fehlt der Untertitel (Vorschau ohne gepflegtes Feld),
   bekommt das Leitbild direkt nach dem Motto denselben Abstand
   (.hero-titel + .leitbild), damit die Abstände gleich funktionieren. */
:root {
    --abstand-hero-titel: calc(var(--space-xl) + var(--space-xxs));
}
@media only screen and (min-width: 1536px) {
    :root {
        --abstand-hero-titel: var(--space-xl);
    }
}
@media only screen and (min-width: 501px) and (max-width: 720px) {
    :root {
        --abstand-hero-titel: calc(var(--space-xl) + var(--space-md));
    }
}
.hero-untertitel {
    margin: var(--abstand-hero-titel) 0 0;
    font-family: var(--font-body);
    font-size: var(--navbar-text);
    line-height: var(--navbar-leading);
    letter-spacing: var(--navbar-tracking);
    color: var(--color-primary);
}
.hero-titel + .leitbild {
    margin-top: var(--abstand-hero-titel);
}
/* Leitbild in Festivalausgabe und Vorschau (siehe snippets/leitbild.php):
   festes Format 3:2, auf Mobile 2:3 (seit 2026-10-16; zuvor
   kurzzeitig je Breakpoint 1:1 bis 3:1), unabhängig vom
   hochgeladenen Bild: ein Hochformat-Upload sprengt so nicht das Layout,
   sondern wird zugeschnitten (object-fit: cover). Der sichtbare
   Ausschnitt folgt dem im Panel gesetzten Fokuspunkt (object-position
   als Inline-Style, siehe snippets/picture.php), ohne Fokuspunkt der
   Bildmitte. Bewusst ohne Höhengrenze. height: auto hebt das aus den
   width/height-Attributen abgeleitete Original-Verhältnis auf.
   (2026-10-14 kurzzeitig eine Höhengrenze relativ zum Bildschirm,
   siehe Git-Historie.)
   Abstand oben: siehe .hero-untertitel (Hero-Untertitel zwischen
   Motto/Überschrift und Leitbild) bzw. die Ausnahme ohne Untertitel
   dort. */
.leitbild img {
    height: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}
@media only screen and (max-width: 500px) {
    .leitbild img {
        aspect-ratio: 2 / 3;
    }
}

/* Video */
.video {
    width: 100%;
    height: auto;
    display: block;
    /* Kirby kennt Breite/Höhe von Videodateien nicht (anders als bei
       Bildern), daher kein width/height-Attribut möglich. "auto" lässt
       das echte Seitenverhältnis gewinnen, sobald es feststeht (kein
       Verzerren bei Nicht-16:9-Videos) – 16/9 reserviert nur vorher
       schon den Platz und verhindert so einen Layout-Sprung. */
    aspect-ratio: auto 16 / 9;
}

/* Video-Cover (YouTube-Quelle, siehe templates/video.php): Beitragsbild
   mit einem zentrierten Button darüber, der auf YouTube verlinkt – es
   wird bewusst kein Video eingebettet (siehe Datenschutz-Hinweis bei
   "youtubeUrl" in blueprints/pages/video.yml), das Bild dient hier nur
   als anklickbare Vorschau. */
.video-cover {
    position: relative;
    overflow: hidden;
}
/* Dunkler, transparenter Layer zwischen Bild und Button, damit der
   Button-Text unabhängig vom Bildinhalt lesbar bleibt (WCAG-Kontrast) –
   rein dekorativ, daher aria-hidden. pointer-events: none wie bei
   .card-media__scrim, damit der Klick weiterhin den Button darüber
   trifft statt am Overlay hängen zu bleiben. */
.video-cover__overlay {
    position: absolute;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
/* Der eigentliche Link (siehe templates/video.php): deckt per inset: 0
   die komplette .video-cover-Fläche ab (nicht nur die sichtbare
   Button-Pille) – Hover reagiert dadurch auf dem gesamten Cover, nicht
   erst direkt über dem kleinen Button. flex zentriert die Button-Pille
   darin, ohne dass der Link selbst irgendeine sichtbare Fläche bräuchte
   (transparent, nur der verschachtelte .video-cover__button-Span trägt
   die Button-Optik). */
.video-cover__link {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Kein eigener Default-Override mehr nötig (siehe Git-Historie): bis
   2026-09-16 bewusst UMGEKEHRT gegenüber dem damaligen .button--primary-
   Default (heller Hintergrund mit --color-tertiary-Kontur statt des
   damals gefüllten Defaults) – seit dessen eigener Umkehr (siehe dort)
   liefert die einzelne .button--primary-Regel bereits exakt dieselben
   Werte, ein eigener .video-cover__button-Override wäre nur ein
   Duplikat. Hover/Active bleibt dagegen eine bewusste Abweichung (siehe
   unten, --color-primary statt des dortigen --color-secondary): auf dem
   dunkel abgetönten Video-Cover (siehe .video-cover__overlay oben) soll
   der Button bei Interaktion vollständig in --color-primary
   verschwinden, nicht im helleren --color-secondary. */
/* Hover/Active ausgelöst über den ganzen Link (.video-cover__link),
   nicht nur beim direkten Hover über der Button-Pille selbst – passend
   zum "ganzes Cover ist klickbar"-Verhalten oben. */
.video-cover__link:hover .video-cover__button,
.video-cover__link:active .video-cover__button {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-background);
}

/* Hintergrundbild reagiert wie bei den Cards: sehr zurückhaltender Zoom
   (siehe .card-media--lg img), ausgelöst über dem ganzen Cover. Gleiche
   Werte und gleiche Einschränkungen wie dort: nur mit Maus/Pen, bei
   prefers-reduced-motion weder Übergang noch Zoom. overflow: hidden liegt
   bereits auf .video-cover. */
.video-cover img {
    transform: scale(1);
    transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) and (pointer: fine) {
    .video-cover:hover img {
        transform: scale(1.04);
    }
}
@media (prefers-reduced-motion: reduce) {
    .video-cover img {
        transition: none;
    }
    .video-cover:hover img {
        transform: none;
    }
}

/* Buttons-Block: mehrere Einträge nebeneinander, umbrechend – Default für
   Primary-/Secondary-Buttons (siehe .buttons--vertical für Tertiary). */
.buttons {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--space-xs);
    row-gap: var(--space-xs);
}
/* Variante: Einträge untereinander – gilt projektweit ausschließlich für
   Tertiary-Buttons (z. B. Links auf Person-/Programmpunkt-Detailseite),
   Primary-/Secondary-Buttons bleiben immer horizontal (siehe .buttons
   oben, z. B. snippets/buttons.php, Pagination, Mediathek-/Programm-
   Filter). Alle drei Varianten fest an die Button-Art gekoppelt statt
   frei wählbar (siehe auch snippets/blocks/buttons.php "Version").
   align-items: flex-start ist bereits die native Linksbündigkeit, keine
   eigene Zentrierungs-Variante mehr nötig. */
.buttons--vertical {
    flex-direction: column;
    align-items: flex-start;
}
/* Zusätzlicher Abstand, wenn ein Buttons-Block der erste Block einer
   Blocks-Seite ist (siehe snippets/blocks-grid.php ".block--erstes",
   z. B. templates/default.php "Über uns"): direkt unter der h1 wirkt der
   normale row-gap des .grid sonst zu knapp. Strukturell an die Position
   gekoppelt statt an eine bestimmte Seite/ID, bleibt daher unabhängig
   davon korrekt, welche Seite den Block an erster Stelle verwendet. */
.block-type-buttons.block--erstes {
    margin-bottom: var(--space-xl);
}

/* Footer-Sektion (siehe snippets/footer-inhalt.php, ehemals "Kontakt" –
   umbenannt, siehe dort): zwei Zeilen – erste Zeile drei Spalten
   (Festival/Service/Social Media, siehe .footer-spalten/-spalte weiter
   unten), zweite Zeile die Footerbar mit Rechtliches (siehe .footer-bar
   weiter unten). Bewusst kein Kind von .grid – padding-
   inline: var(--content-margin) wie Navbar/Breadcrumb (siehe
   .grid-Kommentar oben), ändert sich automatisch über dessen eigene
   @media-Kaskade (siehe :root), keine eigene Tablet-/Mobile-Regel hier
   mehr nötig. margin-top: doppelter Abstand (--space-xl + --space-xl)
   statt des von .section (margin-block: var(--space-xl)) geerbten
   Werts – steht nach .section im Code, gewinnt daher bei gleicher
   Spezifität. Kollabiert mit dem margin-bottom der letzten Sektion
   davor (siehe unten), es gilt der größere der beiden Werte. margin-
   bottom dagegen gezielt auf --space-sm verkleinert (siehe unten): der Abstand zwischen Footer und
   unterem Bildschirmrand soll kleiner sein als der sonst übliche
   --space-xl-Abschnittsabstand. Kollabiert wie gewohnt durch das
   layoutlose <footer> (siehe dort) hindurch bis zum unteren body-Rand –
   gewollt, daher hier NICHT unterbunden.
   display: flow-root dagegen bewusst gesetzt: verhindert klassisches
   Parent-Child-Margin-Collapsing zwischen .footers eigenem margin-top
   (von .section geerbt) und dem margin-top seines ersten Kindes (siehe
   .footer > :first-child weiter unten) – ohne .footer eigene
   Border/Padding-Top hätte das Kind-margin sonst nach oben durch
   .footer hindurch mit dessen margin-top verschmolzen (beide Werte
   identisch --space-xl, beim Kollabieren gilt dabei nur der GRÖSSERE
   Wert, nicht deren Summe) – das zusätzliche margin-top am ersten Kind
   (siehe dort) wäre dadurch wirkungslos geblieben. flow-root erzeugt
   einen neuen Block Formatting Context nur für die Kind-Kollabierung,
   ganz ohne die Nebenwirkungen älterer BFC-Hacks (overflow: hidden
   o. Ä.) und ohne .footers eigenes margin-top/-bottom nach außen zu
   beeinflussen. */
.footer {
    display: flow-root;
    padding-left: var(--content-margin);
    padding-right: var(--content-margin);
    /* Färbt die ganze Sektion (Zeilen-Überschriften, Nav-Links)
       einheitlich – Überschriften/Text erben ihre Farbe sonst nur vom
       body (siehe .color-secondary weiter oben), Links stehen ohnehin
       schon per zentralem "a"-Reset auf --color-secondary. */
    color: var(--color-secondary);
    /* Deckelt die Inhaltsbreite wie .grid/.navbar-bar/.breadcrumb-bar
       (siehe dort für die Begründung). */
    max-width: var(--content-max-width);
    margin-inline: auto;
    margin-top: calc(var(--space-xl) + var(--space-xl));
    margin-bottom: var(--space-sm);
}
/* margin-top zusätzlich zum ohnehin über .section (siehe .footer oben)
   vorhandenen Abstand vor dem Footer – kein Border-Top/kein
   korrespondierendes Padding-Top (mehr) hier, nur der reine Abstand.
   :first-child statt einer festen Klasse (z. B. .footer-spalten): greift
   dadurch unabhängig davon, welche der beiden Zeilen tatsächlich zuerst
   gerendert wird (z. B. wenn ausnahmsweise nur Rechtliches gepflegt ist,
   siehe snippets/footer-inhalt.php). */
.footer > :first-child {
    margin-top: var(--space-lg);
}
/* Erste Zeile: Festival/Service/Social Media als drei echte Grid-Spalten
   (siehe snippets/footer-inhalt.php .footer-spalte) statt der früheren
   Titel-neben-Liste-Zeilen (siehe Git-Historie) – dasselbe Raster wie
   .grid (Spaltenzahl aus --grid-cols, Column-Gap --content-margin, siehe
   :root "Seitenraster"), aber OHNE dessen eigenes padding-inline/
   max-width/margin-inline: die deckt hier bereits .footer selbst ab
   (siehe oben), ein zusätzliches .grid würde beides doppelt anwenden.
   row-gap greift nur im gestapelten Zustand unterhalb von 1080px (siehe
   @media weiter unten). */
.footer-spalten {
    display: grid;
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    column-gap: var(--content-margin);
    row-gap: var(--space-md);
}
/* span-sm OHNE führende "1 /" (anders als die Utility-Klasse .span-sm,
   siehe dort: die pinnt die Startspalte fest auf 1, alle drei Spalten
   lägen sonst übereinander statt sich einzureihen) – automatische
   Platzierung durch den Browser reiht dadurch drei aufeinanderfolgende
   Spalten zu je --span-sm (2 von 8) Rasterspalten aneinander (die
   restlichen 2 Rasterspalten rechts bleiben frei), genau wie .span-sm
   optisch breit, nur mit funktionierender Auto-Platzierung. Eigener
   Flex-Container mit gap statt eines Abstands direkt auf dem Label
   (Titel oben, Linkliste darunter, siehe .footer-menu weiter unten für
   deren Spalten-Ausrichtung innerhalb dieser Spalte). */
.footer-spalte {
    grid-column: span var(--span-sm);
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}
/* Unterhalb von 1080px (Tablet/Mobile): eine Spalte statt drei
   nebeneinander – bei wenig Platz reicht die Breite sonst kaum für
   Label + Linkliste einer Spalte, bevor die Einträge umbrechen müssten.
   Drei Spalten gibt es nur auf Desktop/Screen. */
@media only screen and (max-width: 1079px) {
    .footer-spalten {
        grid-template-columns: 1fr;
    }
    .footer-spalte {
        grid-column: 1;
    }
}
/* margin-bottom: 0 überschreibt gezielt das von .buttons__label geerbte
   --space-xs (gilt sonst z. B. für Person-Links, siehe blocks/
   buttons.php) – der Abstand zur Linkliste soll hier ausschließlich aus
   dem gap von .footer-spalte (siehe oben) kommen, sonst würde sich
   beides addieren. */
.footer h2 {
    margin-bottom: 0;
}
/* Innerhalb einer Spalte läuft die Linkliste (siehe .footer-menu weiter
   oben, dort Default für die weiterhin horizontale Footerbar) untereinander
   statt nebeneinander – align-items: flex-start verhindert, dass die
   Links sich sonst auf die volle Spaltenbreite strecken (Flexbox-Default
   in Spaltenrichtung). */
.footer-spalte .footer-menu {
    flex-direction: column;
    align-items: flex-start;
}
/* Footerbar: nur Rechtliches (Datenschutz/Impressum), ohne eigenes
   Label (siehe snippets/footer-inhalt.php/rechtliches-menu.php – Name
   bleibt stattdessen als aria-label am <nav> erhalten, siehe
   nav-liste.php) – bewusst eigener, größerer Abstand zur ersten Zeile
   statt des sonst für Geschwisterzeilen üblichen Werts, auf
   ausdrücklichen Wunsch. */
.footer-bar {
    margin-top: calc(var(--space-lg) + var(--space-xl));
}
/* Horizontaler Abstand zwischen Datenschutz/Impressum (siehe
   .footer-bar oben, einzige weiterhin horizontale Fußzeilen-Navigation)
   – derselbe Wert wie zwischen den Einträgen der Hauptnavigation (siehe
   .desktop-nav ul), damit beide horizontalen Link-Reihen gleich wirken. */
.footer-bar .footer-menu {
    gap: var(--space-lg);
}

/* Label über einem Buttons-Block (siehe blocks/buttons.php, Person-Links)
   sowie über einer Footer-Spalte (siehe .footer-spalte oben). */
.buttons__label {
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
    text-align: left;
    margin-bottom: var(--space-xs);
}
/* Label des Inhaltsverzeichnisses (siehe snippets/inhaltsverzeichnis.
   php): --text-sm wie die übrigen .buttons__label, steht aber innerhalb
   des Rahmens; Abstand zur Liste darunter insgesamt --space-sm. Eigene
   Element-Klasse statt Kontext-Selektor; steht nach .buttons__label und
   gewinnt bei gleicher Spezifität. */
.inhaltsverzeichnis__label {
    margin-bottom: var(--space-sm);
}

/* Button: Struktur/Typo gemeinsam, Farben kommen von den drei Varianten
   .button--primary (Pill mit Kontur, für CTAs), .button--secondary
   (Pill in denselben Maßen wie Primary, nur zurückhaltendere Farben, für
   nachrangige CTAs) und .button--tertiary (schlichter unterstrichener
   Text-Link, ohne Pill, für nachrangige Buttons im Fließ-/Listenkontext,
   siehe jeweils dort) – .button allein ist bewusst unstyled (Farbe),
   damit immer eine Variante explizit gewählt wird. */
.button {
    display: inline-block;
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-md);
    /* Linie bleibt immer "da", nur unsichtbar (transparent), damit ihr
       Erscheinen beim Hover weich animiert werden kann (text-decoration-
       line selbst lässt sich nicht transitionieren, nur die Farbe). */
    text-decoration: underline;
    text-decoration-style: dotted;
    text-decoration-color: transparent;
    text-underline-offset: 0.15em;
    transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, text-decoration-color 150ms ease;
}
/* Primary: die auffälligste Button-Variante, für Navigations-/Utility-
   Buttons wie CTAs gleichermaßen (Subnav, Anker-Links, Festivallinks,
   Konzeptlinks, Downloads, "Buttons"-Block). Default: heller Hintergrund
   mit --color-tertiary-Kontur, Text --color-primary – bei der Mediathek-
   Typfilterung (siehe snippets/mediathek-nav.php) wirkt dadurch die
   aktuell AKTIVE Filterung (.active, siehe unten) durch den komplett
   gefüllten Hover-/Active-Zustand hervorgehoben, statt umgekehrt
   zurückhaltender als die übrigen, nicht gewählten Filter. Kontrast
   bleibt dabei weiterhin mehr als ausreichend: --color-primary auf
   --color-background sowie --color-background auf --color-primary
   liegen beide deutlich über 4,5:1 (WCAG AA), unabhängig davon, welche
   der beiden Kombinationen gerade Default bzw. Hover/Active ist. */
.button--primary {
    background-color: var(--color-background);
    border: 1px solid var(--color-tertiary);
    color: var(--color-primary);
    /* --font-subheading wie Secondary (seit 2026-10-19) – Primary und
       Secondary sind damit optisch gleich (bewusst so entschieden). */
    font-family: var(--font-subheading);
    /* Kompakter als das von .button geerbte --space-xs (8px): wirkt bei
       der Pillenform von Primary-Buttons stimmiger. Bleibt weit über der
       WCAG-2.5.8-Mindestklickfläche (siehe Kommentar weiter unten). */
    padding-block: 6px;
}
/* Etwas kompakter auf Mobile/Tablet-Portrait: passt zur dort ebenfalls
   kleineren Schrift-/Spacing-Stufe (siehe :root-Breakpoint-Kaskade). */
@media only screen and (max-width: 720px) {
    .button--primary {
        padding-block: 4px;
    }
}
.button--primary:hover,
.button--primary.active {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-background);
}

/* Secondary: dieselben Maße/Kontur/Farben wie Primary (Pill), aber in
   --font-subheading statt --font-heading (seit 2026-10-16; davor Text
   und Hover in --color-secondary) – für alle Button-Leisten außerhalb
   des Heros (siehe snippets/buttons.php $version). Default: helle
   --color-tertiary-Kontur, Text --color-primary – bei Hover/Active
   komplett in --color-primary gefüllt, Text --color-background. */
.button--secondary {
    background-color: var(--color-background);
    border: 1px solid var(--color-tertiary);
    color: var(--color-primary);
    font-family: var(--font-subheading);
    /* Wie .button--primary: siehe Kommentar dort. */
    padding-block: 6px;
}
@media only screen and (max-width: 720px) {
    .button--secondary {
        padding-block: 4px;
    }
}
.button--secondary:hover,
.button--secondary.active {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-background);
}

/* Tertiary: schlichter Text-Link statt Pill mit Kontur (siehe
   .button--primary/.button--secondary) – kein eigenes Padding/
   Hintergrund/Rahmen, echt so kompakt wie ein reiner Link. Farbe
   (geerbt, auch bei Hover) und --font-body stehen unten, hier die
   weiteren Abweichungen vom zentralen "a" (siehe Reset oben):
   Unterstreichung schon im Default State sichtbar (nicht erst bei
   Hover, wie z. B. bei den Breadcrumbs) und größerer Abstand zur Zeile.
   Für nachrangige Buttons, die sich optisch nicht wie ein primärer CTA
   aufdrängen sollen (z. B. die Anker-Links auf der Programm-Seite).
   Die Unterstreichung MUSS hier stehen statt in der gemeinsamen Regel
   weiter oben (.text a/.breadcrumb a/…): .button setzt oben bereits
   text-decoration: underline (Shorthand, setzt Farbe/Stärke zurück) – bei gleicher
   Spezifität wie eine dortige .button--tertiary-Regel gewinnt sonst
   .button allein durch seine spätere Position im Stylesheet. Hier,
   NACH .button, gewinnt die eigene Regel dagegen zuverlässig. */
.button--tertiary {
    padding: 0;
    /* Wie Links im Fließtext: Farbe des umgebenden Texts (siehe oben) und
       --font-body statt der --font-heading von .button. Unterstreichung
       in der Textfarbe (gepunktet, siehe .button), bei Hover
       verschwindet sie. */
    color: inherit;
    font-family: var(--font-body);
    text-decoration-color: currentColor;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.3em;
}
.button--tertiary:hover {
    color: inherit;
    text-decoration-color: transparent;
}
/* Wie bei den Navbar-Active-Items: hervorgehoben über die Farbe, nicht
   zusätzlich über eine Unterstreichung. */
.button--tertiary.active {
    color: var(--color-primary);
    text-decoration-color: transparent;
}
/* Quaternary (seit 2026-10-14): wie Tertiary, aber zurückhaltender in
   --color-secondary statt der Textfarbe – für Hilfslinks, die kaum
   auffallen sollen (z. B. "Zum Inhalt" zurück zum Inhaltsverzeichnis,
   siehe templates/programm.php, snippets/mitwirkende-abschnitt.php;
   auch im Buttons-Block wählbar). Kontrast --color-secondary auf Weiß
   4,95:1 (WCAG AA). Gleiche Unterstreichung/Hover wie Tertiary. */
.button--quaternary {
    padding: 0;
    color: var(--color-secondary);
    font-family: var(--font-body);
    text-decoration-color: currentColor;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.3em;
}
.button--quaternary:hover {
    color: var(--color-secondary);
    text-decoration-color: transparent;
}
.button--quaternary.active {
    color: var(--color-primary);
    text-decoration-color: transparent;
}
/* WCAG 2.5.8 Target Size (Minimum, neu in WCAG 2.2): mindestens 24×24px
   Klickfläche. Live gemessen: .menu-toggle/.site-title/.desktop-nav a
   landeten bei 22px Höhe, Footer-Menü/Breadcrumb bei 18px – --text-sm/
   --leading-sm allein reicht ohne zusätzliches Padding nicht.
   Eine gemeinsame Regel statt sechs einzelner Duplikate – aber bewusst
   NICHT global auf jedes <a> (z. B. per Reset oben): vertikales Padding
   auf einem Link MITTEN in einem Fließtext-Absatz (Writer-Feld,
   Bildunterschriften) würde optisch mit der Nachbarzeile kollidieren,
   Inline-Elemente schieben umgebende Zeilen dabei nicht auseinander.
   Genau solche Fließtext-Links brauchen die Vergrößerung ohnehin nicht
   (WCAG-2.5.8-Ausnahme "Inline": Link in einem Satz/Textblock). Alle
   hier gelisteten Elemente stehen dagegen isoliert (Button oder Eintrag
   in einer Flex-/Listen-Navigation, nie von Fließtext umgeben) – Padding
   ist dort überall gefahrlos: 22px -> 30px, 18px -> 26px, beides über
   der 24px-Grenze. Muss nach den einzelnen Komponentenregeln stehen
   (u. a. .menu-toggle/.button--tertiary setzen padding: 0), sonst
   würden diese das padding-block hier wieder überschreiben. */
.menu-toggle,
.button--tertiary,
.button--quaternary,
.site-title,
.footer-menu a,
.breadcrumb a,
.desktop-nav a {
    padding-block: var(--space-xxs);
}

/* Card-Grid: echtes CSS-Grid mit derselben Spaltenlogik wie .grid (siehe
   :root "Seitenraster"), aber mit eigener Spaltenzahl: --cols (siehe
   .span-sm/md/lg) sagt, wie viele Seitenraster-Spalten der Umschlag hat
   (span-lg → 8/4/2, span-md → 4/3/2 je Breakpoint), das Card-Grid teilt
   sich exakt diese Spalten mit demselben Column-Gap (--content-margin) –
   eine Card mit span-sm ist dadurch immer exakt so breit wie ein
   .span-sm-Element im Seitenraster. Das .card-grid selbst darf ein
   direktes .grid-Kind mit eigener span-Klasse sein (z. B.
   "card-grid span-lg") oder verschachtelt in einem span-Umschlag stehen
   (z. B. ".span-lg.stack > .card-grid", im Block-Raster ".block.span-md >
   .card-grid") – --cols wird vererbt. Ohne jeden span-Umschlag gilt die
   volle Seitenraster-Spaltenzahl (--grid-cols).
   Cards darin sind per Default span-sm (Person-Card span-lg, Festival-
   ausgabe-Card span-md), siehe cards/*.php. Vier Cards pro Zeile auf
   Desktop/Screen (span-sm = 2 von 8), zwei auf Tablet (2 von 4), eine auf
   Mobile (2 von 2). Row-Gap ist die Ausnahme: bleibt bewusst konstant
   --space-xl auf jedem Breakpoint (Team-Übersicht/Personen abweichend 0,
   siehe .card-grid--divider/--person). */
.card-grid {
    display: grid;
    grid-template-columns: repeat(var(--cols, var(--grid-cols)), minmax(0, 1fr));
    column-gap: var(--content-margin);
    row-gap: var(--space-xl);
    /* Grid-Items strecken sich sonst per Default auf die Höhe der
       größten Card in der Zeile – jede Card soll aber nur so hoch sein
       wie ihr eigener Inhalt (z. B. mit vs. ohne Bild). */
    align-items: start;
    list-style: none;
    margin: 0;
    padding: 0;
}
/* Cards im Card-Grid: automatische Platzierung (span N ohne festen
   Startpunkt, anders als die Utility-Klassen .span-* im Seitenraster,
   siehe dort) – mehrere Cards reihen sich dadurch nebeneinander, eine
   unvolle letzte Zeile bleibt linksbündig. span-lg-Cards füllen immer
   die ganze Zeile (1 / -1) statt ihrer eigenen Spaltenzahl: sie stehen in
   einem span-lg- ODER span-md-Umschlag (z. B. Person-Cards), sollen aber
   in beiden Fällen dessen volle Breite einnehmen und nie über die
   Umschlagbreite hinausragen. */
.card-grid > .span-sm { grid-column: span var(--span-sm); }
.card-grid > .span-md { grid-column: span var(--span-md); }
.card-grid > .span-lg { grid-column: 1 / -1; }
/* Ausnahme für die Rückblick-Cards der Festivalausgabe
   (.card-grid--rueckblick): span-md ist dort auf Tablet (und Mobile, dort
   ohnehin 2) nur 2 statt 3 Spalten breit, die beiden Cards stehen dadurch
   auf Tablet nebeneinander (2 + 2 von 4). --span-md wird hier lokal
   überschrieben und an die Kinder vererbt (siehe .span-md), alle übrigen
   span-md-Elemente behalten :root "Seitenraster". */
@media only screen and (max-width: 1079px) {
    .card-grid--rueckblick {
        --span-md: 2;
    }
}
/* Verknüpfung-Blöcke (siehe blocks/verknuepfung-festivalausgabe/
   -mediathek/-mitwirkende.php): eigener, kleinerer Row-Gap --space-lg statt des generischen
   --space-xl oben. Bei den Verknüpfung-Blöcken (dort span-md, siehe
   .card-grid > .span-md) ohnehin einspaltig, Row-Gap damit der einzig
   sichtbare Abstand zwischen den Cards. Card-Grids mit Programmpunkt-
   Cards (Person, Medienseiten, Festivalausgabe) verwenden diese Klasse
   bewusst NICHT: dort gilt der generische --space-xl. */
.card-grid--verknuepfung {
    row-gap: var(--space-lg);
}
/* Teaser-Card-Grids (Klasse manuell gesetzt, siehe festivalausgabe.php
   "Programm", vorschau.php "Bisherige Festivalausgaben"/"Mediathek",
   templates/mediathek.php "Aktuelles" je Medientyp): zeigen exemplarisch
   4 Cards auf Desktop/Screen (span-sm = 2 von 8, siehe .card-grid), aus
   Platzgründen aber nur 2 auf Tablet/Mobile (< 1080px: dort 2 pro Zeile
   auf Tablet bzw. 1 pro Zeile auf Mobile). Serverseitig werden weiterhin
   alle 4 gerendert (kein zusätzlicher Request, kein Layout-Sprung beim
   Resize) – die 3. und 4. Card werden unterhalb von 1080px per
   display:none entfernt (auch aus dem Tab-/Screenreader-Fokus), was hier
   unproblematisch ist, da es sich um austauschbare Beispiel-Cards
   handelt, nicht um eigenständigen, sonst nirgends erreichbaren Inhalt. */
@media only screen and (max-width: 1079px) {
    .card-grid--teaser > :nth-child(n + 3) {
        display: none;
    }
}
/* Überschreibt gezielt .card-grid { margin: 0 }, damit überhaupt ein
   Abstand zwischen Uhrzeit-Heading und erster Card ankommt (siehe
   templates/programm.php) – zwei Klassen statt einer, also höhere
   Spezifität als .card-grid, gewinnt daher unabhängig von der Reihenfolge
   im Code. */
.stack > .card-grid {
    margin-top: var(--space-lg);
}
.card {
    display: flex;
    flex-direction: column;
    position: relative; /* Bezugsrahmen fürs "Stretched Link" (siehe .card__link::after) */
    border-radius: 0;
    overflow: hidden;
    /* Keine eigene Hintergrundfarbe: die Card liegt immer direkt auf
       ihrer Umgebung (main) und übernimmt deren Fläche 1:1. */
    transition: border-color 150ms ease;
}
.card__body {
    display: flex;
    flex-direction: column;
    /* Kein Innenabstand: Typ "Bild-Card" (Mediathek/Festivalausgabe/
       Rückblick, siehe .card-media--lg) hat bewusst keine Kontur und
       keinen Innenabstand. */
    padding: 0;
    /* Winziger Puffer unten, unabhängig vom sonst padding-losen Bild-Card-
       Typ: .card selbst hat overflow: hidden (siehe dort) – sitzt ein
       Titel/Link OHNE weitere Zeile danach exakt bündig an dieser Kante
       (z. B. Rückblick-Card, wo
       der Titel das letzte Element ist), schneidet das dessen
       Unterstreichung ab, sobald sie sichtbar wird (Hover/Fokus): der
       Rand des Textes selbst reicht noch knapp innerhalb der Box, die
       Linie ein, zwei Pixel darunter aber nicht mehr. Der Puffer betrifft
       nur diesen einen Fall – bei Cards mit mehr Inhalt danach (z. B.
       Mediathek-Meta-Zeile) bleibt er optisch unauffällig. */
    padding-bottom: var(--space-xxs);
}
/* Row-Gap 0 statt des sonstigen .card-grid-Defaults (--space-md, siehe
   .card-grid oben) – für die Team-Übersicht (siehe templates/team.php),
   deren Cards dort ohne zusätzlichen Row-Gap direkt aneinanderstoßen
   sollen. */
.card-grid--divider {
    row-gap: 0;
}
/* "Stretched Link": macht die ganze Card klickbar, ohne dass die Card
   selbst ein <a> wird – nur der Titel bleibt der echte, semantische Link
   im DOM (ein Link pro Card, kurzer Screenreader-Text, kein Risiko
   verschachtelter Links). Das Bild bleibt rein dekorativ verlinkt
   (tabindex="-1"), der unsichtbare ::after liegt als position:absolute
   automatisch über allen normal fließenden Inhalten der Card. */
.card__link::after {
    content: "";
    position: absolute;
    inset: 0;
}
/* Kartentitel sollen bewusst NICHT sekundär/gedämpft wirken wie ein
   gewöhnlicher Link, sondern wie eine Überschrift: eigene Farbe (statt
   der zentralen Link-Farbe, siehe "a" oben) UND im Default State
   unsichtbare Unterstreichung – text-decoration-color: transparent statt
   text-decoration-line: none, damit die Linie bei Hover per Farb-
   Transition sanft einblendet statt abrupt zu erscheinen (Zeile/Stil
   bleiben dabei durchgehend underline/dotted, geerbt vom zentralen "a"
   oben, nur die Farbe wechselt – siehe die gemeinsame .card__link:hover/
   .programm-card:hover .card__link-Regel weiter oben sowie deren
   transition, ebenfalls vom zentralen "a" geerbt). */
.card__link {
    color: var(--color-primary);
    text-decoration-color: transparent;
    /* Immer exakt 1px dünn statt des Browser-Defaults "auto", das sonst
       mit der Schriftgröße mitskaliert (dasselbe Prinzip wie bei der
       Linkgruppe weiter oben). */
    text-decoration-thickness: 1px;
}
/* Programm-Übersicht, Ansicht "Alle Tage" (siehe templates/programm.php):
   Desktop/Screen zeigt die Festivaltage in drei Spalten nebeneinander –
   serverseitig fest verteilt (siehe sbwProgrammtageAufSpalten() in
   site/plugins/programm-spalten), NICHT über natives CSS-
   Mehrspaltenlayout (column-count, siehe Git-Historie für die frühere
   Fassung): dessen automatisches Spalten-Balancing berechnet bei jeder
   Höhenänderung (z. B. durch den von font-display: swap ausgelösten
   Font-Wechsel, siehe Font-Preloads in header.php) neu, welcher Tag in
   welche Spalte fällt – das erzeugte sichtbar verzögerte, scheinbar
   willkürliche Sprünge in Spalte 2/3 (bestätigter Bug, 2026-09-14). Eine
   einmalige, serverseitige Verteilung auf drei echte, unabhängige
   Flex-Spalten (siehe .programm-spalte weiter unten) hat dieses Problem
   strukturell nicht: eine Höhenänderung in einer Spalte wirkt sich nie
   auf die anderen aus. Die drei Spalten sitzen als echtes 8-Spalten-
   Grid (dieselbe Spaltenzahl/derselbe Column-Gap wie das Seitenraster,
   siehe .programm-spalte): Spalte 1 = Rasterspalten 1–2, Rasterspalte 3
   bleibt als Abstand frei, Spalte 2 = 4–5, Rasterspalte 6 frei, Spalte 3
   = 7–8 – jede Spalte dadurch exakt so breit wie ein span-sm-Element. */
@media only screen and (min-width: 1080px) {
    .programm-tage-spalten {
        display: grid;
        grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
        column-gap: var(--content-margin);
        align-items: start;
    }
}
/* Eine der drei Spalten (siehe .programm-tage-spalten oben, Verteilung
   der Tage siehe sbwProgrammtageAufSpalten()): unterhalb von Desktop/
   Screen löst sich ihre eigene Box komplett auf (display: contents) –
   die enthaltenen Tage (.programm-tag, direktes Kind im Markup, siehe
   templates/programm.php) werden dadurch zu direkten Kindern von
   .programm-tage-spalten und stapeln sich normal untereinander, in
   genau der ursprünglichen, chronologischen Reihenfolge (die Verteilung
   ordnet Tage fortlaufend in die Spalten ein, nie quer/verschachtelt –
   Spalte 1 + Spalte 2 + Spalte 3 hintereinander ergibt daher wieder
   exakt die ursprüngliche Reihenfolge). Ab Desktop/Screen eine echte
   Grid-Spalte (jeweils 2 von 8 Rasterspalten, siehe oben): min-width: 0
   verhindert, dass ein breiter Inhalt (z. B. ein langer, nicht
   umbrechender Titel) die Spalte über ihren Anteil hinaus verbreitert
   (Grid-Items sind ohne das per Default nie schmaler als ihr eigener
   Inhalt). */
.programm-spalte {
    display: contents;
}
@media only screen and (min-width: 1080px) {
    .programm-spalte {
        display: block;
        min-width: 0;
    }
    .programm-spalte:nth-child(1) { grid-column: 1 / span 2; }
    .programm-spalte:nth-child(2) { grid-column: 4 / span 2; }
    .programm-spalte:nth-child(3) { grid-column: 7 / span 2; }
}
/* Abstand zwischen zwei Tagen, die untereinanderstehen: nur unterhalb
   von Desktop/Screen nötig (dort stapeln sich die Tage einspaltig
   normal untereinander, siehe .programm-tage-spalten oben) – ab
   Desktop/Screen stehen mehrere Tage zwar auch mal in derselben Spalte
   übereinander, bekommen dort aber bewusst KEINEN zusätzlichen Abstand
   (kein Override nötig, Default-Margin ist 0). margin-top statt Row-
   Gap/Stack-Spacing: die Tage sind hier keine direkten Geschwister mehr
   im umgebenden .grid (sie liegen jeweils innerhalb einer Spalte),
   normales Block-Margin funktioniert dort unabhängig vom Spalten-Kontext
   gleichermaßen. */
@media only screen and (max-width: 1079px) {
    .programm-tag + .programm-tag {
        margin-top: var(--space-lg);
    }
}
/* Abstand vor jeder Uhrzeit-Gruppe außer der jeweils ersten eines Tages
   (siehe .programm-zeitgruppe in snippets/programm-tag.php): kein
   eigener Wert mehr nötig, das generische .stack-Standardspacing
   --space-xl (siehe .stack > * + * weiter oben) passt bereits. Die
   jeweils erste Zeitgruppe eines Tages bekommt hier bewusst KEINEN
   eigenen margin-top (kein :first-child-Fall zu behandeln, seit die
   Regel entfällt) – bei "Alle Tage" (Wochentag-Überschrift und
   Zeitgruppen im selben .stack) übernimmt ebenfalls .stack > * + *
   bereits --space-xl, bei Einzeltag-Filterung (siehe programm-tag.php
   $eigeneUeberschrift) kommt der Abstand stattdessen vom Row-Gap des
   umgebenden .grid (--space-lg) plus dem margin-top der Uhrzeit-H3
   selbst (siehe .heading--h3 weiter unten) – zusammen ebenfalls
   --space-xl. */
/* Abstand zwischen Uhrzeit-Überschrift und den zugehörigen Cards
   (".programm-zeitgruppe > .stack" trifft eindeutig nur den Cards-
   Wrapper, siehe programm-tag.php – das einzige .stack-Kind von
   .programm-zeitgruppe, immer nach der h3 im Markup): --space-xl, wie
   das generische .stack-Standardspacing (siehe .stack > * + * weiter
   oben), hier aber ausdrücklich gesetzt, da die Card selbst keine
   Trennlinie/keinen Innenabstand nach oben mehr hat (siehe
   .programm-card). */
.programm-zeitgruppe > .stack {
    margin-top: var(--space-xl);
}
/* Abstand zwischen den Cards derselben Uhrzeit-Gruppe: --space-xl. */
.programm-zeitgruppe .programm-card + .programm-card {
    margin-top: var(--space-xl);
}
/* Block "Programmpunkt-Verknüpfung" (siehe blocks/verknuepfung-
   programmpunkt.php): dieselbe vertikale Stapelung wie bei der
   Einzeltag-Filterung der Programm-Übersicht (span-md, kein echtes
   Card-Grid, siehe dort) statt eines eigenen .programm-zeitgruppe/
   .stack – --space-lg Abstand zwischen den Cards. */
.programmpunkt-verknuepfung .programm-card + .programm-card {
    margin-top: var(--space-lg);
}
/* Maximale Bildbreite der Programmpunkt-Cards bei "Alle Tage" (siehe
   .card-media--sm weiter unten): die Cards selbst stehen auf der
   Programm-Übersicht nicht mehr in einem echten Card-Grid (siehe
   .programm-zeitgruppe in snippets/programm-tag.php – alle vertikal in
   einem .stack), können dadurch je nach Spaltenbreite deutlich breiter
   werden als eine .card-grid > .span-sm-Spalte. Das Bild soll trotzdem
   nie größer wirken, als stünde die Card in einem echten Mehrspalten-
   Grid – die Formel entspricht exakt einem span-sm-Element auf Desktop/
   Screen (2 von 8 Spalten, also (Breite - 3 Lücken) / 4 bei Lücke =
   --content-margin, siehe :root "Seitenraster"), nur mit
   100vw statt 100% als Bezugsgröße: max-width bezieht sich sonst auf
   die BREITE DES DIREKTEN UMSCHLAGS (der bei schmaleren Zeitgruppen-
   Spalten selbst schon schmaler als die volle Seitenbreite ist), hier
   ist aber immer die volle Seitenbreite abzüglich der Seitenränder
   (--content-margin) gemeint, unabhängig vom tatsächlichen Umschlag.
   Ab Tablet-Landscape abwärts (< 1080px: Tablet-Landscape, Tablet-
   Portrait, Mobile) bewusst KEINE Begrenzung: .programm-tage-spalten
   löst dort kein echtes Mehrspalten-Layout mehr aus (column-count
   bleibt bei 1, siehe dort), das Bild soll die volle Breite seines
   Umschlags ausfüllen (auf Tablet-Landscape die eigene, zu span-md
   äquivalente Breite von .programm-zeitgruppe, siehe .programm-tage-
   spalten .programm-zeitgruppe weiter oben, sonst volle Seitenbreite),
   statt künstlich auf eine 2-Spalten-Optik verkleinert zu werden, die es
   auf dieser Stufe ohnehin nicht mehr gibt. */
@media only screen and (min-width: 1080px) {
    .programm-zeitgruppe .card-media--sm {
        max-width: calc((100vw - 5 * var(--content-margin)) / 4);
    }
}
/* Rück-Link "Zu den Festivaltagen" am Ende jedes Tages (siehe templates/
   programm.php): derselbe Abstand zum Inhalt davor wie im Mitwirkenden-
   Archiv (dort der Row-Gap des Grids, --space-lg). */
.programm-zurueck {
    margin-top: var(--space-lg);
}
/* Inhaltsverzeichnis der Programm-Seite (siehe templates/programm.php):
   Sprunglinks zu den Festivaltagen nur unterhalb von Desktop/Screen –
   ab 1080px stehen die Tage ohnehin in Spalten nebeneinander. */
@media only screen and (min-width: 1080px) {
    .programm-inhaltsverzeichnis,
    .programm-zurueck {
        display: none;
    }
    /* Ohne Inhaltsverzeichnis folgen die Tage direkt auf die h1: zwischen
       beiden nur deren margin-bottom und der normale Row-Gap, ohne das
       zusätzliche margin-top der ersten Tages-h2 jeder Spalte. */
    .programm-spalte > .programm-tag:first-child .heading--h2 {
        margin-top: 0;
    }
}
/* Programmpunkt-Card (ein Template "programmpunkt" für Veranstaltung und
   Ausstellung, unterschieden nur über das Feld "Zeitraum", siehe
   programmpunkt.yml) – EINE einheitliche Version für alle Einsatzstellen
   (Programm-Übersicht, Festivalausgabe, Person, Mediathek-Verknüpfungen,
   Block "Programmpunkt-Verknüpfung", siehe cards/programmpunkt.php und
   dessen Aufrufer): Textinfo (Programmart-Pillen, Beitragsbild,
   Mitwirkende/Titel/Weitere Mitwirkende/Zusatz, dann Datum/Uhrzeit/
   Nummerierung), alles in einem Block untereinander (siehe .programm-
   card__content weiter unten) – bis 2026-09-19 gab es dafür zusätzlich eine
   eigene "Standard"-Variante nur für den Verknüpfung-Block (Textinfo/
   Datum nebeneinander, Bild als eigene Spalte daneben, siehe
   Git-Historie), das Beitragsbild stand zwischenzeitlich außerdem direkt
   nach dem Titel bzw. als letztes Element der ganzen Card statt direkt
   nach den Pillen (siehe .programm-card__text weiter unten). */
/* Trennlinie (border-top, bis 2026-09-16 stattdessen ein voller Rahmen,
   siehe Git-Historie) nur bei Cards OHNE Beitragsbild (siehe :has()-
   Regel weiter unten) – Cards mit Bild haben weder Trennlinie noch
   Padding-top, das Bild bildet selbst die optische Abgrenzung.
   overflow: hidden bleibt als Sicherheitsnetz gegen
   Subpixel-Rundung (sowie als Maske fürs beim Hover skalierte Bild,
   siehe transform: translateZ(0) unten). */
.programm-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative; /* Bezugsrahmen fürs "Stretched Link" (siehe .card__link::after) */
    /* Erzwingt eine eigene Compositing-Ebene für diese Card: ohne das
       "leckt" das beim Hover skalierte Bild (siehe .card-media--sm img
       weiter unten, transform: scale) in manchen Browsern minimal über
       die hier gesetzte overflow:hidden-Maske hinaus – ein bekanntes
       Rendering-Verhalten bei transformierten Kindelementen,
       ursprünglich auf der Programm-Übersicht beobachtet (damals noch
       echtes CSS-Mehrspaltenlayout, siehe Git-Historie zu .programm-
       tage-spalten). translateZ(0) ändert am Layout/der Optik selbst
       nichts, zwingt den Browser aber, die Maske zuverlässig auf die
       eigene, isolierte Ebene dieser Card anzuwenden – bewusst als
       generelle Absicherung auf .programm-card selbst belassen, nicht
       nur auf die damals betroffene Ansicht beschränkt. */
    transform: translateZ(0);
}
/* Cards OHNE Beitragsbild: Trennlinie oben (border-top) und dazu
   passender Innenabstand (padding-top, --space-sm, Abstand zur Pille
   bzw. zum Text darunter) – Cards mit Bild haben beides nicht (siehe
   .programm-card oben). */
.programm-card:not(:has(.card-media--sm)) {
    border-top: 1px solid var(--color-quaternary);
}
.programm-card:not(:has(.card-media--sm)) .programm-card__text {
    padding-top: var(--space-sm);
}
/* Einziger Inhaltsblock der Card (bis 2026-10-09 zusätzlich in einem
   Wrapper .programm-card__textdatum neben einem eigenen Datumsblock, aus
   der Zeit des Spaltenlayouts, siehe Git-Historie): Beitragsbild (falls
   vorhanden), Programmart-Pillen (horizontal, siehe .card-label--
   horizontal weiter oben) und .programm-card__content. display: flex/flex-direction: column macht
   daraus eine vertikale Abfolge (Bild, Pillen, dann Titel/Text) mit
   --space-xs dazwischen – Pillen stehen immer im normalen Textfluss als
   erstes Element des Textes, MIT Bild direkt unter dem Bild (bis
   2026-09-20 stattdessen als Overlay in dessen linker oberer Ecke, siehe
   Git-Historie). */
.programm-card__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}
/* Kein zusätzlicher margin-bottom mehr auf den Programmart-Pillen
   selbst (Default --space-xs, siehe .card-label--pills oben) – als
   eigener Flex-Child von .programm-card__text kommt der Abstand zum
   nachfolgenden Element bereits vollständig aus dessen gap, ein
   zusätzlicher margin-bottom würde die Lücke nur vergrößern. */
.programm-card__text > .card-label--pills {
    margin-bottom: 0;
}
/* Abstand zwischen Bild und Pillen: --space-sm statt des gap (--space-xs)
   – margin-top ergänzt nur die Differenz. */
.programm-card__text > .card-media--sm + .card-label--pills {
    margin-top: calc(var(--space-sm) - var(--space-xs));
}
/* Text-Teil: Mitwirkende, Titel, dann Weitere Mitwirkende, Eckdaten,
   Datum und Uhrzeit (alle vier .card-text) – kein eigener Innenabstand
   (kommt von .programm-card, siehe oben). Kein gap (Default 0,
   Block-Elemente mit p-Default-Margin 0 stapeln sich dadurch ohne
   Abstand, im reinen Zeilenabstand): Mitwirkende direkt an Titel, die
   vier .card-text-Zeilen direkt untereinander – einzige Ausnahme ist
   der Übergang vom Titel zur ERSTEN Zeile darunter, egal welche das ist
   (gemeinsame Regel mit Mediathek/Festivalausgabe, siehe
   ".card--sammlung .card-title--bold + .card-text" weiter unten). */
.programm-card__content {
    display: flex;
    flex-direction: column;
    flex: 1;
}
/* Abstand zwischen den Card-Elementen: kein Abstand zwischen den
   Textinformationen (Default 0) – gilt für .card__body (Person/Bild/
   Video/Text/Rückblick/Festivalausgabe), siehe .programm-card__content
   weiter oben für die Programmpunkt-Textelemente. Jedes .card-text
   holt sich seinen Abstand nach oben trotzdem selbst zurück (siehe
   dort, höhere Spezifität + spätere Kaskadenposition) – diese Regel
   bleibt daher relevant für alle anderen Rollenklassen (z. B.
   .card-title direkt nach .card-label).*/
.card__body > * + * {
    margin-top: 0;
}

/* Card-Elemente: wiederverwendbare Rollen für alle Card-Typen (Person,
   Bild/Video/Text, Rückblick, Festivalausgabe, Programmpunkt) statt
   komponentenspezifischer, aber inhaltlich identischer Klassen. */

/* Bild Groß: randloses Bild als erstes Element der Card, volle Breite,
   kein Innenabstand (siehe card.php, cards/festivalausgabe.php). Festes
   Seitenverhältnis 3:2 + object-fit: cover (siehe .card-media--lg img
   weiter unten) statt des natürlichen Bildformats: Quell-Bilder kommen in
   sehr unterschiedlichen Formaten (Hoch-/Querformat, Foto vs. Video-
   Thumbnail) – ohne festes Verhältnis würde das die Card-Höhe im Grid
   stark streuen. 3:2 entspricht dem Format der meisten vorhandenen
   Event-Fotos (kaum Beschnitt) und beschneidet 16:9-Video-Thumbnails nur
   moderat. Bewusst weiterhin kein erzwungenes Gleichmachen der GESAMTEN
   Card-Höhe (siehe .card-grid: align-items: start) – nur der Bildbereich
   wird vereinheitlicht, Text bleibt unterschiedlich lang. */
.card-media--lg,
.card-media--sm {
    display: block;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    /* Bezugsrahmen für den Credit-Overlay (siehe .card-media__scrim/
       __credit unten, snippets/picture.php $creditOverlay). */
    position: relative;
    /* Ist immer ein "a" (siehe card.php/cards/mediathek-card.php/cards/
       person-bild.php/cards/programmpunkt.php), erbt daher die
       Unterstreichung vom zentralen "a" (siehe Reset oben) – muss HIER
       auf dem "a" selbst stehen, nicht nur auf einem Kind-Element (z. B.
       .card-media--placeholder): einmal gesetzt, malt ein Vorfahre die
       Linie unter jeden Nachfahren-Text, ein text-decoration: none nur am
       Kind hebt das nicht auf. Betrifft den Initialen-Platzhalter (die
       Info-Pillen, siehe .card-media__pillen, liegen dagegen inzwischen
       außerhalb dieses "a", brauchen die Regel hier also nicht mehr). */
    text-decoration: none;
}
/* Platzhalter im Bildbereich der Person-Card-mit-Bild (siehe cards/
   person-bild.php), wenn kein Portrait gepflegt ist – zeigt die
   Initialen der Person (siehe PersonPage::initialen()) statt eines
   leeren Freiraums, füllt dafür den kompletten .card-media--lg-Rahmen
   (dessen Seitenverhältnis/Bezugsrahmen bereits oben gelten). */
.card-media--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background-color: var(--color-tertiary);
    font-family: var(--font-body);
    font-size: var(--text-xl);
    line-height: var(--leading-xl);
    letter-spacing: var(--tracking-xl);
    color: var(--color-background);
}
/* Abstand Bild -> Text bei den vier Mediathek-Medientypen (siehe
   cards/mediathek-card.php) sowie der Person-Card-mit-Bild (siehe cards/
   person-bild.php, dieselbe Bild-Card-Optik) – .card selbst ist bereits
   ein Flex-Container (display: flex; flex-direction: column), daher
   reicht ein gap auf den jeweiligen Typ statt eines margin auf
   .card-media--lg/.card__body einzeln. Gezielt auf diese Typen
   beschränkt, nicht auf .card generell: Rückblick (dieselbe .card-Basis)
   soll unverändert bleiben. */
.card--sammlung,
.card--galerie,
.card--video,
.card--text,
.card--person-bild {
    gap: var(--space-xs);
}
/* Festivalausgabe-Card: gleiches Prinzip, aber eigener (etwas größerer)
   Abstand als die Mediathek-Medientypen. */
.card--festivalausgabe {
    gap: var(--space-xs);
}
/* Person-Card OHNE Bild (siehe cards/person.php, nur Mitwirkende-
   Übersicht + Mitwirkende-Sektion auf der Festivalausgabe-Seite): keine
   umlaufende Border/kein umlaufender Innenabstand, sondern eine
   Trennlinie unten (wie ein Listen-Eintrag) – padding-inline bewusst 0,
   die Card reicht dadurch bis an den Rand des Grids (siehe
   .card-grid--person weiter unten für row-gap: 0/das fehlende
   padding-top der ersten Card, beides zusammen ergibt eine durchgehende
   Liste ohne doppelte Abstände zwischen den Trennlinien). */
.card--person {
    padding-block: var(--space-sm);
    border-bottom: 1px solid var(--color-quaternary);
}
/* Card-Grid mit Person-Cards ohne Bild (siehe cards/person.php,
   templates/mitwirkende.php, snippets/festivalausgabe.php Sektion
   "Mitwirkende"): kein Row-Gap – der Abstand zwischen den Einträgen
   kommt stattdessen ausschließlich aus .card--persons eigenem
   padding-block, die Trennlinie (border-bottom) sitzt dadurch exakt
   mittig zwischen zwei Einträgen statt in einer eigenen Lücke.
   :first-child ohne padding-top: der erste Eintrag beginnt bündig mit
   dem Grid, ohne zusätzlichen Abstand vor der ersten Zeile. */
.card-grid--person {
    row-gap: 0;
}
.card-grid--person > .card--person:first-child {
    padding-top: 0;
}
/* Letzte Card ohne Trennlinie/Innenabstand nach unten – analog zu
   :first-child oben, dieselbe Begründung wie bei
   .beschreibungsliste__zeile:last-child. */
.card-grid--person > .card--person:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}
/* Bildcredit-Overlay auf Card-Bildern (siehe snippets/picture.php
   $creditOverlay – nur gerendert, wenn ein Credit gepflegt ist), unten
   links, immer weiß, unabhängig vom Bildinhalt dank der dunklen Fläche
   darunter lesbar. */
.card-media__scrim {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    /* Weicher, gekrümmter Verlauf ("Scrim-Gradient", seit 2026-10-19):
       viele Stufen statt eines linearen Auslaufs, damit kein sichtbarer
       Balken entsteht. Feste Höhe statt Prozent der Bildhöhe – bei
       kleinen Card-Bildern war der Verlauf sonst zu kurz. Bis 42 % der
       Höhe (= zwei Credit-Zeilen inkl. Abstand unten, ca. 2,5rem) bleibt
       die Deckkraft bei mindestens 55 % Schwarz: weiß darauf über weißem
       Bildgrund (ungünstigster Fall) ca. 4,7:1, über den 4,5:1 von WCAG
       1.4.3 für den kleinen Credit-Text (--text-xs). Darüber läuft er
       sanft aus. */
    height: 6rem;
    max-height: 100%;
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.7) 0%,
        rgba(0, 0, 0, 0.66) 15%,
        rgba(0, 0, 0, 0.6) 30%,
        rgba(0, 0, 0, 0.55) 42%,
        rgba(0, 0, 0, 0.45) 55%,
        rgba(0, 0, 0, 0.32) 68%,
        rgba(0, 0, 0, 0.18) 80%,
        rgba(0, 0, 0, 0.07) 90%,
        rgba(0, 0, 0, 0) 100%
    );
    pointer-events: none;
}
.card-media__credit {
    position: absolute;
    left: var(--space-xs);
    right: var(--space-xs);
    bottom: var(--space-xxs);
    /* Text sitzt unten rechts, "left" bleibt trotzdem als Breitenbegrenzung
       stehen (verhindert Überlaufen bei einem langen Credit-Text). */
    text-align: right;
    margin: 0;
    color: #fff;
    font-family: var(--font-body);
    font-size: var(--text-xs);
    line-height: var(--leading-xs);
    letter-spacing: var(--tracking-xs);
    /* Wie .card-media__scrim: rein dekorativ/informativ, soll den
       darunterliegenden "Stretched Link" (siehe .card__link::after) nicht
       blockieren – sonst wäre genau die Ecke, in der der Credit steht,
       eine tote Zone ohne Hover-/Klick-Wirkung. */
    pointer-events: none;
}
/* $creditText (siehe picture.php) kommt aus einem Writer-Feld und ist
   selbst schon in <p> gewrappt (block-level) – ohne diese Regel bricht
   das vorangestellte "©" (reiner Text davor, siehe picture.php) in eine
   eigene Zeile um, statt mit dem Credit-Text zu fließen. Gleiches Prinzip
   wie .caption-text/.caption-credit p oben. */
.card-media__credit p {
    display: inline;
    margin: 0;
}

/* Art: kleines, sans-serif Label – Default (--text-sm) weicht bei
   Festivalausgabe (Seitentitel über dem Festivaltitel, siehe Regel
   direkt danach), Programmart bei Programmpunkt/Video und dem
   Medientyp-Kicker in der Mediathek ab (siehe .card-label--horizontal/
   --type unten). */
.card-label {
    font-family: var(--font-subheading);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
}
/* Festivalausgabe-Kicker (Seitentitel, z. B. "Festival 2026") in
   Titelgröße statt der sonst üblichen --card-label-Größe – bleibt
   trotzdem sans-serif/font-heading wie der Default oben, nur größer,
   da diese Card kein eigenes .card-title--bold für den Seitentitel hat
   (das übernimmt hier bereits der Festivaltitel darunter). */
.card--festivalausgabe .card-label {
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
}
/* Festivaltitel (Motto) bei Festivalausgabe: sans-serif/font-subheading
   statt des von .card-title geerbten serifigen --font-body – hebt sich
   dadurch stärker vom Fließtext-Charakter des Default-Titels ab. */
.card--festivalausgabe .card-title {
    font-family: var(--font-subheading);
}
.card-label--horizontal {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
}
/* Programmtyp-Tags (Programmpunkt-Card UND Programmpunkt-Detailseite,
   siehe .card-label--pills unten) und Info-Pillen der Mediathek-Card
   (Videolänge/Bildanzahl, siehe .card-media__pillen
   weiter unten): serifig statt sans-serif – weniger "schreiend" als der
   Festivalausgabe-Kicker, da hier mehrere Tags/Cards nebeneinander
   stehen. */
.card-label--pills {
    font-family: var(--font-body);
}
/* Programmtyp-Tags (Programmpunkt-Card UND -Detailseite) und Info-Pillen
   der Mediathek-Card als kompakte Pills statt reinem Text: eigener
   Hintergrund (eckig, kein Radius mehr, siehe Git-Historie), mit
   Innenabstand. Eigene, wiederverwendbare Modifier-Klasse statt an
   .programm-card gebunden, da dieselbe Pill-Optik auch außerhalb der
   Card gebraucht wird (siehe .programmpunkt-art in templates/
   programmpunkt.php). Der Hintergrund/Innenabstand sitzt auf den
   einzelnen <span>s (siehe cards/programmpunkt.php), nicht auf dem
   umgebenden Flex-Container selbst – sonst gäbe es einen einzigen
   durchgehenden Balken statt mehrerer einzelner Pills. */
.card-label--pills {
    gap: var(--space-xs);
    margin-bottom: var(--space-xs);
}
.card-label--pills span {
    background-color: var(--color-quaternary);
    padding-inline: var(--space-xs);
    padding-block: 6px;
}
@media only screen and (max-width: 720px) {
    .card-label--pills span {
        padding-block: 4px;
    }
}
/* Pille direkt unter der H1 (Programmpunkt-/Medienseiten-Detailseiten,
   siehe templates/programmpunkt.php/sammlung.php/galerie.php/video.php/
   text.php): eigene
   span-md-Grid-Reihe statt vormals eines gemeinsamen .stack-Wrappers mit
   der Überschrift (siehe Git-Historie) – der Abstand zum nachfolgenden
   Inhalt soll ausschließlich vom normalen Grid-Row-Gap kommen, kein
   zusätzliches eigenes Spacing mehr. .card-label--pills' generisches
   margin-bottom oben ist für die Pillen-Nutzung INNERHALB von Cards
   gedacht (Abstand zum Kartentext darunter) und wird hier gezielt
   zurückgesetzt.
   margin-bottom: --space-xl zum nachfolgenden Inhalt statt des sonst
   üblichen Grid-Row-Gaps. */
.mediathek-art,
.programmpunkt-art {
    margin-bottom: var(--space-xl);
}
/* Info-Pillen der Mediathek-Card (Videolänge/Bildanzahl je nach
   Medientyp, siehe cards/mediathek-card.php
   $pillen sowie cards/sammlung|galerie|video|text.php – bis 2026-09-16
   stattdessen ein einzelner Medientyp-Kicker, siehe Git-Historie; aktuell
   nur Videolänge bzw. Bildanzahl, das Datum steht unter dem Titel) – sitzt
   ohne zusätzlichen Abstand in der rechten oberen Ecke des Beitragsbilds
   (Credit-Overlay unten links, siehe .card-media__credit). Bewusst als eigenes
   Geschwisterelement VOR dem Bild-Link statt eines Kindes darin (siehe
   cards/mediathek-card.php) – bleibt dadurch für Screenreader erreichbar,
   Videolänge/Bildanzahl sind für alle Nutzer:innen
   relevante Informationen, nicht nur dekorativ. Bezugsrahmen ist .card
   selbst (position: relative, siehe dort), nicht der Bildbereich: das
   Bild ist das erste Kind ohne jeden Innenabstand, seine Ecke fällt
   daher exakt mit der Kartenecke zusammen. pointer-events: none, damit
   die Pillen nicht Teil des "Stretched Link" werden (siehe
   .card__link::after). z-index: 1 nötig, da das Bild (.card-media--lg)
   im Markup NACH den Pillen steht und selbst position: relative hat:
   ohne explizites z-index stapeln sich zwei "positionierte" Elemente
   (auch position: relative zählt dafür, nicht nur absolute) mit
   z-index:auto in Dokumentreihenfolge – das später stehende Bild läge
   sonst über den Pillen und würde sie unsichtbar machen (bestätigter
   Bug, siehe Git-Historie). */
.card-media__pillen {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 1;
    margin-bottom: 0;
    pointer-events: none;
}
/* Hintergrund/Schrift bei den Medien-Card-Pillen (Overlay auf dem
   Beitragsbild) gegenüber dem .card-label--pills-Default umgedreht –
   dunkler Hintergrund (--color-primary) statt hellem Grau, helle
   Schrift (--color-quaternary) statt der sonst geerbten dunklen
   Textfarbe. Nur beim Card-Overlay, NICHT auf der Medienseite selbst
   (.mediathek-art bleibt beim generischen .card-label--pills-Default,
   genau wie .programmpunkt-art) und nicht an der generischen
   .card-label--pills-Basis (dort z. B. auch "Mitwirkung" auf der
   Person-Seite unverändert bleibt). Kontrastprüfung: #ddd-Text auf
   #222-Hintergrund ergibt rund 11,7:1 – deutlich über den geforderten
   4,5:1 (WCAG AA). Teiltransparenter statt voll deckender Hintergrund,
   da etwas vom Foto durchscheinen soll. color-mix() mischt
   --color-primary mit "transparent" statt eines festen rgba()-Werts,
   damit die Pille der zentralen Farbvariable folgt, falls die sich
   künftig ändert. Kontrastprüfung der Transparenz: der ungünstigste
   Fall ist ein sehr HELLER Bildausschnitt hinter der Pille (ein
   dunkler Ausschnitt macht den Hintergrund nur zusätzlich dunkler, das
   verbessert den Kontrast zur hellen Schrift nur) – über vollständig
   weißem Bildgrund bleibt das resultierende Grau bei 85% Deckkraft
   (0,85 × #222 + 0,15 × #fff ≈ #444444) noch klar unter dem Grauwert,
   der mit --color-quaternary (#ddd) als Text gerade noch 4,5:1 ergibt
   (ab rund 71% Deckkraft nötig) – reichlich Sicherheitsabstand für
   realistische Fotoausschnitte. */
.card-media__pillen span {
    /* Fallback für Browser ohne color-mix() (Chrome < 111, Safari <
       16.2): sie verwerfen die zweite Deklaration, ohne diese Zeile blieb
       die Pille transparent. Entspricht --color-primary bei 85 % Deckung. */
    background-color: rgba(34, 34, 34, 0.85);
    background-color: color-mix(in srgb, var(--color-primary) 85%, transparent);
    color: var(--color-quaternary);
}

/* Titel Regular: serifig, 24px, normales Gewicht, linksbündig – Default
   gilt noch für Rückblick-Titel und Festivaltitel bei Festivalausgabe;
   Person-Name sowie die Mitwirkende-Zeile bei Programmpunkt weichen ab
   (siehe die beiden Regeln direkt danach). */
.card-title {
    font-family: var(--font-body);
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
    text-align: left;
    font-weight: 400;
}
/* Personenbezug (Name) statt Fließtext-Charakter: in Titelgröße statt
   --card-label-Größe – eigene Rolle, kein card-label, da diese Zeile
   meist der prominenteste Text nach dem eigentlichen Titel ist. Bei der
   Person-Card (mit UND ohne Bild, siehe cards/person-bild.php/
   person.php) und der Mitwirkende-Zeile der Programmpunkt-Card
   zusätzlich --font-heading (sans-serif wie ein Label) statt des von
   .card-title geerbten --font-body (die Mitwirkende-Zeile bei
   Programmpunkt bis 2026-09-21 noch --font-body, siehe Git-Historie). */
.card--person-bild .card-title,
.card--person .card-title,
.programm-card .card-title {
    font-size: var(--text-md);
}
.card--person-bild .card-title,
.card--person .card-title,
.programm-card .card-title {
    font-family: var(--font-subheading);
}

/* Titel Bold: wie Titel Regular, aber fett (z. B. Programmpunkt-Titel,
   Bild-/Video-/Text-Titel in der Mediathek) – --font-subheading (T-Star-
   Bold, siehe :root) statt --font-body + font-weight: 700 (bis
   2026-09-2X, siehe Git-Historie: löste über die eigene 700-Fassung der
   --font-body-Familie faktisch T-Star-Heavy statt eines echten
   Bold-Schnitts auf). font-weight bleibt trotzdem explizit gesetzt (statt
   geerbtem 400 von .text/body): passend zum einzigen, unter
   --font-subheading registrierten Schnitt (siehe @font-face
   "T-Star-Bold" weiter oben, font-weight: 700). */
.card-title--bold {
    font-family: var(--font-subheading);
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
    text-align: left;
    font-weight: 700;
}
/* Text: serifig, 16px – Datum/Veranstaltungsort/Stadt bei
   Festivalausgabe und sämtliche card-text-Angaben in der Mediathek
   (Eintragsanzahl, Bildanzahl, Videolänge, Erscheinungsdatum,
   Textausschnitt) bekommen zusätzlich --color-secondary statt der
   geerbten --color-primary – deutlich gedämpfter als der Titel/die
   Mitwirkende-Zeile darüber. Programmpunkt ebenso: Weitere Mitwirkende,
   Eckdaten, Datum und Uhrzeit alle gedämpft (bis 2026-10-09 nur Datum
   und Uhrzeit, siehe Git-Historie). Eigener margin-top statt Vererbung aus dem umgebenden
   Null-Abstand (siehe ".card__body, .programm-card jeweils > * + *"
   weiter oben): höhere Spezifität (eine Klasse statt Universalselektor
   + Kindkombinator) und später in der Kaskade, gewinnt daher gezielt
   für jedes .card-text. */
.card-text {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
}
.card--sammlung .card-text,
.card--galerie .card-text,
.card--video .card-text,
.card--text .card-text,
.card--festivalausgabe .card-text,
.programm-card .card-text {
    color: var(--color-secondary);
}
/* Medientyp/Datum unter dem Titel der vier Mediathek-Medientypen (siehe
   cards/mediathek-card.php $medientyp/$datum): zwei eigene .card-text-
   Zeilen (Medientyp, dann Datum bzw. bei Sammlung die Eintragsanzahl),
   Schrift/Größe von .card-text (--text-sm, --font-body), Farbe
   --color-secondary (siehe Regel oben). --space-xxs Abstand (bis
   2026-10-09 --space-xs) nur zwischen Titel und der ERSTEN Zeile
   darunter – als margin-top auf dieser Zeile, nicht als margin-bottom
   am Titel. Zwischen den .card-text-Zeilen selbst kein Abstand (siehe
   Regel direkt danach). Gemeinsame Regel für alle Card-Typen mit
   Titel + Textzeilen: Mediathek (Medientyp/Datum), Festivalausgabe
   (Festivaltitel, dann Datum) und Programmpunkt (Weitere Mitwirkende,
   Eckdaten, Datum oder Uhrzeit – je nachdem, welche Zeile als erste
   vorhanden ist, siehe .programm-card__content). */
.card--sammlung .card-title--bold + .card-text,
.card--galerie .card-title--bold + .card-text,
.card--video .card-title--bold + .card-text,
.card--text .card-title--bold + .card-text,
.card--festivalausgabe .card-title + .card-text,
.programm-card .card-title--bold + .card-text {
    margin-top: var(--space-xxs);
}
.card--sammlung .card-text + .card-text,
.card--galerie .card-text + .card-text,
.card--video .card-text + .card-text,
.card--text .card-text + .card-text {
    margin-top: 0;
}
/* Kein Puffer unter dem Titel (siehe .card__body padding-bottom):
   - Mediathek: das Datum bildet das abschließende Textelement, die
     Unterstreichung des Titels ist dadurch nicht von .card overflow:
     hidden bedroht.
   - Person ohne Bild (Team, Mitwirkende): die Card hat selbst
     padding-block (siehe .card--person), die Unterstreichung liegt
     damit immer innerhalb der Card. Ohne Puffer ist der Abstand oben und
     unten gleich, die Trennlinie sitzt mittig zwischen zwei Einträgen
     (bis 2026-10-09 unten --space-xxs mehr als oben). */
.card--sammlung .card__body,
.card--galerie .card__body,
.card--video .card__body,
.card--text .card__body,
.card--person .card__body {
    padding-bottom: 0;
}
/* Text-Card (seit 2026-10-15 ohne Beitragsbild, siehe cards/text.php):
   Linie oben, Abstand --space-sm darunter, dann Titel, Kurzbeschreibung
   (--text-md in --font-body und Haupttextfarbe – liest sich wie ein
   kurzer Vorspann), Autor*innen, Medientyp und Datum (alle drei als
   gedämpfte card-text). --space-xxs zwischen Titel und Kurzbeschreibung
   (gemeinsame Regel .card-title--bold + .card-text oben) sowie zwischen
   Kurzbeschreibung und Autor*innen (Regel hier, überschreibt das
   margin-top: 0 zwischen card-text-Zeilen). */
.card--text {
    border-top: 1px solid var(--color-quaternary);
}
.card--text .card__body {
    padding-top: var(--space-sm);
}
.card--text .card-text--beschreibung {
    font-family: var(--font-body);
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
    color: var(--color-primary);
}
.card--text .card-text--beschreibung + .card-text {
    margin-top: var(--space-xxs);
}
/* Erscheinungsdatum unter dem Titel: gedämpfter als der Titel, wie die
   übrigen sekundären Angaben im Projekt (siehe .card-text) – hier aber
   zentriert statt linksbündig, da die Text-Detailseite insgesamt
   zentriert aufgebaut ist. */
.text-erscheinungsdatum {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
    color: var(--color-secondary);
}
/* Modifier für "Stand: [Jahr]" unterhalb der Links (siehe
   templates/person.php): links statt zentriert. Eigene Grid-Row, der
   Abstand zu den Links darüber kommt bereits vom row-gap des .grid
   (siehe hr-Kommentar oben im Dokument), kein zusätzliches margin-top
   nötig. */
.text-erscheinungsdatum--biografie {
    text-align: left;
}
.programmpunkt-mitwirkende {
    font-family: var(--font-heading);
    font-size: var(--text-xl);
    line-height: var(--leading-xl);
    letter-spacing: var(--tracking-xl);
}
/* Weitere Mitwirkende (siehe ProgrammpunktPage::weitereMitwirkendeHtml())
   und Eckdaten-Feld (Zusatz), siehe templates/programmpunkt.php – kein
   Gap zwischen den beiden <p>-Zeilen nötig, da <p>-Margins bereits
   zentral auf 0 zurückgesetzt sind (siehe Reset oben) und hier keine
   zusätzliche Regel das wieder aufhebt. Die separate Beschreibungsliste
   mit Datum/Uhrzeit/Ort/Besuch (siehe .beschreibungsliste--einspaltig
   weiter oben) nutzt bewusst eine andere Klasse: sie braucht die
   Trennlinie zwischen den Zeilen, hier ist keine gewünscht. */
.programmpunkt-meta {
    font-family: var(--font-body);
    font-size: var(--text-md);
    line-height: var(--leading-md);
    letter-spacing: var(--tracking-md);
}
/* Bild füllt den festen Rahmen komplett aus, statt ihn nur einzupassen
   (letterboxing) – object-fit: cover statt contain, da hier bewusst
   beschnitten werden soll (anders als z. B. .lightbox-image, das den
   vollen Bildinhalt zeigt). <picture> selbst braucht dieselbe Höhe/Breite
   wie das <img> darin, sonst reicht es die prozentuale Höhe nicht
   zuverlässig durch (picture.php rendert <picture><img>...</picture>). */
.card-media--lg picture,
.card-media--lg img,
.card-media--sm picture,
.card-media--sm img {
    display: block;
    width: 100%;
    height: 100%;
}
.card-media--lg img,
.card-media--sm img {
    object-fit: cover;
}
/* Card-Image-Hover: sehr reduzierter, ausschließlich per transform:scale
   umgesetzter Zoom beim Hover über die ganze Card (nicht erst über dem
   Bild selbst) – dieselbe "ganze Card reagiert"-Logik wie bei
   .programm-card:hover .card__link weiter oben (der Stretched Link macht
   die ganze Card klickbar, der Hover-Zustand soll daher schon dort
   greifen). overflow: hidden liegt bereits auf .card-media--lg/--sm
   (siehe oben) – das Bild darf dadurch gefahrlos über seinen Rahmen
   hinaus skalieren, ohne dass Card oder Bildcontainer selbst ihre Größe
   ändern (kein Layout Shift, kein Überlaufen). Nur transform, keine
   weiteren Eigenschaften (kein Overlay/Blur/Opacity). Gilt automatisch
   nicht für Cards ohne Bild: der Initialen-Platzhalter (siehe
   .card-media--placeholder) ist ein <span>, kein <img> – dieser Selektor
   trifft dort schlicht nichts, keine eigene Ausnahme nötig. */
.card-media--lg img,
.card-media--sm img {
    transform: scale(1);
    transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* @media (hover: hover) and (pointer: fine) statt eines ungeschützten
   :hover: verhindert, dass der Zoom auf Touch-Geräten nach einem Tap als
   künstlicher, "hängenbleibender" Hover-Zustand sichtbar bleibt (kein
   echtes Zeigegerät vorhanden, das den Hover beim Wegbewegen wieder
   aufheben könnte) – ein 4-%-Zoom fällt dabei stärker auf als die
   übrigen, unproblematischeren Hover-Effekte dieser Seite (z. B.
   .card__link), daher hier zusätzlich abgesichert. */
@media (hover: hover) and (pointer: fine) {
    .card:hover .card-media--lg img,
    .programm-card:hover .card-media--sm img {
        transform: scale(1.04);
    }
}
/* Reduzierte Bewegungspräferenz: kein Übergang UND keine skalierte
   Endposition – der Zoom entfällt dadurch vollständig, nicht nur
   beschleunigt (siehe Kommentar zu ähnlichen Reduced-Motion-Regeln an
   anderer Stelle dieser Datei, z. B. main). */
@media (prefers-reduced-motion: reduce) {
    .card-media--lg img,
    .card-media--sm img {
        transition: none;
    }
    .card:hover .card-media--lg img,
    .programm-card:hover .card-media--sm img {
        transform: none;
    }
}

/* Bildunterschrift / Credit (siehe picture.php): beide zusammen als ein
   durchlaufendes Textelement im <figcaption> statt zwei getrennter
   Zeilen – display: inline auf beiden Wrappern UND deren jeweiligem
   inneren <p> (Bildunterschrift/Credit kommen aus Writer-Feldern, siehe
   blueprints/files/default.yml, die ihren Inhalt immer in <p> wrappen –
   ohne diese Zusatzregel bliebe das <p> block-level und erzwänge trotz
   display: inline auf dem Wrapper einen Zeilenumbruch). Der normale
   Leerraum zwischen den beiden <div>s im Markup ergibt automatisch den
   sichtbaren Abstand zwischen Unterschrift und Credit, kein eigenes
   margin/gap nötig. Bildunterschrift UND Credit beide in
   --color-secondary (bis 2026-09-2X Bildunterschrift stattdessen in
   --color-primary, siehe Git-Historie), Credit weiterhin mit
   vorangestelltem "©" (siehe picture.php).
   font-size/line-height zusätzlich hier auf <figcaption> selbst (nicht
   nur auf den beiden Kind-Elementen darunter): jede Zeilenbox erhält
   sonst einen "Strut" in der vom body geerbten Zeilenhöhe (--text-md/
   --leading-md, größer als --text-sm/--leading-sm hier) – bei einer
   umbrechenden Bildunterschrift wirkte der Zeilenabstand dadurch
   sichtbar größer als bei gewöhnlichem Kleintext (.text--sm), obwohl
   beide dieselben --text-sm/--leading-sm-Werte verwenden. */
figcaption {
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    margin-top: var(--space-xxs);
}
.caption-text,
.caption-credit {
    display: inline;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--leading-sm);
    letter-spacing: var(--tracking-sm);
    color: var(--color-secondary);
}
.caption-text p,
.caption-credit p {
    display: inline;
    margin: 0;
}

/* Festivalausgabe-Logo: eingebettetes SVG (Kreis + Wortmarke "S.BW.",
   Pfade aus assets/favicon.svg, siehe logo.php), Farben je Ausgabe über
   --logo-bg/--logo-text. --logo-size steuert die Größe je nach
   Einsatzort. Kein erzwungener Mindestkontrast zwischen den beiden
   Farben: Wortmarke, nicht Fließtext, WCAG-Logo-Ausnahme greift (siehe
   Docblock in logo.php für die genaue Erfüllungskriterien-Referenz). */
.logo {
    --logo-size: calc(var(--text-xl) * 1.1);
    width: var(--logo-size);
    height: var(--logo-size);
    flex-shrink: 0;
}
/* overflow: visible – der Kreis reicht im SVG exakt bis an den Rand
   der Zeichenfläche (viewBox 0–85.04). Bei krummen Pixelgrößen (z. B.
   46,8 px auf Tablet Portrait) schneiden Browser, v. a. Safari/iOS, die
   Kantenglättung am unteren Rand sonst ab (Fix 2026-10-15). */
.logo__svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.logo__kreis {
    fill: var(--logo-bg);
}
.logo__schrift {
    fill: var(--logo-text);
}
/* Größerer Faktor auf schmaleren Breakpoints (Mobile/Tablet-Portrait):
   --text-xl selbst wird dort kleiner (siehe :root-Breakpoint-Kaskade),
   ein gleichbleibender 1.1er-Faktor würde das Logo optisch zu klein
   wirken lassen. Mobile und Tablet-Portrait teilen sich seit 2026-10-08
   denselben Faktor (seit 2026-10-10 1.3, davor 1.35). */
@media only screen and (max-width: 720px) {
    .logo {
        --logo-size: calc(var(--text-xl) * 1.3);
    }
}


