/* ==========================================================================
   festival.css — gemeinsame Designschicht für die vom Tool erzeugten
   Festivalseiten (wasted-open-air, nbgpunkfest, weinturm, alle künftigen).

   Warum diese Datei existiert
   ---------------------------
   Vorher trug jede Site eine eigene, zu 95 % identische Kopie desselben
   CSS (Wasted.css, NBGPunkfest.css, Weinturm.css). Gemeinsam war darin
   alles ausser den Markenfarben — und damit lag jeder Layout- und
   Kontrastfehler dreifach im Repo. Diese Datei trägt jetzt das Aussehen,
   die Site-CSS tragen nur noch Farbe und echte Eigenheiten.

   Reihenfolge: dd_clubs lädt diese Datei über setup.typoscript. Das
   sys_template der jeweiligen Site hängt danach ihr <Festival>.css an.
   Die Site gewinnt also im Zweifel — genau so ist es gedacht.

   Farbvertrag
   -----------
   Die Site-CSS liefern --bs-primary/--bs-secondary und dazu die vom
   Tool bereits ausgerechnete lesbare Textfarbe auf dieser Fläche
   (--klaro-primary-contrast). Diese Datei rechnet nichts nach, sie
   benutzt den Vertrag. Steht dort nichts, greift ein Fallback.

   Kontrast, nachgerechnet (WCAG 2.1, sRGB-Relativluminanz) statt
   geschaetzt — Stand vor dieser Datei:
     .btn-primary weiss auf #0aa8b0 ...... 2,90:1  AA verlangt 4,5:1  ✗
     Helferband .text-dark auf Tuerkis ... wechselnd, Foto darunter   ✗
     Spielplan auf .bg-white.bg-opacity-50 wechselnd, Foto darunter   ✗
     .side-meta-btn weiss auf #0aa8b0 .... 2,90:1                     ✗
     Fliesstext #64748B auf Weiss ........ 4,76:1  gerade so AA       ~
   Der Fliesstext war also nicht der Durchfaller, als den ich ihn
   zuerst genommen habe — sein Problem war Groesse, Zentrierung und
   Zeilenlaenge. Die vier Faelle darueber sind echte Durchfaller und
   unten einzeln behoben.

   Die lesbare Farbe auf einer Markenflaeche wird nie geraten: jede
   Site-CSS liefert sie als --klaro-*-contrast, und diese Datei nimmt
   sie als --fest-on-primary/--fest-on-secondary. Wo eine Flaeche
   ueber einem Foto liegt, hilft kein Token — dort steht ein Scrim.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
    /* Marke. Fallbacks nur, damit die Datei auch ohne Site-CSS nicht bricht. */
    --fest-primary: var(--bs-primary, #175da9);
    --fest-secondary: var(--bs-secondary, #69acdf);

    /* Lesbare Schrift auf der jeweiligen Vollfläche. Kommt aus dem Tool. */
    --fest-on-primary: var(--klaro-primary-contrast, #fff);
    --fest-on-secondary: var(--klaro-secondary-contrast, #000);

    /* Markenfarbe als Textfarbe — nie --fest-primary direkt für Text
       nehmen, die Riso-artigen Festivalfarben sind dafür im hellen Thema
       durchweg zu hell und im dunklen zu dunkel. Das Tool liefert je
       Festival beide Enden: -text-emphasis ist die abgedunkelte,
       -border-subtle die aufgehellte Fassung derselben Farbe. */
    --fest-primary-ink: var(--bs-primary-text-emphasis, #0b2e4f);

    /* Schrift. --fest-ink liegt bei 17,8:1 auf Weiss, --fest-ink-muted
       bei 7,5:1 — beide bestehen AA auch in kleinen Graden. */
    --fest-ink: #14181f;
    --fest-ink-muted: #4a5568;
    --fest-ink-invers: #fff;

    /* Flächen */
    --fest-flaeche: #fff;
    --fest-flaeche-leise: #f5f6f8;
    --fest-linie: rgba(20, 24, 31, 0.12);

    /* Karten, die über einem Foto liegen. Deckend genug, dass die
       Schrift nicht vom Motiv abhängt. */
    --fest-karte: rgba(255, 255, 255, 0.94);

    /* Abstandsleiter. Fluid, damit mobil nichts auseinanderfällt und
       Desktop nicht gedrängt wirkt. */
    --fest-space-2xs: 0.5rem;
    --fest-space-xs: 0.75rem;
    --fest-space-s: 1rem;
    --fest-space-m: clamp(1.5rem, 4vw, 2.5rem);
    --fest-space-l: clamp(2.5rem, 7vw, 4.5rem);
    --fest-space-xl: clamp(3.5rem, 10vw, 7rem);

    /* Textbreite. Alles darüber liest sich schlecht, egal wie schön.
       68 statt der oft genannten 60 Zeichen, weil deutsche Komposita
       lang sind: bei 60 fiel "Stoner-Punk-Blödsinn-Nackedei-Festival"
       allein in eine Zeile und riss ein Loch in den Absatz darüber. */
    --fest-mass: 68ch;

    /* ----------------------------------------------------------------
       Schriftgrössen: eine Leiter, auf der alles liegt.

       Bootstrap bringt eine feste Leiter mit (.fs-1 = 2,5rem und so
       weiter). Fest heisst hier: dieselbe Zahl auf 360 px wie auf
       1440 px. Praktisch sprang eine Sektionsüberschrift mobil auf
       28 px — zu gross für die Zeile — und wirkte auf dem Desktop
       verloren. Die Leiter unten ist dieselbe Leiter, nur fluid:
       zwischen 360 px und 1440 px interpoliert, an beiden Enden
       gedeckelt. Der Verhältnisschritt liegt bei rund 1,22 mobil und
       1,33 auf grossen Schirmen — enger, wo wenig Platz ist.

       Alles darunter (h1…h6, .fs-1…6, .lead, .small, .display-*)
       wird auf diese sechs Stufen gelegt. Neue Grössen gehören in
       diese Leiter, nicht als Einzelwert an ein Bauteil.
       ---------------------------------------------------------------- */
    --fest-step--1: clamp(0.875rem, 0.85rem + 0.11vw, 0.9375rem);  /* 14 → 15 */
    --fest-step-0:  clamp(1rem, 0.955rem + 0.19vw, 1.125rem);      /* 16 → 18 */
    --fest-step-1:  clamp(1.125rem, 1.04rem + 0.37vw, 1.375rem);   /* 18 → 22 */
    --fest-step-2:  clamp(1.3125rem, 1.1rem + 0.93vw, 1.75rem);    /* 21 → 28 */
    --fest-step-3:  clamp(1.5rem, 1.17rem + 1.48vw, 2.25rem);      /* 24 → 36 */
    --fest-step-4:  clamp(1.875rem, 1.35rem + 2.33vw, 3rem);       /* 30 → 48 */
    --fest-step-5:  clamp(2.25rem, 1.5rem + 3.33vw, 4rem);         /* 36 → 64 */

    /* Kanten und Tiefe */
    --fest-radius: 14px;
    --fest-radius-s: 8px;
    --fest-schatten: 0 1px 2px rgba(20, 24, 31, 0.06),
                     0 8px 24px -12px rgba(20, 24, 31, 0.28);
    --fest-schatten-hoch: 0 2px 4px rgba(20, 24, 31, 0.08),
                          0 20px 40px -16px rgba(20, 24, 31, 0.38);

    /* Höhe der fixierten Kopfleiste. Wird unten je Breakpoint gesetzt und
       von scroll-padding, Sprungmarken und dem Offcanvas mitbenutzt. */
    --fest-header-h: 68px;

    /* ----------------------------------------------------------------
       Schriften.

       Das Theme verlangte "Public Sans" und lieferte keine Datei dazu —
       gemessen: null Font-Requests. Die Festivalseiten liefen komplett
       in Arial. Deshalb liegen die Rollen jetzt auf Tokens, und geladen
       werden die drei Schriften aus dem Design-System (SIL Open Font
       License, selbst ausgeliefert, kein Google):

         display  Anton .................. Plakatzeilen
         text     Atkinson Hyperlegible .. alles, was gelesen wird
         mono     Courier Prime .......... Zeiten, Mengen, Nummern

       Bringt ein Festival eine eigene Schrift mit, überschreibt seine
       Site-CSS diese drei Tokens — sonst nichts. Die Ersatzschriften
       dahinter sind nach Laufweite gewählt, nicht nach Beliebtheit:
       springt Anton ein, soll die Zeile nicht doppelt so breit werden.
       ---------------------------------------------------------------- */
    --fest-font-display: "Anton", "Oswald", "Arial Narrow", system-ui, sans-serif;
    --fest-font-text: "Atkinson Hyperlegible", system-ui, -apple-system,
                      "Segoe UI", Roboto, sans-serif;
    --fest-font-mono: "Courier Prime", ui-monospace, "SFMono-Regular",
                      Menlo, Consolas, monospace;

    /* Bleibt in beiden Themen dunkel: Scrims über Fotos, Footer,
       Einwilligungskasten, Meta-Leiste. Das ist keine Themenfarbe,
       sondern eine Materialeigenschaft. */
    --fest-dunkel: #14181f;

    --fest-tempo: 180ms;
}

/* --------------------------------------------------------------------------
   1a. Dunkles Thema
   --------------------------------------------------------------------------
   Das Theme schaltet data-bs-theme am <html> und folgt dabei
   prefers-color-scheme (block/js/vendors/color-modes.js). Die Tokens
   oben waren fest auf helles Papier gerechnet — im dunklen Thema stand
   dadurch dunkle Schrift auf dunklem Grund, praktisch die ganze Seite.
   Hier kippen dieselben Tokens, nicht einzelne Bauteile: was oben eine
   Rolle bekommen hat (Schrift, leise Schrift, Fläche, Linie), bekommt
   hier seinen dunklen Wert, und alles Weitere folgt von allein.

   Gemessene Verhältnisse auf --fest-flaeche (#12151b):
     --fest-ink       #f2f4f7 .... 16,0:1
     --fest-ink-muted #a8b2c1 ....  7,7:1
   Beide bestehen AA auch in kleinen Graden, wie im hellen Thema.

   Die Auswahl gilt nur für das <html>-Element. Innerhalb der Seite
   benutzt das Theme dasselbe Attribut für einzelne dunkle Bänder über
   Fotos — die sollen hiervon nicht getroffen werden. Deshalb html[…]
   und nicht [data-bs-theme="dark"] allein.
   -------------------------------------------------------------------------- */

html[data-bs-theme="dark"] {
    --fest-primary-ink: var(--bs-primary-border-subtle, #9ec5fe);

    --fest-ink: #f2f4f7;
    --fest-ink-muted: #a8b2c1;
    --fest-ink-invers: #14181f;

    --fest-flaeche: #12151b;
    --fest-flaeche-leise: #1b1f27;
    --fest-linie: rgba(255, 255, 255, 0.16);

    --fest-karte: rgba(24, 28, 36, 0.94);

    /* Schatten tragen auf dunklem Grund nicht — dort arbeitet die Kante. */
    --fest-schatten: 0 0 0 1px rgba(255, 255, 255, 0.08);
    --fest-schatten-hoch: 0 0 0 1px rgba(255, 255, 255, 0.16),
                          0 20px 40px -16px rgba(0, 0, 0, 0.6);
}

/* Bootstrap mitziehen, damit Utility-Klassen im Redaktionsinhalt
   (.text-muted, .border, Cards) nicht am eigenen Graustufen-Set kleben. */
.festival-theme,
footer {
    --bs-body-color: var(--fest-ink);
    --bs-secondary-color: var(--fest-ink-muted);
    --bs-tertiary-color: var(--fest-ink-muted);
    --bs-border-color: var(--fest-linie);
    --bs-link-color: var(--fest-primary-ink);
    --bs-link-hover-color: var(--fest-primary-ink);

    /* Bootstrap rechnet Abstände und Zeilenhöhen aus dieser Variable —
       sie zu setzen ist sauberer, als hinterher font-size zu überschreiben. */
    --bs-body-font-size: var(--fest-step-0);
    --bs-body-line-height: 1.65;

    /* Über --bs-font-sans-serif erben auch Knöpfe, Felder, Tabellen und
       alles aus dem Redaktionsinhalt die Leseschrift — einzeln
       nachzuziehen wäre eine Liste, die immer eine Stelle vergisst. */
    --bs-font-sans-serif: var(--fest-font-text);
    --bs-body-font-family: var(--fest-font-text);
    --bs-font-monospace: var(--fest-font-mono);
}

/* --------------------------------------------------------------------------
   2. Basis — Typografie, Links, Fokus, Bewegung
   -------------------------------------------------------------------------- */

html {
    /* Ankersprünge landeten bisher unter der fixierten Kopfleiste. */
    scroll-padding-top: calc(var(--fest-header-h) + var(--fest-space-s));
}

body {
    font-family: var(--fest-font-text);
    color: var(--fest-ink);
    background-color: var(--fest-flaeche);
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    /* Auf Android/Safari sonst willkürlich vergrösserte Absätze im Querformat. */
    -webkit-text-size-adjust: 100%;
    /* clip, nicht hidden: overflow-x:hidden macht den Body zum
       Scroll-Container, und darin haelt kein position:sticky mehr —
       die Kopfleiste scrollte damit weg statt oben zu bleiben.
       overflow-x:clip schneidet genauso ab, ohne Scroll-Container. */
    overflow-x: clip;
}

/* Die Schriftfarbe wird genau einmal gesetzt, oben am Container, und
   vererbt sich von dort. Das ist keine Stilfrage, sondern der einzige
   Weg, auf dem lokal umgedrehte Flächen weiter funktionieren: die
   FAQ-Seite legt einen Kasten mit data-bs-theme="dark" mitten in die
   helle Seite, das Helferband tut dasselbe über einem Foto. Stand die
   Farbe fest an p und li, gewann sie dort gegen die Umgebung — dunkle
   Schrift auf dunklem Kasten, gemessen 1,0:1 über 36 Textstellen. */
.festival-theme {
    color: var(--fest-ink);
}

.festival-theme p,
.festival-theme li {
    line-height: 1.65;
}

/* Ein umgedrehter Kasten dreht auch die Schrift um. Bootstrap markiert
   ihn mit data-bs-theme="dark"; dieselbe Marke benutzen wir hier. */
.festival-theme [data-bs-theme="dark"] {
    color: #f2f4f7;
}

/* Bootstraps Grössenklassen auf die Leiter legen. Die Klassen im
   Template bleiben unangetastet — .fs-1 heisst weiter .fs-1, es meint
   jetzt nur eine Grösse, die mit dem Viewport mitgeht. */
.festival-theme .display-1,
.festival-theme .display-2,
.festival-theme .display-3 {
    font-size: var(--fest-step-5);
}

.festival-theme h1,
.festival-theme .h1,
.festival-theme .fs-1 {
    font-size: var(--fest-step-4);
}

.festival-theme h2,
.festival-theme .h2,
.festival-theme .fs-2 {
    font-size: var(--fest-step-3);
}

.festival-theme h3,
.festival-theme .h3,
.festival-theme .fs-3 {
    font-size: var(--fest-step-2);
}

.festival-theme h4,
.festival-theme .h4,
.festival-theme .fs-4,
.festival-theme .lead {
    font-size: var(--fest-step-1);
}

.festival-theme h5,
.festival-theme .h5,
.festival-theme .fs-5 {
    font-size: var(--fest-step-0);
}

.festival-theme h6,
.festival-theme .h6,
.festival-theme .fs-6,
.festival-theme .small,
.festival-theme small {
    font-size: var(--fest-step--1);
}

.festival-theme h1,
.festival-theme h2,
.festival-theme h3,
.festival-theme h4,
.festival-theme h5,
.festival-theme h6,
.festival-theme .h1,
.festival-theme .h2,
.festival-theme .h3,
.festival-theme .display-1,
.festival-theme .display-2 {
    text-wrap: balance;
    /* inherit, nicht --fest-ink: das Theme faerbt Ueberschriften fest
       mit #1e293b, und ein fester Ersatz wuerde in umgedrehten Kaesten
       denselben Fehler machen wie vorher. */
    color: inherit;
    /* Damit eine angesprungene Überschrift nicht hinter der Leiste klebt. */
    scroll-margin-top: calc(var(--fest-header-h) + var(--fest-space-s));
}

/* ----------------------------------------------------------------------
   Plakatzeilen: h1, h2 und die display-Klassen.

   Anton ist eine Plakatschrift — eng, hoch, ohne Halbfett. Sie braucht
   deshalb das Gegenteil dessen, was eine Textschrift in dieser Grösse
   braucht: fast keinen Zeilenabstand, keine negative Laufweite (sie ist
   von Haus aus eng genug), und Trennung, weil deutsche Komposita in
   einer engen Schrift trotzdem über die Spalte hinausragen.

   Das Markenhandbuch schreibt für Anton zusätzlich Grossbuchstaben vor.
   Hier bewusst nicht: die Überschriften kommen aus dem Backend und sind
   nicht alle Plakatzeilen. "Gefördert aus den Projektmitteln Freie
   Szene des Bayerischen Staatsministeriums für Wissenschaft und Kunst"
   steht auf wasted-open-air als h2 — in Versalien wäre das eine Wand.
   Anton in gemischter Schreibung sieht immer noch nach Plakat aus und
   verträgt auch die langen Zeilen, die Redaktionen nun mal schreiben.

   Die Untergrenze aus dem Handbuch (nie unter 24px) ist eingehalten:
   Stufe 3 beginnt bei genau 24px, Stufe 4 bei 30px. h3 und kleiner
   bleiben deshalb in der Leseschrift.
   ---------------------------------------------------------------------- */
.festival-theme h1,
.festival-theme h2,
.festival-theme .h1,
.festival-theme .h2,
.festival-theme .fs-1,
.festival-theme .fs-2,
.festival-theme .display-1,
.festival-theme .display-2,
.festival-theme .display-3 {
    font-family: var(--fest-font-display);
    font-weight: 400;
    line-height: 1.04;
    letter-spacing: 0.005em;
    hyphens: auto;
}

/* Ab hier wird gelesen, nicht plakatiert. */
.festival-theme h3,
.festival-theme h4,
.festival-theme h5,
.festival-theme h6,
.festival-theme .h3,
.festival-theme .h4,
.festival-theme .fs-3,
.festival-theme .fs-4,
.festival-theme .fs-5,
.festival-theme .fs-6 {
    font-family: var(--fest-font-text);
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.01em;
    hyphens: none;
}

/* Ein sichtbarer, eckiger Fokusring auf allem, was Fokus bekommt.
   Vorher war er stellenweise vom Theme wegoptimiert. */
.festival-theme a:focus-visible,
.festival-theme button:focus-visible,
.festival-theme input:focus-visible,
.festival-theme select:focus-visible,
.festival-theme textarea:focus-visible,
footer a:focus-visible,
.navbar a:focus-visible,
.navbar button:focus-visible {
    outline: 3px solid var(--fest-primary);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Bilder verhalten sich im Fluss immer flüssig — auch die, die das Tool
   mit festen width/height-Attributen ausgibt. */
.festival-theme img,
footer img {
    max-width: 100%;
    height: auto;
}

/* Markierter Text in der Markenfarbe — kostet nichts und die Seite
   fuehlt sich sofort gemeint an statt geerbt. */
.festival-theme ::selection,
footer ::selection {
    background-color: var(--fest-primary);
    color: var(--fest-on-primary);
}

/* Ankersprünge gleiten, ausser jemand hat Bewegung abbestellt. */
html {
    scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* --------------------------------------------------------------------------
   3. Kopfleiste
   --------------------------------------------------------------------------
   Gemessener Ausgangszustand bei 390 px Breite: die Leiste war 178 px hoch
   — 21 % des Viewports, dauerhaft, weil position:fixed. Ursache war der
   Container mit .w-50 (mobil wirklich 187 px breit, w-md-100 greift erst
   ab 768 px): das Logo musste in die halbe Breite, der Burger rutschte
   darunter. Hier wird die Leiste wieder eine Leiste.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------
   Eine Leiste, nicht zwei.

   headhesive.js (geladen in block/…/page.typoscript) klont beim Scrollen
   die komplette Navigation an <body> und zeigt den Klon als feste
   Leiste. Im DOM standen dadurch zwei <nav aria-label="…navigation">
   und zwei Burger-Knoepfe; sichtbar war zwar immer nur einer, aber
   beide zeigten auf dasselbe #offcanvasNavbar, und der Klon ist ein
   Schnappschuss — was sich am Original aendert (aktiver Punkt,
   geoeffnetes Menue), erreicht ihn nicht.

   position:sticky macht dasselbe ohne Kopie. Der Klon wird ausgeblendet
   statt das Skript zu entfernen: headhesive laeuft auch auf orgadiy,
   und diese Datei gilt nur fuer die Festivalseiten.

   Unterschied im Verhalten, bewusst in Kauf genommen: der Klon kam erst
   ab einer Scrollschwelle, sticky ist sofort da. Bei 68px Leiste ist
   das kein Verlust.
   -------------------------------------------------------------------- */

.navbar-clone {
    display: none !important;
}

.navbar:not(.navbar-clone) {
    /* !important, weil im Template .position-relative steht und
       Bootstraps Positions-Utilities selbst !important sind — ohne das
       blieb die Leiste relativ und scrollte weg. */
    position: sticky !important;
    top: 0;
}

.navbar {
    --fest-header-h: 68px;
    background-color: var(--fest-flaeche);
    z-index: 1040;
    min-height: var(--fest-header-h);
    padding-top: 0;
    padding-bottom: 0;
    z-index: 1040;
    /* Das Hintergrundbild der Leiste darf nie höher werden als die Leiste. */
    overflow: visible;
}

.navbar > .position-absolute img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* lib.headerBackground und lib.footerBackground geben auch dann ein
   <img> aus, wenn nichts gepflegt ist — mit src="". Auf nbgpunkfest
   stand deshalb das Symbol für ein kaputtes Bild in Leiste und Footer.
   Ein leeres src ist nie etwas, das jemand sehen soll. */
/* Das !important ist hier nicht Bequemlichkeit: die Bilder tragen im
   Template .d-block und .w-100, und Bootstraps Utilities sind selbst
   !important. Ohne Gleichstand bliebe das kaputte Bild stehen. */
.navbar img[src=""],
.navbar img:not([src]),
footer img[src=""],
.festival-theme img[src=""] {
    display: none !important;
}

/* Den mobilen Halbbreiten-Container zurückholen. Die Klassen stehen im
   Template, hier wird ihre Wirkung neutralisiert statt sie zu entfernen —
   so bleibt das Template für andere Sites unangetastet. */
.navbar > .container {
    width: 100% !important;
    max-width: 100%;
    min-height: var(--fest-header-h);
    gap: var(--fest-space-s) !important;
    padding-inline: var(--fest-space-s);
}

@media (min-width: 1200px) {
    .navbar > .container {
        max-width: 1320px;
    }
}

/* Logo: eine Höhe, die zur Leiste passt, statt einer Breite, die sie sprengt. */
.navbar-logo-wrapper {
    flex: 0 0 auto;
    min-width: 0;
}

.navbar-logo-wrapper .navbar-brand {
    display: flex;
    align-items: center;
}

.navbar-logo-wrapper img {
    max-height: 48px;
    width: auto;
    max-width: min(60vw, 260px);
    object-fit: contain;
    transition: max-height var(--fest-tempo) ease;
}

@media (min-width: 992px) {
    .navbar {
        --fest-header-h: 88px;
    }

    .navbar-logo-wrapper img {
        max-height: 64px;
        max-width: 300px;
    }
}

/* Burger: rechts, in der Leiste, und mit einer Trefferfläche, die den
   44-px-Richtwert erfüllt.

   Die Regel steht bewusst in einer max-width-Abfrage. Ohne sie gewann
   das display:flex hier gegen das display:none, das .navbar-expand-lg
   dem Burger ab 992 px gibt — der Burger blieb auf dem Desktop stehen
   und die Hauptnavigation daneben verschwand. */
@media (max-width: 991.98px) {
    .navbar .navbar-toggler {
        margin-left: auto !important;
        align-self: center !important;
        margin-top: 0 !important;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 46px;
        height: 46px;
        padding: 0;
        border-width: 2px;
        border-radius: var(--fest-radius-s);
        background: var(--fest-flaeche);
        box-shadow: none;
    }
}

.navbar .navbar-toggler:focus {
    box-shadow: none;
}

/* Ohne Kopfbild trägt die Leiste die Themenfläche — dann muss auch die
   Schrift der Menüpunkte dem Thema folgen. Das Theme faerbt sie fest
   mit --bs-gray-500, was im dunklen Thema auf dunkler Leiste steht. */
.navbar:not(:has(> .position-absolute img[src]:not([src=""]))) .navbar-nav .nav-item .nav-link {
    color: var(--fest-ink);
}

.navbar:not(:has(> .position-absolute img[src]:not([src=""]))) .navbar-nav .nav-item .nav-link:hover,
.navbar:not(:has(> .position-absolute img[src]:not([src=""]))) .navbar-nav .nav-item .nav-link:focus-visible {
    color: var(--fest-primary-ink);
    background-color: transparent;
}

/* Der aktive Punkt bekam vom Theme eine gefüllte Fläche in --bs-gray-100.
   Im dunklen Thema ist das ein heller Kasten mitten in der Leiste, im
   hellen ein grauer — beides sieht nach vergessenem Zustand aus. Der
   aktive Punkt wird stattdessen unterstrichen, dieselbe Geste wie beim
   Überfahren, nur dauerhaft. */
.navbar .navbar-nav .nav-item .nav-link.active,
.navbar .navbar-nav .nav-item .nav-link:hover,
.navbar .navbar-nav .nav-item .nav-link.show {
    background-color: transparent !important;
}

.navbar .navbar-nav .nav-item .nav-link.active {
    background-size: 100% 2px;
}

/* Hauptnavigation ab lg: ruhige Grössen statt fs-3, klare Zustände. */
@media (min-width: 992px) {
    .navbar .navbar-nav {
        gap: clamp(1rem, 1.6vw, 2rem) !important;
    }

    /* Im Template stehen die Punkte als .fs-3 — das waren 28 px pro
       Menüpunkt und damit breiter als die Leiste. Stufe 1 statt 2. */
    .navbar .nav-link {
        font-size: var(--fest-step-1) !important;
        padding-block: 0.5rem;
        line-height: 1.2;
    }
}

/* Die Leiste trägt je Site ein Hintergrundbild (lib.headerBackground).
   Wo eines liegt, stand die Schriftfarbe der Menüpunkte bisher in jeder
   Site-CSS einzeln auf Weiss — bei Wasted per !important, sonst gar
   nicht. Hier entscheidet die Leiste selbst: liegt ein Bild darin,
   bekommt es einen Scrim und die Punkte stehen weiss darauf. Liegt
   keines darin, bleibt die Schriftfarbe des Themes. */
/* Die Farbe unter dem Bild, nicht nur der Scrim darueber: solange das
   Kopfbild laedt — oder wenn es einmal fehlschlaegt — standen die
   weissen Menuepunkte sonst auf der weissen Themenflaeche. Einmal im
   Screenshot erwischt, und es ist genau der Moment, in dem jemand die
   Seite zum ersten Mal sieht. */
.navbar:has(> .position-absolute img[src]:not([src=""])) {
    background-color: var(--fest-dunkel);
}

.navbar:has(> .position-absolute img[src]:not([src=""]))::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    background: linear-gradient(
        to bottom,
        rgba(20, 24, 31, 0.35),
        rgba(20, 24, 31, 0.15)
    );
    pointer-events: none;
}

/* Die Selektorkette ist so lang, weil das Theme die Punkte mit
   .navbar-light .navbar-nav .nav-item .nav-link faerbt — vier Klassen.
   Kuerzer verliert hier, und ein !important waere die schlechtere
   Antwort auf eine reine Spezifitaetsfrage. */
.navbar:has(> .position-absolute img[src]:not([src=""])) .navbar-nav .nav-item .nav-link,
.navbar:has(> .position-absolute img[src]:not([src=""])) .navbar-nav .nav-item .nav-link:hover,
.navbar:has(> .position-absolute img[src]:not([src=""])) .navbar-nav .nav-item .dropdown-toggle {
    color: #fff;
    background-color: transparent;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

.navbar:has(> .position-absolute img[src]:not([src=""])) .navbar-toggler {
    border-color: rgba(255, 255, 255, 0.85);
}

/* .gradient-text aus dem Theme fuellt die Schrift mit einem Verlauf und
   setzt dafuer -webkit-text-fill-color: transparent. Der Verlauf steht
   dort fest in den Sass-Variablen des Themes (#175DA9 → #69ACDF) und
   kennt die Festivalfarben nicht — auf dem lila Kopfbild von Wasted
   waren die Menuepunkte dadurch praktisch unsichtbar. Jede Site-CSS
   hat das bisher einzeln mit color/-webkit-text-fill-color: #fff
   !important geradegebogen. Hier einmal, an einer Stelle:
   -webkit-text-fill-color zurueck auf currentColor, damit wieder
   color entscheidet.

   Der Verlauf-Hintergrund wird gleich weiterbenutzt — als
   Unterstreichung beim Hover. Die lag vorher als ::after am Text und
   sprang bei mehrzeiligen Punkten. */
.navbar .gradient-text {
    -webkit-text-fill-color: currentColor;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0% 2px;
    transition: background-size var(--fest-tempo) ease;
}

.navbar .gradient-text:hover,
.navbar .gradient-text:focus-visible,
.navbar .nav-item.active > .gradient-text {
    background-size: 100% 2px;
}

.navbar .gradient-text::after {
    content: none;
}

/* Und die dritte Unterstreichung: diy.css setzt fuer alle Sites
   .nav-link:hover { text-decoration-line: underline }. Auf den
   Festivalseiten wuchs damit beim Zeigen die Linie von links ein — und
   darunter erschien gleichzeitig eine zweite, statische. Die
   animierte oben genuegt; diy.css behaelt seine Fassung fuer orga.diy. */
.navbar .nav-link:hover,
.navbar .nav-link:focus-visible {
    text-decoration: none;
}

/* Offcanvas-Menü: grosse Ziele, klare Trennung, Platz für den Daumen. */
.offcanvas.offcanvas-start {
    --bs-offcanvas-width: min(88vw, 380px);
}

.offcanvas .offcanvas-header {
    padding-top: var(--fest-space-s) !important;
    border-bottom: 1px solid var(--fest-linie);
}

.offcanvas .offcanvas-body .nav-link {
    display: block;
    padding: 0.85rem 0.25rem;
    font-size: var(--fest-step-1);
    color: var(--fest-ink);
    border-bottom: 1px solid var(--fest-linie);
}

.offcanvas .offcanvas-body .nav-link:hover,
.offcanvas .offcanvas-body .nav-link:focus-visible {
    color: var(--fest-primary-ink);
}

.offcanvas .offcanvas-body .list-unstyled .nav-link {
    font-size: var(--fest-step-0);
    padding-block: 0.6rem;
    color: var(--fest-ink-muted);
}

/* --------------------------------------------------------------------------
   4. Hero
   -------------------------------------------------------------------------- */

/* Eine Bühne ohne Bild ist keine Bühne. Auf nbgpunkfest steht im
   Datensatz eine Hero-Referenz, deren Datei fehlt: das Template
   rendert eine leere Folie, und die bekam hier ein Seitenverhältnis
   und eine dunkle Fläche — ein schwarzer Kasten über der halben
   Startseite. Ohne echtes Bild fällt der Hero weg. */
.festival-theme .swiper-container#festival-hero-slider:not(
    :has(.swiper-slide img[src]:not([src=""]))
) {
    display: none;
}

.swiper-container {
    /* 50vh liess auf breiten Schirmen ein Briefschlitz-Bild stehen und
       schnitt mobil die Gesichter weg. Seitenverhältnis statt Viewporthöhe,
       gedeckelt, damit über dem Falz noch Inhalt sichtbar bleibt. */
    height: auto;
    aspect-ratio: 4 / 5;
    max-height: calc(100svh - var(--fest-header-h));

    /* Ohne diese Zeile ist die Buehne nicht randlos: sie ist ein
       Flex-Element, ihre Breite also nicht gesetzt, sondern errechnet.
       Greift dann der Deckel, haelt der Browser das Seitenverhaeltnis,
       indem er die BREITE schrumpft — gemessen 1050px in einem 1280px
       breiten Rahmen, also Luecken links und rechts. Mit fester Breite
       beschneidet er stattdessen das Bild, und genau das soll passieren
       (overflow: hidden unten, object-fit: cover am Bild). */
    width: 100%;

    position: relative;
    overflow: hidden;
    background: var(--fest-dunkel);
}

@media (min-width: 576px) {
    .swiper-container {
        aspect-ratio: 3 / 2;
    }
}

@media (min-width: 992px) {
    .swiper-container {
        aspect-ratio: 21 / 9;

        /* Gemeinsamer Deckel. Wer flacher will, setzt ihn in seiner
           Site-CSS neu — Wasted tut das. */
        max-height: 78vh;
    }
}

.swiper-slide {
    /* Swiper laeuft hier mit slidesPerView:"auto" — ohne Breite richtet
       sich die Folie nach dem Bild und es standen zwei Fotos nebeneinander
       im Hero. Eine Folie ist eine Bildschirmbreite. */
    width: 100% !important;
    flex: 0 0 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.swiper-slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Verlauf nach unten, damit der Störer mit dem Datum auf jedem Foto
   steht — bisher war das Glück je nach Bild. */
.swiper-container::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 55%;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.62), transparent);
    pointer-events: none;
    z-index: 2;
}

.divide-cloud-inside {
    position: absolute;
    bottom: clamp(1.5rem, 5vh, 4rem);
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    width: min(92%, 760px);
    pointer-events: none;
}

.divide-cloud-inside > img {
    width: 100%;
    height: auto;
}

.divide-cloud-inside .cloud-text {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: none;
    padding-inline: 1rem;
    padding-top: 0 !important;
}

.divide-cloud-inside .cloud-text h1 {
    font-family: var(--fest-font-display);
    font-weight: 400;
    font-size: clamp(1rem, 0.4rem + 3vw, 2.25rem) !important;
    letter-spacing: 0.01em;
    line-height: 1.05;
    margin: 0;
    text-align: center;
    text-wrap: balance;
    /* Der Störer ist je Festival eine andere Farbe; der Schlagschatten
       hält die Zeile auf hellem wie dunklem Störer lesbar. */
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
}

/* Ohne hochgeladenes Störer-Bild (festival.stickyCloudImage) hätte die
   Datumsplakette sonst keine eigene Höhe und keine Farbe — sie kollabiert
   auf 0 und die Seite wirkt farblos. Fallback: eigene Pille in der
   Festival-Markenfarbe, Kontrastfarbe kommt aus dem bereits pro Festival
   berechneten --klaro-primary-contrast (siehe FestivalStyleService). */
.divide-cloud-inside--plain {
    width: auto;
    max-width: min(92%, 620px);
}

.divide-cloud-inside--plain .cloud-text {
    position: relative;
    inset: auto;
    background: linear-gradient(135deg, var(--fest-primary), var(--fest-secondary));
    border-radius: 999px;
    padding: 0.65rem 1.75rem;
    box-shadow: var(--fest-schatten-hoch);
}

.divide-cloud-inside--plain .cloud-text h1 {
    color: var(--klaro-primary-contrast, #fff) !important;
    text-shadow: none;
}

/* Swiper-Punkte: grösser, mit Kontrast gegen jedes Foto. */
.swiper-container .swiper-pagination {
    z-index: 6;
}

.swiper-container .swiper-pagination-bullet {
    width: 10px;
    height: 10px;
    background: #fff;
    opacity: 0.55;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
}

.swiper-container .swiper-pagination-bullet-active {
    opacity: 1;
    background: var(--fest-secondary);
    width: 28px;
    border-radius: 5px;
}

/* --------------------------------------------------------------------------
   5. Sektionsrhythmus
   --------------------------------------------------------------------------
   Die frame-space-*-Klassen von TYPO3 und die py-5 aus den Content-Blöcken
   addierten sich stellenweise zu über 200 px Leerraum, an anderen Stellen
   stiessen zwei Sektionen ohne Abstand zusammen. Ein Rhythmus für alle.
   -------------------------------------------------------------------------- */

.festival-theme > .frame {
    padding-block: var(--fest-space-m);
    margin-block: 0;
}

.festival-theme > .frame.frame-space-before-medium,
.festival-theme > .frame.frame-space-before-large,
.festival-theme > .frame.frame-space-after-medium,
.festival-theme > .frame.frame-space-after-large {
    margin-block: 0;
}

.festival-theme > .frame > section,
.festival-theme > .frame .container.py-5,
.festival-theme > .frame > .tx-orgadiy > .container {
    padding-block: 0 !important;
}

.festival-theme > .frame > section.py-5 {
    padding-block: 0 !important;
}

/* Erste Sektion sitzt direkt am Hero — aber nur, wenn darüber wirklich
   ein Hero steht. Auf Seiten ohne Bühne (nbgpunkfest) klebte die erste
   Überschrift sonst direkt unter der Leiste. */
.festival-theme > .frame:first-child {
    padding-top: var(--fest-space-l);
}

.festival-theme > .frame:has(> .tx-orgadiy > .swiper-container) + .frame {
    padding-top: var(--fest-space-m);
}

.festival-theme > .frame:last-child {
    padding-bottom: var(--fest-space-l);
}

/* Klassische Inhaltselemente (Formular, Text, Tabelle) bringen keinen
   Container mit — die Content Blocks tun das in ihrem eigenen Template.
   Gemessen auf /kontakt: die Eingabefelder liefen ueber die vollen
   1440px von Kante zu Kante. Wer keinen Container hat, bekommt hier
   einen; Buehne und Karte sind ausgenommen, die sind absichtlich
   randlos. */
.festival-theme > .frame:not(:has(.container)):not(:has(.swiper-container)):not(:has(.google-maps-wrapper)) {
    /* width:100% ist hier nicht redundant: .festival-theme ist ein
       Flex-Container (d-flex flex-column), und ein margin-inline:auto
       an einem Flex-Element schaltet das Strecken ab — der Rahmen fiel
       dadurch auf Inhaltsbreite zusammen, gemessen 258px statt 1320. */
    width: 100%;
    max-width: 1320px;
    margin-inline: auto;
    padding-inline: var(--fest-space-s);
}

/* Der Hero ist bündig, nicht eingerückt. */
.festival-theme > .frame:has(> .tx-orgadiy > .swiper-container) {
    padding-block: 0;
}

/* Die Lineup-Ueberschrift steht im Template als direktes Kind der .row,
   ohne col-Klasse. Im Flex-Raster nahm sie dadurch die Breite einer Karte
   ein und das erste Bandplakat rutschte daneben. */
.festival-theme .row > h2,
.festival-theme .row > h3,
.festival-theme .row > .section-title {
    flex: 0 0 100%;
    width: 100%;
}

/* Gilt genauso fuer den "Alle Kuenstler"-Knopf am Ende des Rasters.

   Nur fuer Elemente ohne Spaltenklasse: der Knopf ist als blosses
   .d-flex.justify-content-center ins Raster gesetzt, und genau dem soll
   die volle Zeile gehoeren. Ohne die Einschraenkung traf die Regel aber
   auch echte Spalten, die dieselben zwei Hilfsklassen zum Zentrieren
   ihres Inhalts benutzen — im Block "Unterstuetzt von" sind das fuenf
   .col.d-flex.justify-content-center, und die standen dadurch als fuenf
   volle Zeilen untereinander statt nebeneinander. Drei Klassen schlagen
   .row-cols-lg-5 > * mit einer.

   Geprueft wird auf .col und col-*, nicht auf "col" als Teilzeichenkette
   — sonst faellt auch .collapse aus der Regel. */
.festival-theme .row > .d-flex.justify-content-center:not(.col):not([class*="col-"]) {
    flex: 0 0 100%;
    width: 100%;
}

/* TYPO3-Frames und die Content-Blöcke bringen beide eigene Abstände mit
   (mb-4 innen, frame-space-* aussen). Am Ende einer Sektion addieren
   sie sich zu einer Lücke, die nach Fehler aussieht. Der Rhythmus
   kommt vom Frame, nicht vom letzten Kind darin. */
.festival-theme > .frame > .tx-orgadiy > .container > *:last-child,
.festival-theme > .frame .festival-info-section:last-child {
    margin-bottom: 0 !important;
}

/* Der Kopf eines Inhaltselements (fluid_styled_content, Header/All)
   steht als <header> direkt im Frame, ohne Container — er begann
   deshalb bei x=0 und lief links aus dem Bild. Gleiche Achse wie alles
   andere. (Dass er auf den CTA-Bändern zusätzlich dieselbe Zeile zeigt
   wie das Band darunter, ist kein Layoutproblem, sondern doppelter
   Inhalt aus dem TYPO3-14-Umstieg — das gehört ins Block-Template.) */
.festival-theme > .frame > header,
.festival-theme > .frame > section > header {
    max-width: 1320px;
    margin-inline: auto;
    padding-inline: var(--fest-space-s);
}

/* Die erste Sektion einer Seite ist ihr Anfang und darf das zeigen.
   Ohne Bühne (nbgpunkfest) trug die Seite sonst dieselbe Überschriften-
   grösse wie jede Zwischenüberschrift weiter unten — die Seite fing
   nicht an, sie lief einfach los. Eine Stufe höher, mehr nicht. */
.festival-theme > .frame:first-child .section-title,
.festival-theme > .frame:has(> .tx-orgadiy > .swiper-container) + .frame .section-title {
    font-size: var(--fest-step-4);
}

/* Sektionsüberschriften bekommen eine gemeinsame Form. */
.festival-theme .section-title {
    margin-bottom: var(--fest-space-s);
    text-wrap: balance;
}

/* Kein max-width hier: die Sektionsüberschrift ist in mehreren
   Templates ein direktes Kind einer .row und damit ein Flex-Element.
   Eine Maximalbreite schrumpft es auf diese Breite zusammen, und das
   erste Bandplakat rutscht daneben. Den Zeilenausgleich macht
   text-wrap: balance, das braucht keine Breitenangabe. */
.festival-theme .section-title.text-center {
    margin-inline: auto;
    text-wrap: balance;
}

/* --------------------------------------------------------------------------
   6. Fliesstext
   --------------------------------------------------------------------------
   Der Beschreibungstext stand in 20 px, zentriert, über die volle
   Containerbreite und in #64748B (4,02:1 — zu schwach). Jetzt: linksbündig
   dort, wo es wirklich zählt, begrenzte Zeilenlänge, AA-fähige Farbe.
   -------------------------------------------------------------------------- */

/* Die Seite hatte drei Achsen auf einmal: die Überschrift zentriert
   über 1320 px, der Fliesstext als eigene zentrierte 62-Zeichen-Spalte,
   und jede andere Sektion (Playlist, Tickets, Lineup) linksbündig an
   der Containerkante. Drei Anfänge, die nichts miteinander zu tun
   haben — das ist der Grund, warum der Anfang der Seite unfertig
   aussieht, nicht die Schriftgrösse.

   Jetzt hat die Seite eine Achse. Die Textspalte bleibt bei ihrem Mass,
   sitzt aber mittig statt an der linken Containerkante: linksbuendig
   liess sie rechts 585px leer (gemessen: 605px Spalte in 1280px Rahmen),
   und das liest sich als Zweispalter, dessen rechte Spalte fehlt — kein
   bewusster Weissraum, sondern eine halbe Seite. Mittig verteilt sich
   derselbe Raum auf beide Seiten. Dieselbe Entscheidung wie bei den
   Formularen in Abschnitt 15.

   Die Absaetze bleiben dabei linksbuendig, nur der Block wandert in die
   Mitte. Damit wird auch das text-center aus dem Backend ueberschrieben:
   zentrierter Fliesstext ueber mehrere Absaetze zwingt das Auge bei
   jeder Zeile zum Suchen des Anfangs, und genau dieser Text ist auf
   allen drei Sites der laengste der Seite. */
.festival-theme .festival-info-section {
    max-width: 48rem;
    margin-inline: auto;
}

.festival-theme .festival-info-section,
.festival-theme .festival-info-section .section-title,
.festival-theme .festival-description p.text-center {
    text-align: left !important;
}

.festival-theme .festival-description {
    max-width: 48rem;
    font-size: var(--fest-step-0);
    line-height: 1.7;
    color: var(--fest-ink);
}

.festival-theme .festival-description p {
    margin-bottom: 1.1em;
}

.festival-theme .festival-description p:last-child {
    margin-bottom: 0;
}

.festival-theme .festival-description a {
    color: var(--fest-primary-ink);
    text-decoration: underline;
    text-underline-offset: 0.18em;
    text-decoration-thickness: 0.08em;
}

/* Der Bild-Block (Info-Seite: "//Location", "//Anreise" samt Karte)
   bringt seine eigene Spaltenbreite mit — col-lg-10, gemessen 827px
   Textbreite, also rund 95 Zeichen je Zeile. Auf derselben Seite steht
   der Fliesstext des Branding-Blocks bei 48rem und die Formulare auch:
   drei Breiten auf einer Seite. Dieselbe Spalte hier, damit die Seite
   eine Achse hat. Bild und Text teilen sie sich, sonst steht die Karte
   breiter als der Text, der sie erklaert. */
.festival-theme .frame-type-ddfthiel_blockimagex1 .row > [class*="col-"] {
    /* Nicht 48rem, sondern 48rem plus der eigene Innenabstand der Spalte
       (--bs-gutter-x, je zur Haelfte links und rechts): gemessen wird die
       Textbreite, nicht die Spaltenbreite. So beginnt der Text auf
       derselben Kante wie der Fliesstext der anderen Bloecke. */
    max-width: calc(48rem + var(--bs-gutter-x));
    margin-inline: auto;
}

/* Das Template rueckt Ueberschrift und Absaetze zusaetzlich mit mx-4 ein.
   Das war die Ersatzloesung fuer eine fehlende Spaltenbreite — die gibt
   es jetzt, und der doppelte Einzug schoebe den Text um weitere 24px
   nach innen. */
.festival-theme .frame-type-ddfthiel_blockimagex1 .mx-4 {
    margin-inline: 0 !important;
}

/* Bootstraps .text-muted lag bei 4,02:1. Auf den Festivalseiten steht es
   unter Bildunterschriften und Terminen — also dort, wo es zählt. */
.festival-theme .text-muted,
footer .text-muted {
    color: var(--fest-ink-muted) !important;
}

/* --------------------------------------------------------------------------
   7. Schaltflächen
   --------------------------------------------------------------------------
   .btn-primary stand auf weiss/#0aa8b0 = 2,59:1. Die lesbare Farbe auf
   der Markenfläche liefert das Tool bereits als --klaro-primary-contrast;
   sie wird hier benutzt statt neu geraten.
   -------------------------------------------------------------------------- */

.festival-theme .btn,
footer .btn,
.navbar .btn {
    --bs-btn-padding-y: 0.7rem;
    --bs-btn-padding-x: 1.35rem;
    --bs-btn-font-weight: 700;
    --bs-btn-border-radius: var(--fest-radius-s);
    min-height: 46px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border-width: 2px;
    transition: transform var(--fest-tempo) ease,
                box-shadow var(--fest-tempo) ease,
                background-color var(--fest-tempo) ease;
}

.festival-theme .btn:hover,
footer .btn:hover {
    transform: translateY(-2px);
    box-shadow: var(--fest-schatten);
}

.festival-theme .btn:active,
footer .btn:active {
    transform: translateY(0);
}

.festival-theme .btn-primary,
footer .btn-primary,
.navbar .btn-primary {
    --bs-btn-color: var(--fest-on-primary);
    --bs-btn-bg: var(--fest-primary);
    --bs-btn-border-color: var(--fest-primary);

    /* Das Designsystem setzt .btn-primary { background: var(--color-accent) }
       direkt auf die Eigenschaft. Gegen einen gesetzten background verliert
       eine blosse Variable - deshalb hier noch einmal ausgeschrieben, sonst
       steht auf jeder Festivalseite ein lila Knopf zwischen den
       Markenfarben. */
    background: var(--fest-primary);
    color: var(--fest-on-primary);
    --bs-btn-hover-color: var(--fest-on-primary);
    --bs-btn-hover-bg: var(--fest-primary);
    --bs-btn-hover-border-color: var(--fest-on-primary);
    --bs-btn-active-color: var(--fest-on-primary);
    --bs-btn-active-bg: var(--fest-primary);
    --bs-btn-active-border-color: var(--fest-on-primary);
    --bs-btn-disabled-color: var(--fest-on-primary);
    --bs-btn-disabled-bg: var(--fest-primary);
    --bs-btn-disabled-border-color: var(--fest-primary);
}

.festival-theme .btn-secondary,
footer .btn-secondary {
    --bs-btn-color: var(--fest-on-secondary);
    --bs-btn-bg: var(--fest-secondary);
    --bs-btn-border-color: var(--fest-secondary);
    --bs-btn-hover-color: var(--fest-on-secondary);
    --bs-btn-hover-bg: var(--fest-secondary);
    --bs-btn-hover-border-color: var(--fest-on-secondary);
    --bs-btn-active-color: var(--fest-on-secondary);
    --bs-btn-active-bg: var(--fest-secondary);
    --bs-btn-active-border-color: var(--fest-on-secondary);
}

/* Umrandete Varianten: Text in der dunklen Marken-Textfarbe, nicht in
   der Flächenfarbe — die ist auf Weiss regelmässig zu hell. */
.festival-theme .btn-outline-primary {
    --bs-btn-color: var(--fest-primary-ink);
    --bs-btn-border-color: var(--fest-primary-ink);
    --bs-btn-hover-color: var(--fest-on-primary);
    --bs-btn-hover-bg: var(--fest-primary);
    --bs-btn-hover-border-color: var(--fest-primary);
    --bs-btn-active-color: var(--fest-on-primary);
    --bs-btn-active-bg: var(--fest-primary);
    --bs-btn-active-border-color: var(--fest-primary);
}

/* Auf dunklen Bändern kehrt sich die Umrandung um. */
.festival-theme [data-bs-theme="dark"] .btn-outline-primary {
    --bs-btn-color: #fff;
    --bs-btn-border-color: #fff;
    --bs-btn-hover-color: var(--fest-ink);
    --bs-btn-hover-bg: #fff;
    --bs-btn-hover-border-color: #fff;
}

/* Mobil laufen einzelne Aktionen über die volle Breite, nebeneinander
   stehende bleiben nebeneinander. */
@media (max-width: 575.98px) {
    .festival-theme .frame .btn:only-child {
        width: 100%;
    }
}

/* --------------------------------------------------------------------------
   8. CTA-Bänder (Content-Block ctax1)
   --------------------------------------------------------------------------
   Das Band legte eine .bg-primary.bg-opacity-75-Fläche über ein Foto und
   setzte den Text darauf mit .text-dark — auf dem hellen Türkis von
   Wasted ergab das weisse Überschrift + dunklen Fliesstext auf derselben
   Fläche. Hier bekommt das Band einen echten Scrim und eine Textfarbe.
   -------------------------------------------------------------------------- */

.festival-theme .jarallax {
    border-radius: var(--fest-radius);
    overflow: hidden;
    isolation: isolate;
}

/* Ohne Deckungsklasse im Selektor: die Redaktion waehlt sie pro Element
   (wasted nimmt bg-opacity-75, nbgpunkfest bg-opacity-50), und bei 50 %
   blieb die Flaeche so hell, dass die weisse Schrift darauf auf 2,66:1
   stand. Welcher Wert dort gewaehlt wurde, darf nicht entscheiden, ob
   die Ueberschrift lesbar ist. */
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-primary"],
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-secondary"],
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-dark"] {
    /* Marke bleibt sichtbar, aber mit einem dunklen Anteil darunter, damit
       die Textfarbe nicht von der Helligkeit des Fotos abhängt. */
    --bs-bg-opacity: 1;
    background: linear-gradient(
        to bottom,
        rgba(20, 24, 31, 0.48),
        rgba(20, 24, 31, 0.66)
    ) !important;
}

.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-primary"]::after,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-secondary"]::after,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax > [class*="bg-dark"]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--fest-primary);
    mix-blend-mode: multiply;
    opacity: 0.55;
}

/* Auf den Content-Block eingegrenzt, nicht auf .jarallax allgemein:
   der Spielplan benutzt denselben Parallax-Rahmen, legt darin aber eine
   weisse Karte ab. Die frühere Fassung faerbte deren Uhrzeiten mit
   !important weiss — weiss auf weiss. */
/* Und falls ein Band gar keine Flaeche hat: dann ist es auch kein
   dunkles Band, und die Schrift folgt dem Thema statt weiss zu bleiben. */
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax:not(:has(> [class*="bg-"])) .position-relative.z-1,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax:not(:has(> [class*="bg-"])) .position-relative.z-1 * {
    color: var(--fest-ink) !important;
}

.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 h1,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 h2,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 h3,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 p,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 li,
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 .text-dark {
    color: #fff !important;
}

/* Der Textblock stand in .w-75 — mobil sind das drei Wörter pro Zeile. */
.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 .w-75 {
    width: 100% !important;
    max-width: var(--fest-mass);
    margin-inline: auto;
}

.festival-theme .frame-type-ddfthiel_blockctax1 .jarallax .position-relative.z-1 p {
    font-size: var(--fest-step-0);
    line-height: 1.65;
}

/* Der Text hatte auf kleinen Schirmen zu wenig Luft zur Bildkante. */
@media (max-width: 575.98px) {
    .festival-theme .jarallax {
        padding: var(--fest-space-m) var(--fest-space-s) !important;
    }
}

/* --------------------------------------------------------------------------
   9. Lineup / Bandraster
   -------------------------------------------------------------------------- */

.festival-theme .frame-type-clubs_fe_artists .col-md-4 .card {
    display: block;
    height: 100%;
}

/* Die Plakate haben je Band ein anderes Seitenverhältnis. Ein gemeinsames
   Raster macht aus der Sammlung ein Lineup. */
.festival-theme .frame-type-clubs_fe_festivalbranding .row > [class*="col-"] > a {
    position: relative;
    display: block;
    /* Nicht height:100% — die Spalte ist so hoch wie die höchste Karte
       der Zeile, und der Link würde unter dem Plakat eine leere weisse
       Fläche aufspannen. */
    height: auto;
    line-height: 0;
    overflow: hidden;
    border-radius: var(--fest-radius);
    box-shadow: var(--fest-schatten);
    transition: transform var(--fest-tempo) ease,
                box-shadow var(--fest-tempo) ease;
}

.festival-theme .frame-type-clubs_fe_festivalbranding .row > [class*="col-"] > a img {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 0 !important;
    transition: transform 320ms ease;
}

.festival-theme .frame-type-clubs_fe_festivalbranding .row > [class*="col-"] > a:hover,
.festival-theme .frame-type-clubs_fe_festivalbranding .row > [class*="col-"] > a:focus-visible {
    transform: translateY(-4px);
    box-shadow: var(--fest-schatten-hoch);
}

.festival-theme .frame-type-clubs_fe_festivalbranding .row > [class*="col-"] > a:hover img {
    transform: scale(1.04);
}

/* Der Bandname stand bisher nur im alt-Attribut. Als Bildunterschrift
   ist er auch für Sehende und für die Suche da. */
.festival-theme .lineup-name {
    display: block;
    margin-top: 0.6rem;
    font-weight: 700;
    font-size: var(--fest-step-0);
    line-height: 1.25;
    color: var(--fest-ink);
    text-wrap: balance;
}

/* Zwei Karten nebeneinander statt einer pro Zeile — mobil passen zwei
   Plakate gut, und die Seite wird halb so lang. */
@media (max-width: 767.98px) {
    .festival-theme .frame-type-clubs_fe_festivalbranding .row > .col-12.col-md-6.col-lg-4 {
        width: 50%;
        padding-inline: 0.375rem;
    }

    .festival-theme .lineup-name {
        font-size: var(--fest-step--1);
    }
}

/* Bandkarten auf der Bands-Seite */
.festival-theme .frame-type-clubs_fe_artists .card {
    border: 1px solid var(--fest-linie);
    border-radius: var(--fest-radius);
    box-shadow: var(--fest-schatten);
    overflow: hidden;
    transition: transform var(--fest-tempo) ease,
                box-shadow var(--fest-tempo) ease;
}

.festival-theme .frame-type-clubs_fe_artists .card:hover {
    transform: translateY(-4px);
    box-shadow: var(--fest-schatten-hoch);
}

.festival-theme .zoom-img {
    overflow: hidden;
    border-radius: var(--fest-radius-s);
}

.festival-theme .zoom-img img {
    transition: transform 320ms ease;
}

.festival-theme .card:hover .zoom-img img {
    transform: scale(1.05);
}

/* --------------------------------------------------------------------------
   10. Galerie
   -------------------------------------------------------------------------- */

.gallery-masonry {
    position: relative;
    width: 100%;
}

/* Zwei Spalten ab der schmalsten Breite: eine 6500 px lange Bilderspalte
   war der Hauptgrund fuer die Scrolllaenge der Startseite. Das stand
   vorher hinter einer Schwelle bei 480px und griff damit auf keinem
   ueblichen Telefon (iPhone 390-430px, Android ab ~360px) — genau dort
   also nicht, wo die Spalte am laengsten wurde. */
.gallery-sizer,
.gallery-item {
    width: calc((100% - 0.75rem) / 2);
}

.gallery-gutter {
    width: 0.75rem;
}

.gallery-sizer,
.gallery-gutter {
    height: 0;
    padding: 0;
    margin: 0;
}

.gallery-item {
    margin-bottom: 1rem;
}

.gallery-image-wrapper {
    position: relative;
    overflow: hidden;
    width: 100%;
    border-radius: var(--fest-radius-s);
    background: var(--fest-flaeche-leise);
}

.gallery-image-wrapper > a {
    display: block;
    width: 100%;
}

.gallery-image {
    display: block;
    width: 100%;
    height: auto;
    transition: transform 360ms ease;
}

.gallery-image-wrapper:hover .gallery-image {
    transform: scale(1.035);
}

.gallery-copyright-overlay {
    position: absolute;
    right: 0;
    bottom: 0;
    z-index: 2;
    padding: 0.35rem 0.5rem;
    background: rgba(0, 0, 0, 0.62);
    color: #fff;
    font-size: var(--fest-step--1);
}

/* Der Bildnachweis steht in einem <p> im Overlay. Die allgemeine
   Fliesstextfarbe griff dort mit durch und setzte dunkle Schrift auf
   die dunkle Blende — gemessen 1,18:1. */
.gallery-copyright-overlay p,
.gallery-copyright-overlay span,
.gallery-copyright-overlay a {
    color: #fff;
    margin: 0;
}

/* Das Theme setzt den Nachweis auf opacity:0 und blendet ihn erst beim
   Ueberfahren ein. Auf jedem Touchgeraet heisst das: nie. Ein
   Fotonachweis, den man nur mit der Maus sehen kann, ist keiner —
   dafuer steht er da, und die Fotografin hat ihn sich nicht als
   Hover-Effekt ausgebeten. Er steht jetzt immer, dafuer leiser: ein
   Verlauf statt eines Balkens, damit er das Bild nicht abschneidet. */
.festival-theme .gallery-copyright-overlay {
    opacity: 1;
    left: 0;
    right: 0;
    padding: 1.6rem 0.6rem 0.4rem;
    /* Der Verlauf ist unten deckend und wird erst oben durchsichtig —
       der Text sitzt im unteren Drittel. Mit einem gleichmaessigen
       Verlauf stand die Oberkante der Zeile ueber hellen Bildstellen
       noch auf 3,9:1. */
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.88) 0%,
        rgba(0, 0, 0, 0.82) 55%,
        transparent 100%
    );
}

.festival-theme .gallery-copyright-overlay p {
    font-size: 0.75rem;
    line-height: 1.3;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

@media (min-width: 768px) {
    .gallery-sizer,
    .gallery-item {
        width: calc((100% - 2rem) / 3);
    }

    .gallery-gutter {
        width: 1rem;
    }
}

@media (min-width: 1200px) {
    .gallery-sizer,
    .gallery-item {
        width: calc((100% - 3rem) / 4);
    }

    .gallery-gutter {
        width: 1rem;
    }
}

/* --------------------------------------------------------------------------
   11. Spielplan
   -------------------------------------------------------------------------- */

.festival-theme .frame-type-clubs_fe_gigs .bg-white.bg-opacity-50,
.festival-theme .frame-type-clubs_fe_gigs .bg-light.bg-opacity-50 {
    /* Halbtransparentes Weiss über einem Foto: der Text stand je nach
       Bildstelle auf 2:1. Volle Fläche, dafür lesbar. */
    --bs-bg-opacity: 1;
    background-color: var(--fest-karte) !important;
    backdrop-filter: blur(6px);
    border-radius: var(--fest-radius);
}

/* Der Spielplan steht in einem Abschnitt mit data-bs-theme="dark" —
   die Uhrzeiten und Bandnamen erben von dort weiss. Auf dem Foto war
   das richtig, in der weissen Karte darueber ist es weiss auf weiss.
   Innerhalb der Karte gilt deshalb wieder die Fliesstextfarbe. */
.festival-theme .frame-type-clubs_fe_gigs .bg-white.bg-opacity-50,
.festival-theme .frame-type-clubs_fe_gigs .bg-light.bg-opacity-50,
.festival-theme .frame-type-clubs_fe_gigs .bg-white.bg-opacity-50 *,
.festival-theme .frame-type-clubs_fe_gigs .bg-light.bg-opacity-50 * {
    color: var(--fest-ink);
}

.festival-theme .frame-type-clubs_fe_gigs .bg-white.bg-opacity-50 a:hover,
.festival-theme .frame-type-clubs_fe_gigs .bg-light.bg-opacity-50 a:hover {
    color: var(--fest-primary-ink);
}

.festival-theme .frame-type-clubs_fe_gigs .contrast-text {
    color: #fff;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}

.festival-theme .frame-type-clubs_fe_gigs .list-unstyled li {
    padding-block: 0.5rem;
    border-bottom: 1px solid var(--fest-linie);
}

.festival-theme .frame-type-clubs_fe_gigs .list-unstyled li:last-child {
    border-bottom: 0;
}

/* Zeiten, Mengen und Nummern in der Schreibmaschinenschrift mit
   Tabellenziffern: untereinander stehende Uhrzeiten fluchten dann
   wirklich, statt je nach Ziffer zu wackeln. */
.festival-theme .frame-type-clubs_fe_gigs .list-unstyled li,
.festival-theme .presale-dates,
.festival-theme time,
.festival-theme .gallery-copyright-overlay {
    font-family: var(--fest-font-mono);
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   12. Seitliche Meta-Leiste / Nach-oben-Knopf
   -------------------------------------------------------------------------- */

.side-meta {
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
}

/* Im Template steht .bg-primary.text-white. Bei Wasted ergab das weiss
   auf #0aa8b0 = 2,90:1. Eine je Festival passende Schriftfarbe liesse
   sich aus --fest-on-primary holen — dann müsste aber auch das Icon
   (ein <img>, kein Font) mitschalten, und dafür gibt es in CSS keine
   Bedingung auf einen Variablenwert. Deshalb hier die Lösung, die für
   jedes Festival ohne Zusatzangabe stimmt: dunkle Fläche, weisse
   Schrift, weisses Icon — 15,6:1, und die Marke trägt der Rand. */
.side-meta-btn {
    color: #fff !important;
    background-color: color-mix(in srgb, var(--fest-dunkel) 92%, transparent) !important;
    border: 2px solid var(--fest-primary);
    min-height: 44px;
    padding-inline: 0.7rem !important;
    border-radius: var(--fest-radius-s) !important;
    box-shadow: var(--fest-schatten);
    backdrop-filter: blur(6px);
    transition: background-color var(--fest-tempo) ease;
}

.side-meta-btn:hover,
.side-meta-btn:focus-visible {
    background-color: var(--fest-dunkel) !important;
}

.side-meta-icon {
    width: 22px;
    height: 22px;
    filter: brightness(0) invert(1);
}

@media (max-width: 767.98px) {
    /* Zwei Klassen, nicht eine: am Element haengt Bootstraps .flex-column
       mit !important. Das Theme laedt vor Bootstrap, bei gleicher
       Spezifitaet gewinnt also die Spalte — die vier Knoepfe standen
       dadurch als volle Zeilen untereinander statt nebeneinander. Mit
       .side-meta.flex-column entscheidet die Spezifitaet, nicht die
       Ladereihenfolge. */
    .side-meta.flex-column {
        flex-direction: row !important;
        flex-wrap: nowrap !important;
    }

    .side-meta {
        left: 0;
        right: 0;
        top: auto;
        bottom: 0;
        transform: none;
        justify-content: center;
        gap: 0.5rem;
        padding: 0.5rem 0.75rem calc(0.5rem + env(safe-area-inset-bottom));
        background: var(--fest-karte);
        backdrop-filter: blur(8px);
        border-top: 1px solid var(--fest-linie);
    }

    .side-meta-btn {
        flex: 1 1 0;
        justify-content: center;
    }

    /* Die Leiste steht mobil unten fest — der letzte Inhalt darf nicht
       darunter verschwinden. */
    footer {
        padding-bottom: 72px;
    }

    /* … und der Nach-oben-Knopf nicht darauf. Er wird mobil zudem
       kleiner: zwei schwebende Elemente in derselben Ecke sind eins
       zu viel, und die Meta-Leiste ist das wichtigere. */
    .btn-scroll-top {
        bottom: 84px !important;
        width: 40px !important;
        height: 40px !important;
        opacity: 0.85;
    }
}

/* Der Nach-oben-Knopf kam als weisser Kasten mit Markenkante — auf dem
   dunklen Fuss ein heller Fleck, im dunklen Thema erst recht. Gleiche
   Form wie die seitliche Meta-Leiste: dunkle Scheibe, Markenkante,
   weisser Pfeil. */
.btn-scroll-top {
    z-index: 1030;
    background-color: color-mix(in srgb, var(--fest-dunkel) 92%, transparent) !important;
    border: 2px solid var(--fest-primary) !important;
    border-radius: var(--fest-radius-s) !important;
    color: #fff !important;
    backdrop-filter: blur(6px);
    box-shadow: var(--fest-schatten);
    transition: background-color var(--fest-tempo) ease;
}

.btn-scroll-top:hover {
    background-color: var(--fest-primary) !important;
    color: var(--fest-on-primary) !important;
}

.btn-scroll-top svg,
.btn-scroll-top .progress-square {
    stroke: currentColor;
}

.btn-scroll-top i,
.btn-scroll-top .bi {
    color: currentColor;
}

/* --------------------------------------------------------------------------
   13. Footer
   --------------------------------------------------------------------------
   Das Template legt den gesamten Footerinhalt mit .position-absolute über
   ein Hintergrundbild. Die <footer>-Box hatte dadurch bei 390 px gemessene
   Höhe 0 und der Inhalt lief über den Seitenrand hinaus. Hier wird das
   Verhältnis umgedreht: das Bild wird zur Ebene dahinter, der Inhalt
   fliesst und bestimmt die Höhe.
   -------------------------------------------------------------------------- */

footer > .position-relative {
    position: relative;
    isolation: isolate;
    background: var(--fest-dunkel);
}

/* Erste Ebene: das Bild. */
footer > .position-relative > div:first-child {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
}

footer > .position-relative > div:first-child img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center top;
}

/* Scrim, damit die Schrift nicht vom Motiv abhängt. */
footer > .position-relative:not(:has(> div:first-child img[src]:not([src=""]))) > div:first-child {
    /* Ohne Hintergrundbild braucht es keine Bildebene — die dunkle
       Fläche der Footer-Box trägt den Text dann allein. */
    display: none;
}

footer > .position-relative > div:first-child::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        to bottom,
        color-mix(in srgb, var(--fest-dunkel) 45%, transparent),
        color-mix(in srgb, var(--fest-dunkel) 86%, transparent)
    );
}

/* Zweite Ebene: der Inhalt — jetzt im Fluss. */
footer > .position-relative > .position-absolute {
    position: relative !important;
    z-index: 1;
    inset: auto !important;
    height: auto !important;
}

footer > .position-relative > .position-absolute > .d-flex {
    height: auto !important;
}

footer .pt-md-7 {
    padding-top: var(--fest-space-l) !important;
    padding-bottom: var(--fest-space-m) !important;
}

footer,
footer p,
footer li,
footer a,
footer .h4,
footer .fw-bold {
    color: #fff;
}

footer a {
    text-decoration: none;
}

footer a:hover,
footer a:focus-visible {
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

footer .border-bottom {
    border-color: rgba(255, 255, 255, 0.28) !important;
}

/* Die Spaltenlinks im Fuss hatten keinen sichtbaren Zustand — text-reset
   nahm ihnen auch die Unterstreichung. */
footer .list-unstyled a {
    color: rgba(255, 255, 255, 0.85);
    text-decoration: none;
}

footer .list-unstyled a:hover,
footer .list-unstyled a:focus-visible {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

/* Die Spaltenueberschriften standen in .fs-4.fw-bold, also so laut wie
   die Links darunter.

   Die Auswahl greift bewusst nur <p> mit border-bottom — genau so
   stehen die Spaltenkoepfe im Template. Ein blosses ".fw-bold.fs-4"
   traf auch die Rechtslinks in der Meta-Leiste: die standen dann in
   gesperrten Versalien, und "DATENSCHUTZERKLÄRUNG" passte mobil nicht
   mehr in die Zeile. */
footer p.fw-bold.fs-4.border-bottom {
    font-size: var(--fest-step--1) !important;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(255, 255, 255, 0.62);
    margin-bottom: 0.75rem;
}

/* Die Meta-Leiste: mobil umbruchfähig, mit Trefferflächen statt
   aneinanderklebender Links. */
/* Das Theme dreht --bs-dark im dunklen Thema auf Weiss um. Im Footer,
   der in beiden Themen dunkel ist, wurden der Meta-Streifen und das
   Band mit der Copyright-Zeile dadurch zu weissen Baendern im
   schwarzen Fuss — und die Vereinszeile darin stand weiss auf weiss,
   gemessen 1,0:1. Die html-Variante braucht es, weil das Theme seine
   Umkehrung an html[data-bs-theme] haengt und damit spezifischer ist
   als ein blosses "footer .bg-dark". */
footer .bg-dark,
footer .bg-light,
html[data-bs-theme="dark"] footer .bg-dark,
html[data-bs-theme="dark"] footer .bg-light {
    --bs-bg-opacity: 1;
    /* Flaeche und Schrift zusammen und beide mit !important: das Theme
       setzt zu .bg-dark auch eine Schriftfarbe (im dunklen Thema
       #020617, also fast Schwarz). Die Flaeche allein zu drehen liess
       jeden losen Textknoten in diesen Baendern schwarz auf schwarz
       stehen — gemessen 1,13:1 an der Copyright-Zeile und am
       Farbschema-Schalter. Das Band ist in beiden Themen dunkel, seine
       Schriftfarbe ist damit keine Themenfrage. */
    background-color: var(--fest-dunkel) !important;
    color: #fff !important;
}

/* Im Template stehen die Rechtslinks als .fs-4.fw-bold — 22px fett fuer
   "Impressum" und "AGB". Das sind Pflichtangaben, keine Ansage; sie
   gehoeren in Lesegroesse. */
footer .metaMenu {
    background: color-mix(in srgb, var(--fest-dunkel) 90%, transparent);
    gap: 0 !important;
    padding-block: 0.25rem !important;
    border-top: 1px solid rgba(255, 255, 255, 0.14);
}

footer .metaMenu li {
    margin: 0 !important;
}

footer .metaMenu a {
    display: block;
    padding: 0.7rem 0.9rem;
    font-size: var(--fest-step-0) !important;
    font-weight: 600 !important;
    color: rgba(255, 255, 255, 0.88);
    border-radius: var(--fest-radius-s);
}

@media (max-width: 575.98px) {
    footer .metaMenu a {
        padding: 0.6rem 0.5rem;
        font-size: var(--fest-step--1) !important;
    }
}

footer .metaMenu a:hover,
footer .metaMenu a:focus-visible,
footer .metaMenu a.active {
    color: #fff;
    background-color: rgba(255, 255, 255, 0.08);
    text-decoration: none;
}

/* Die Copyright-Zeile stand ebenfalls in .fs-4.fw-bolder. */
footer .bg-dark .fw-bolder,
footer .bg-dark .fs-4 {
    font-size: var(--fest-step--1) !important;
    font-weight: 400 !important;
    color: rgba(255, 255, 255, 0.72);
}

footer .bg-dark .fw-bolder a {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 0.2em;
    text-decoration-color: rgba(255, 255, 255, 0.4);
}

footer .bg-dark .fw-bolder a:hover {
    text-decoration-color: currentColor;
}

/* Die runden Knoepfe am Fuss (Farbschema, Social, Einstellungen) kamen
   als .btn-light. Im dunklen Thema faerbt das Theme sie auf #334155 mit
   Symbol in #94a3b8 — 2,6:1. Auf dem dunklen Band des Fusses ist ein
   umrandeter Kreis die ehrlichere Form: gleiche Wirkung in beiden
   Themen, und das Symbol steht auf 15,6:1. */
/* Die Social-Knoepfe heissen .btn-instagram/.btn-facebook/.btn-youtube
   und setzen ihre Flaeche als background-color direkt, nicht ueber
   --bs-btn-bg — die Variable allein zu drehen hat sie deshalb nicht
   erreicht. Gemessen im hellen Thema: Symbol rgba(255,255,255,.85) auf
   #f1f5f9, also 1,05:1. Die Icons waren schlicht nicht da.

   background-color und color stehen hier deshalb direkt und mit
   !important. Alles in dieser Reihe ist dieselbe Geste — Kreis, Kante,
   Symbol — unabhaengig davon, welcher Dienst dahinter steckt. */
footer .btn-light,
footer .btn-icon,
footer [class*="btn-instagram"],
footer [class*="btn-facebook"],
footer [class*="btn-youtube"],
footer [class*="btn-twitter"],
footer [class*="btn-linkedin"],
footer [class*="btn-tiktok"],
html[data-bs-theme="dark"] footer .btn-light {
    background-color: transparent !important;
    border-color: rgba(255, 255, 255, 0.4) !important;
    color: #fff !important;
    --bs-btn-color: #fff;
    --bs-btn-bg: transparent;
    --bs-btn-border-color: rgba(255, 255, 255, 0.4);
    --bs-btn-hover-color: var(--fest-on-primary);
    --bs-btn-hover-bg: var(--fest-primary);
    --bs-btn-hover-border-color: var(--fest-primary);
    --bs-btn-active-color: var(--fest-on-primary);
    --bs-btn-active-bg: var(--fest-primary);
    --bs-btn-active-border-color: var(--fest-primary);
    width: 44px;
    height: 44px;
    min-height: 44px;
    padding: 0 !important;
    justify-content: center;
    border-width: 2px;
}

footer .btn-light i,
footer .btn-icon i,
footer .btn-icon svg {
    color: inherit !important;
    fill: currentColor;
    opacity: 1;
}

footer .btn-light:hover,
footer .btn-icon:hover,
footer .btn-light:focus-visible,
footer .btn-icon:focus-visible {
    background-color: var(--fest-primary) !important;
    border-color: var(--fest-primary) !important;
    color: var(--fest-on-primary) !important;
    transform: none;
    box-shadow: none;
}

/* Der Dropdown des Farbschema-Schalters stand auf der Themenflaeche des
   Fusses und war dort je nach Thema hell auf hell. */
footer .dropdown-menu {
    --bs-dropdown-bg: var(--fest-flaeche);
    --bs-dropdown-color: var(--fest-ink);
    --bs-dropdown-link-color: var(--fest-ink);
    --bs-dropdown-link-hover-bg: var(--fest-flaeche-leise);
    --bs-dropdown-link-hover-color: var(--fest-ink);
    --bs-dropdown-link-active-bg: var(--fest-primary);
    --bs-dropdown-link-active-color: var(--fest-on-primary);
    border: 2px solid var(--fest-linie);
    border-radius: var(--fest-radius-s);
    box-shadow: var(--fest-schatten-hoch);
}

/* mix-blend-mode: difference auf dem Footertext war ein Ratespiel: über
   hellen Bildstellen wurde die Schrift schwarz, über dunklen weiss, und
   an den Übergängen unlesbar. Der Scrim oben ersetzt das. */
footer .footer-contrast-text,
footer .footer-contrast-text a,
footer .footer-contrast-text * {
    color: #fff;
    mix-blend-mode: normal;
}

/* --------------------------------------------------------------------------
   14. Einwilligungsbanner (Klaro) und Platzhalter für externe Inhalte
   --------------------------------------------------------------------------
   Das Banner stand als volle Fläche am unteren Rand, deckte auf 390 px
   ein Drittel des Bildschirms ab und setzte den Sekundär-Knopf in der
   Markenfarbe auf die Markenfarbe.
   -------------------------------------------------------------------------- */

/* Klaro.new.css setzt .cn-body selbst auf position:fixed mit
   right:80px. Der umgebende .cookie-notice fällt dadurch auf 8 px
   Höhe zusammen — er ist nicht der sichtbare Kasten, .cn-body ist es.
   Formatiert wird deshalb .cn-body. Klaro.new.css bleibt unangetastet,
   sie gilt auch für orgadiy. */
.klaro .cn-body {
    right: 1rem !important;
    left: auto !important;
    bottom: 1rem !important;
    width: min(calc(100vw - 2rem), 400px) !important;
    max-width: none !important;
    padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom)) !important;
    border-radius: var(--fest-radius) !important;
    box-shadow: var(--fest-schatten-hoch) !important;
    /* Die Markenfläche trägt den Text je Festival unterschiedlich gut.
       Der Kasten steht deshalb dunkel, mit der Marke als Kante. */
    background-color: color-mix(in srgb, var(--fest-dunkel) 97%, transparent) !important;
    border: 2px solid var(--fest-primary);
    color: #fff !important;
}

.klaro .cn-body p,
.klaro .cn-body span {
    color: #fff !important;
    font-size: var(--fest-step--1);
    line-height: 1.5;
    margin-bottom: 0.5rem !important;
}

/* "Einstellungen öffnen" stand in --bs-secondary auf --bs-primary:
   bei Wasted #e74f0c auf #0aa8b0 = 1,9:1. Weiss unterstrichen = 15,6:1
   und bleibt als Link erkennbar, weil es unterstrichen ist. */
.klaro .cn-body .cn-learn-more,
.klaro .cn-body a {
    color: #fff !important;
    text-decoration: underline !important;
    text-underline-offset: 0.2em;
    font-size: var(--fest-step--1);
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

.klaro .cn-body .cn-ok {
    display: flex !important;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    margin-top: 0 !important;
}

.klaro .cn-body .cm-btn {
    font-size: var(--fest-step--1) !important;
    margin: 0 !important;
}

.klaro .cn-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-left: auto;
}

.klaro .cm-btn,
.klaro .cn-buttons button {
    min-height: 44px;
    padding-inline: 1rem !important;
    border-radius: var(--fest-radius-s) !important;
    font-weight: 700;
}

/* Der Platzhalter statt eines geblockten Videos: bisher Markenfarbe auf
   Markenfarbe im selben Ton. Jetzt eine ruhige Fläche mit klarer Kante,
   die Knöpfe tragen die Marke. */
.festival-theme .klaro .context-notice {
    background-color: var(--fest-flaeche-leise) !important;
    border: 2px dashed var(--fest-linie) !important;
    border-radius: var(--fest-radius) !important;
    color: var(--fest-ink) !important;
    padding: var(--fest-space-m) var(--fest-space-s) !important;
}

.festival-theme .klaro .context-notice p {
    color: var(--fest-ink) !important;
    font-size: var(--fest-step-0);
    max-width: 44ch;
    margin-inline: auto;
}

.festival-theme .klaro .context-notice .cm-buttons {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/* Der Platzhalter für geblockte Einbettungen bekam vom Theme die
   Marken-Subtile-Farbe und darauf Text in derselben Familie. */
.festival-theme .video-wrapper,
.festival-theme .google-maps-wrapper {
    border-radius: var(--fest-radius);
    overflow: hidden;
}

.google-maps-wrapper {
    display: block;
    position: relative;
    width: 100%;
    height: clamp(280px, 45vh, 520px);
}

.google-maps-wrapper iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

/* Das pretix-Widget haengt seine Herkunftszeile selbst per JavaScript
   an und faerbt sie #44a3a9 — auf Weiss 2,98:1. Nur die Schriftfarbe
   wird angehoben, alles andere am Widget bleibt wie es ist. */
.festival-theme .pretix-widget-attribution,
.festival-theme .pretix-widget-attribution a {
    color: var(--fest-ink-muted) !important;
}

/* --------------------------------------------------------------------------
   14a. Mitfahrgelegenheiten (clubs_fe_lift)
   --------------------------------------------------------------------------
   Der Block rendert zwei Listen (Suchanfragen, Angebote), jede in einem
   eigenen .container. Zwei Dinge waren kaputt:

   Erstens lagen die beiden .container ineinander verschachtelt im
   .container des Partials — doppelte Polsterung.

   Zweitens war der Leerzustand eine h2 in Plakatschrift. Das ist in den
   Templates behoben (Lift/Tableview.html und Freunde); hier bekommt er
   die Form, die zu einer Nachricht passt.

   Beide Listen standen ausserdem einfach untereinander: Ueberschrift,
   Leertext oder Tabelle, Knopf — zweimal hintereinander, ohne Rahmen,
   ohne Flaeche. Sie bleiben untereinander (eine Tabelle mit sechs
   Spalten braucht die volle Breite, nebeneinander wird sie unlesbar),
   bekommen aber je eine Karte: Rahmen, Radius, Schatten, dieselbe Form
   wie die Bandkarten in Abschnitt 9. Kein Hover-Effekt — die Karte ist
   hier eine Flaeche, kein Link.
   -------------------------------------------------------------------------- */

/* Nur die Verschachtelung aufloesen (Lift/Both.html legt einen
   .container um zwei weitere), nicht den aeusseren Container selbst —
   der traegt die Seitenachse. */
.festival-theme .frame-type-clubs_fe_lift .container .container {
    padding-inline: 0;
    max-width: none;
}

/* Die zwei .container im Partial bringen ihre eigene Bootstrap-Achse mit
   (Zentrierung, kleiner Gutter je Breakpoint) — die Karten unten geben
   das ab (max-width: none), sonst sitzt der Kartenrahmen innerhalb der
   Bootstrap-Breite statt auf der Seitenachse. Ohne Ersatz laeuft er dann
   von Kante zu Kante: die Ausnahme in Abschnitt "Rahmen"
   ("> .frame:not(:has(.container))") greift hier nicht, weil der Rahmen
   ja .container-Kinder hat. Dieselbe Achse wie ueberall sonst (1320px,
   --fest-space-s Gutter) kommt also hier, auf dem Wrapper. */
.festival-theme .frame-type-clubs_fe_lift .tx-orgadiy {
    display: grid;
    gap: var(--fest-space-m);
    max-width: 1320px;
    margin-inline: auto;
    padding-inline: var(--fest-space-s);
}

.festival-theme .frame-type-clubs_fe_lift .tx-orgadiy > .container {
    max-width: none;
    margin: 0;
    /* Ohne das sprengt die Tabelle die Karte: ein Grid-Kind hat
       min-width: auto und waechst mit seinem breitesten Inhalt, die
       Seite scrollt seitlich — und DataTables Responsive sieht dadurch
       genug Platz und klappt keine einzige Spalte ein. */
    min-width: 0;
    padding-inline: var(--fest-space-m);
    /* Abschnitt 5 nimmt jedem .tx-orgadiy > .container mit
       padding-block: 0 !important den vertikalen Abstand weg (dort
       verhindert das doppeltes py-5). Diese Karte braucht ihn aber
       wirklich — deshalb hier gezielt zurueckerobert, nicht die
       generelle Regel aufgeweicht. */
    padding-block: var(--fest-space-m) !important;
    background: var(--fest-flaeche);
    border: 1px solid var(--fest-linie);
    border-radius: var(--fest-radius);
    box-shadow: var(--fest-schatten);
}

.festival-theme .lift-titel {
    font-size: var(--fest-step-2) !important;
    font-family: var(--fest-font-text) !important;
    font-weight: 700 !important;
    letter-spacing: -0.01em !important;
    margin-bottom: var(--fest-space-s);
}

.festival-theme .lift-leer {
    max-width: var(--fest-mass);
    margin-bottom: var(--fest-space-m);
    padding: var(--fest-space-s) var(--fest-space-s) var(--fest-space-s)
             calc(var(--fest-space-s) - 4px);
    border-left: 4px solid var(--fest-linie);
    color: var(--fest-ink-muted);
}

/* Die Tabellen im Block bekamen vom Theme keine eigene Form. */
.festival-theme .frame-type-clubs_fe_lift table {
    --bs-table-color: var(--fest-ink);
    --bs-table-bg: transparent;
    --bs-table-border-color: var(--fest-linie);
    --bs-table-hover-color: var(--fest-ink);
    --bs-table-hover-bg: var(--fest-flaeche-leise);
    width: 100%;
}

.festival-theme .frame-type-clubs_fe_lift th {
    font-size: var(--fest-step--1);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--fest-ink-muted);
    font-weight: 700;
}

.festival-theme .frame-type-clubs_fe_lift td {
    vertical-align: middle;
}

/* ----------------------------------------------------------------------
   DataTables auf der oeffentlichen Seite.

   Die Listen im Backstage stehen in einem Admin-Theme, das die Farben
   von DataTables mitbringt. Hier steht dieselbe Tabelle zwischen
   Markenfarben — ohne diese Zuordnung sitzt ein blauer Blaetter-Knopf
   zwischen tuerkisen. Bootstrap liefert die Paginierung ueber
   --bs-pagination-*, also wird hier die Variable gesetzt und nicht die
   Regel von DataTables ueberschrieben.
   ---------------------------------------------------------------------- */
.festival-theme .frame-type-clubs_fe_lift .pagination {
    --bs-pagination-color: var(--fest-primary-ink);
    --bs-pagination-bg: var(--fest-flaeche);
    --bs-pagination-border-color: var(--fest-linie);
    --bs-pagination-hover-color: var(--fest-primary-ink);
    --bs-pagination-hover-bg: var(--fest-flaeche-leise);
    --bs-pagination-hover-border-color: var(--fest-linie);
    --bs-pagination-focus-color: var(--fest-primary-ink);
    --bs-pagination-focus-bg: var(--fest-flaeche-leise);
    --bs-pagination-active-color: var(--fest-on-primary);
    --bs-pagination-active-bg: var(--fest-primary);
    --bs-pagination-active-border-color: var(--fest-primary);
    --bs-pagination-disabled-color: var(--fest-ink-muted);
    --bs-pagination-disabled-bg: var(--fest-flaeche);
    --bs-pagination-disabled-border-color: var(--fest-linie);
    flex-wrap: wrap;
    margin-bottom: 0;
}

/* Die Laengenauswahl steht in einer Zeile mit ihrer Beschriftung und
   braucht deshalb nur ihre eigene Breite, nicht die volle. (Dass der
   Pfeil auf der Zahl stand, lag nicht daran — das ist in Abschnitt 15
   am Auswahlfeld selbst behoben.) */
.festival-theme .frame-type-clubs_fe_lift .dt-length .form-select {
    width: auto;
    display: inline-block;
}

.festival-theme .frame-type-clubs_fe_lift .dt-info {
    font-size: var(--fest-step--1);
    color: var(--fest-ink-muted);
}

/* ----------------------------------------------------------------------
   Die Formulare des Plugins (Suche anlegen, Angebot anlegen, Kontakt
   aufnehmen) sind Formulare wie die aus EXT:form auf derselben Seite und
   bekommen deshalb dieselbe Spalte. Ohne das liefen ihre Felder ueber
   die volle Rahmenbreite, waehrend das Shuttle-Formular direkt darunter
   bei seinem Mass endete — zwei Formulare auf einer Seite, zwei Breiten.
   (Abschnitt 15 begruendet Mass und Zentrierung.)
   ---------------------------------------------------------------------- */
.festival-theme .frame-type-clubs_fe_lift form {
    max-width: 48rem;
    margin-inline: auto;
}

/* Die Felderpaare (Stadt/Festival, Wann/Personen) brauchen denselben
   Abstand zueinander wie die Rasterzeilen im EXT:form-Formular. */
.festival-theme .frame-type-clubs_fe_lift form .row {
    --bs-gutter-x: var(--fest-space-s);
}

.festival-theme .frame-type-clubs_fe_lift altcha-widget {
    display: block;
    margin: var(--fest-space-s) 0;
    --altcha-border-width: 2px;
    --altcha-border-radius: var(--fest-radius-s);
    --altcha-color-border: var(--fest-linie);
    --altcha-color-text: var(--fest-ink);
    --altcha-color-base: var(--fest-flaeche);
}

/* --------------------------------------------------------------------------
   15. Formulare (EXT:form)
   --------------------------------------------------------------------------
   Die Formularseiten (/kontakt, /feedback, /helfer-werden, /mitfahren)
   waren bis eben komplett leer — nicht ungestaltet, sondern leer: die
   TypoScript-Einbindung zeigte noch auf EXT:form/Configuration/Yaml/
   FormSetup.yaml, die es seit TYPO3 14 nicht mehr gibt, und jede dieser
   Seiten brach mit einer ParseErrorException ab. Behoben in
   block/…/TypoScript/page.typoscript.

   Ein Formular ist eine Abfolge, keine Flaeche: eine Spalte, ein
   Feld unter dem anderen, Beschriftung direkt ueber dem Feld. Breite
   Felder sehen grosszuegig aus und sind trotzdem schlechter — bei
   1400px Feldbreite findet das Auge den Anfang der naechsten Zeile
   nicht wieder.

   Die Spalte stand zuerst bei 38rem und linksbuendig im 1320px-Rahmen.
   Gemessen auf /mitfahren: 608px Formular, 656px leer daneben — die
   Seite las sich dadurch als Zweispalter, dessen rechte Spalte fehlt.
   48rem und mittig loest beides: die Zeile bleibt kurz genug zum
   Bedienen, und der freie Raum verteilt sich auf beide Seiten, statt
   als Loch rechts zu stehen.
   -------------------------------------------------------------------------- */

.festival-theme .frame-type-form_formframework form {
    max-width: 48rem;
    margin-inline: auto;
}

/* Der Titel des Formulars steht als h2 im Formular selbst. */
.festival-theme .frame-type-form_formframework form > h2 {
    margin-bottom: var(--fest-space-m);
}

.festival-theme .form-element,
.frame-type-form_formframework .form-group,
.frame-type-form_formframework .clearfix {
    margin-bottom: var(--fest-space-s);
}

/* ----------------------------------------------------------------------
   Zwischentexte im Formular (Static Text).

   Auf /mitfahren stehen vier davon zwischen den Feldern: "Shuttle",
   "Buchungszeitraum Busshuttle", "Busshuttle", "Barrierefreies
   Shuttle". Jeder bringt eine h2 mit — also viermal Plakatschrift in
   derselben Groesse wie der Seitentitel, direkt aneinander, ohne dass
   man sah, wo ein Abschnitt anfaengt. Sie sind Zwischenueberschriften
   eines Formulars, nicht Titel einer Seite.
   ---------------------------------------------------------------------- */
.festival-theme .form-element-statictext {
    margin-top: var(--fest-space-m);
    margin-bottom: var(--fest-space-s) !important;
    padding-top: var(--fest-space-s);
    border-top: 2px solid var(--fest-linie);
}

.festival-theme .form-element-statictext:first-of-type {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

.festival-theme .form-element-statictext h1,
.festival-theme .form-element-statictext h2,
.festival-theme .form-element-statictext h3 {
    font-family: var(--fest-font-text) !important;
    font-size: var(--fest-step-1) !important;
    font-weight: 700 !important;
    letter-spacing: -0.01em !important;
    line-height: 1.25 !important;
    margin-bottom: 0.35rem;
}

/* Der Text darunter kommt als Klartext mit echten Zeilenumbruechen —
   die Busshuttle-Zeiten standen dadurch als eine einzige Zeile
   aneinandergereiht ("… 16:00 Busshuttle Freitag, 12.06.26 - 16:00 …").
   pre-line behaelt die Umbrueche und laesst den Rest normal umbrechen. */
.festival-theme .form-element-statictext > div {
    white-space: pre-line;
    max-width: var(--fest-mass);
    color: var(--fest-ink);
}

/* Zwischentexte mit eigenem Textkoerper (Buchungszeitraum, der
   Busshuttle-Absatz, Barrierefreies Shuttle) bekommen eine Flaeche statt
   nur der duennen Trennlinie oben — die Seite bestand sonst aus reinem
   Fliesstext zwischen Formularfeldern und wirkte wie Rohmaterial statt
   wie ein fertiges Angebot. Der reine Gruppentitel ("Shuttle", ohne
   eigenen Text) bleibt eine Ueberschrift ohne Flaeche — ein Kartenrahmen
   um eine einzelne Zeile haette nichts zu umfassen. */
.festival-theme .form-element-statictext:has(> div) {
    padding: var(--fest-space-s) var(--fest-space-m);
    background: var(--fest-flaeche-leise);
    border-top: 0;
    border-radius: var(--fest-radius-s);
}

.festival-theme .form-element-statictext:has(> div) h1,
.festival-theme .form-element-statictext:has(> div) h2,
.festival-theme .form-element-statictext:has(> div) h3 {
    margin-top: 0;
}

/* Nebeneinanderstehende Felder brauchen einen Abstand zueinander. */
.festival-theme .form-element-gridrow.row {
    --bs-gutter-x: var(--fest-space-s);
    margin-bottom: 0;
}

.festival-theme .form-element-gridrow .form-element:last-child {
    margin-bottom: var(--fest-space-s);
}

.festival-theme .form-label {
    display: block;
    margin-bottom: 0.4rem;
    font-weight: 700;
    color: var(--fest-ink);
}

/* Das Sternchen fuer Pflichtfelder stand in der Farbe der Beschriftung
   und war damit kein Hinweis, sondern Dekoration. */
.festival-theme .form-label .required {
    color: var(--fest-primary-ink);
    font-weight: 700;
    margin-left: 0.15em;
}

.frame-type-form_formframework .row {
    row-gap: var(--fest-space-s);
}

/* iOS zoomt beim Fokus in jedes Feld unter 16px hinein — deshalb hier
   eine feste Zahl und keine Stufe aus der Leiter. */
.festival-theme .form-control,
.festival-theme .form-select {
    font-family: var(--fest-font-text);
    font-size: 16px;
    min-height: 48px;
    padding: 0.6rem 0.85rem;
    border-radius: var(--fest-radius-s);
    border: 2px solid var(--fest-linie);
    background-color: var(--fest-flaeche);
    color: var(--fest-ink);
    transition: border-color var(--fest-tempo) ease,
                box-shadow var(--fest-tempo) ease;
}

/* Der Pfeil eines Auswahlfeldes ist ein Hintergrundbild: er weicht dem
   Text nicht aus, der Platz dafuer muss als Innenabstand freigehalten
   werden. Die Zeile darueber setzt padding aber als Kurzform und nahm
   Bootstrap damit genau diesen Abstand weg — im Galerie-Plugin lag der
   Pfeil dadurch mitten auf "Wasted_2026", in der Laengenauswahl der
   Tabellen auf der Zahl. background-position kommt von Bootstrap
   (rechts 0,75rem), 2,5rem Abstand laesst den Text davor enden. */
.festival-theme .form-select {
    padding-right: 2.5rem;
}

.festival-theme .form-control::placeholder {
    color: var(--fest-ink-muted);
    opacity: 1;
}

.festival-theme .form-control:hover,
.festival-theme .form-select:hover {
    border-color: var(--fest-ink-muted);
}

.festival-theme .form-control:focus,
.festival-theme .form-select:focus {
    border-color: var(--fest-primary);
    box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.3);
    outline: none;
}

.festival-theme textarea.form-control {
    min-height: 9rem;
    line-height: 1.6;
    resize: vertical;
}

.festival-theme textarea.form-control.xxlarge {
    min-height: 13rem;
}

/* ----------------------------------------------------------------------
   Ankreuzfelder stehen auf derselben Kante wie die Eingabefelder.

   Das Kaestchen haengt per negativem Aussenabstand in den Innenabstand
   seiner Zeile hinein — nur so sitzt es auf der Kante und der Text
   laeuft mehrzeilig nicht darunter. Dafuer muessen beide Werte
   zusammenpassen, und genau das war zweimal nicht der Fall:

   EXT:form umschliesst sein Ankreuzfeld nicht mit .form-check, sondern
   mit .form-check-wrapping-label. Der negative Aussenabstand griff also,
   der ausgleichende Innenabstand nicht — auf /mitfahren stand das
   Kaestchen dadurch 30px links neben allen Feldern.

   Im Plugin-Formular wiederum gewann Bootstraps eigenes
   .form-check .form-check-input (zwei Klassen, spaeter geladen) gegen
   die einklassige Regel hier — der Ausgleich war dort 24px statt 30,4px,
   das Kaestchen sass 6px zu weit rechts. Deshalb stehen beide Regeln
   jetzt zweiklassig.
   ---------------------------------------------------------------------- */
.festival-theme .form-check,
.festival-theme .form-check-wrapping-label {
    padding-left: 1.9rem;
    min-height: 1.75rem;
}

.festival-theme .form-check .form-check-input,
.festival-theme .form-check-wrapping-label .form-check-input {
    width: 1.35em;
    height: 1.35em;
    margin-top: 0.15em;
    margin-left: -1.9rem;
    border: 2px solid var(--fest-linie);
}

.festival-theme .form-check-input:checked {
    background-color: var(--fest-primary);
    border-color: var(--fest-primary);
}

.festival-theme .form-check-label {
    color: var(--fest-ink);
}

/* Fehler: Farbe allein reicht nicht. Die Meldung steht als Text unter
   dem Feld, das Feld bekommt zusaetzlich eine kraeftigere Kante. */
.festival-theme .form-control.error,
.festival-theme .has-error .form-control,
.festival-theme .form-control[aria-invalid="true"] {
    border-color: var(--bs-danger, #b02a37);
    background-color: color-mix(in srgb, var(--bs-danger, #b02a37) 6%, var(--fest-flaeche));
}

.festival-theme .help-block,
.festival-theme .form-element .error {
    display: block;
    margin-top: 0.4rem;
    font-size: var(--fest-step--1);
    font-weight: 700;
    color: var(--bs-danger-text-emphasis, #842029);
}

html[data-bs-theme="dark"] .festival-theme .help-block,
html[data-bs-theme="dark"] .festival-theme .form-element .error {
    color: #ff9a9a;
}

/* Der ALTCHA-Kasten bringt eigene Masse mit und stand als schmales
   Kaestchen neben einem 1400px breiten Feld. */
.frame-type-form_formframework altcha-widget {
    display: block;
    max-width: 100%;
    margin: var(--fest-space-s) 0;
    --altcha-border-width: 2px;
    --altcha-border-radius: var(--fest-radius-s);
    --altcha-color-border: var(--fest-linie);
    --altcha-color-text: var(--fest-ink);
    --altcha-color-base: var(--fest-flaeche);
}

.frame-type-form_formframework .actions {
    margin-top: var(--fest-space-m);
}

/* Auf dem Telefon geht der Absendeknopf ueber die volle Breite — er ist
   das Ende der Abfolge und soll nicht gesucht werden muessen. */
@media (max-width: 575.98px) {
    .frame-type-form_formframework .actions .btn {
        width: 100%;
    }
}

/* Bestaetigung nach dem Absenden. */
.festival-theme .frame-type-form_formframework .actions + *,
.festival-theme .form-element-static-text {
    max-width: var(--fest-mass);
}

/* --------------------------------------------------------------------------
   16. Logos in Navigation, Plugins und Footer
   --------------------------------------------------------------------------
   Diese Logik lag wortgleich in allen drei Site-CSS. Sie entscheidet, wo
   die helle und wo die dunkle Fassung der Wortmarke steht und wo invert()
   nötig ist.
   -------------------------------------------------------------------------- */

.logo-light {
    display: none !important;
}

.logo-dark {
    display: block !important;
    filter: invert(1);
}

.navbar-logo-wrapper .logo-light {
    display: block !important;
    width: auto;
}

.navbar-logo-wrapper .logo-dark {
    display: none !important;
}

/* Welche Fassung in der Leiste steht, haengt davon ab, was die Site
   liefert. Drei Faelle, alle drei kommen im Bestand vor:

   1. Kopfbild vorhanden (wasted-open-air) — helle Fassung auf dem
      Scrim, Menuepunkte weiss. Das ist der Standard weiter oben.
   2. Kein Kopfbild, aber eine dunkle Fassung mit Datei (nbgpunkfest) —
      weisse Leiste, dunkle Wortmarke ohne invert().
   3. Kein Kopfbild und keine dunkle Fassung (weinturm: logo-dark hat
      src="") — dann bleibt nur die helle, weisse Fassung. Damit sie
      nicht weiss auf weiss steht, wird die Leiste selbst dunkel.

   Das kommt ohne eine einzige Angabe je Site aus: die Leiste sieht
   nach, was da ist. Ein neues Festival braucht dafuer nichts. */

/* Fall 2 */
.navbar:not(:has(> .position-absolute img[src]:not([src=""])))
    :is(.navbar-logo-wrapper):has(.logo-dark[src]:not([src=""])) .logo-light {
    display: none !important;
}

.navbar:not(:has(> .position-absolute img[src]:not([src=""])))
    :is(.navbar-logo-wrapper):has(.logo-dark[src]:not([src=""])) .logo-dark {
    display: block !important;
    filter: none !important;
}

/* Im dunklen Thema kippt die Leiste auf die dunkle Fläche — dann ist
   die dunkle Wortmarke aus Fall 2 genauso unsichtbar, wie es die helle
   im hellen Thema war. Also zurück zur hellen Fassung. */
html[data-bs-theme="dark"] .navbar:not(:has(> .position-absolute img[src]:not([src=""])))
    :is(.navbar-logo-wrapper):has(.logo-dark[src]:not([src=""])) .logo-light {
    display: block !important;
}

html[data-bs-theme="dark"] .navbar:not(:has(> .position-absolute img[src]:not([src=""])))
    :is(.navbar-logo-wrapper):has(.logo-dark[src]:not([src=""])) .logo-dark {
    display: none !important;
}

/* Fall 3 */
.navbar:not(:has(> .position-absolute img[src]:not([src=""]))):not(
    :has(.navbar-logo-wrapper .logo-dark[src]:not([src=""]))
) {
    /* --fest-dunkel, nicht --fest-ink: im dunklen Thema ist --fest-ink
       fast weiss, und die Leiste soll hier in beiden Themen dunkel
       bleiben — die weisse Wortmarke ist der Grund für den ganzen Fall. */
    background-color: var(--fest-dunkel);
}

.navbar:not(:has(> .position-absolute img[src]:not([src=""]))):not(
    :has(.navbar-logo-wrapper .logo-dark[src]:not([src=""]))
) .navbar-nav .nav-item .nav-link {
    color: #fff;
}

.navbar:not(:has(> .position-absolute img[src]:not([src=""]))):not(
    :has(.navbar-logo-wrapper .logo-dark[src]:not([src=""]))
) .navbar-toggler {
    border-color: rgba(255, 255, 255, 0.85);
}

.plugin-logo-no-invert .logo-light {
    display: none !important;
}

.plugin-logo-no-invert .logo-dark {
    display: block !important;
    filter: none !important;
}

.footer-logo-invert .logo-light {
    display: none !important;
}

.footer-logo-invert .logo-dark {
    display: block !important;
    filter: invert(1);
}

/* Die Wortmarke im Footer lief ungebremst auf Containerbreite — bei
   nbgpunkfest 1350 px hoch wie breit. Sie ist eine Signatur, kein Hero. */
footer .logo-dark,
footer .logo-light {
    max-height: clamp(80px, 14vw, 160px);
    width: auto;
    margin-inline: auto;
}

/* Im dunklen Systemschema bleibt in der Leiste die farbige Fassung. */
@media (prefers-color-scheme: dark) {
    .navbar-logo-wrapper .logo-light {
        display: block !important;
        width: auto;
        filter: none !important;
    }

    .navbar-logo-wrapper .logo-dark {
        display: none !important;
    }

    .navbar-logo-wrapper,
    .navbar-logo-wrapper .navbar-brand {
        filter: none !important;
    }
}

[data-bs-theme="dark"] .navbar-logo-wrapper .logo-light,
html[data-bs-theme="dark"] .navbar-logo-wrapper .logo-light {
    filter: none !important;
    opacity: 1 !important;
}

[data-bs-theme="dark"] .navbar-logo-wrapper,
[data-bs-theme="dark"] .navbar-logo-wrapper .navbar-brand {
    filter: none !important;
}

#pills-tabTwoContent .logo-dark,
.tab-pane .logo-dark {
    filter: none !important;
}

[data-bs-theme="dark"] #pills-tabTwoContent .logo-dark,
html[data-bs-theme="dark"] #pills-tabTwoContent .logo-dark {
    filter: invert(1) !important;
}

/* Wortmarken im Inhalt (Logo-Block auf der Bands-Seite, Footer) liefen
   bisher ungebremst auf Containerbreite — auf 1440 px war die Marke
   1400 px breit und füllte den ganzen ersten Bildschirm. Eine Wortmarke
   ist eine Signatur, kein Bühnenbild. */
.festival-theme .logo-light,
.festival-theme .logo-dark {
    max-height: clamp(72px, 11vw, 150px);
    width: auto;
}

/* Partnerlogos-Raster. Bewusst ohne grayscale-Filter: Foerderlogos wie
   das des Staatsministeriums duerfen nicht umgefaerbt dargestellt
   werden, und ein Sponsorenlogo in Grau ist auch sonst keine hoefliche
   Geste. Vereinheitlicht wird nur die Hoehe. */
.logo-grid-image {
    max-height: 72px;
    width: auto;
    object-fit: contain;
}

.logo-grid-item {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--fest-space-2xs);
}

/* --------------------------------------------------------------------------
   17. Navigationsebenen
   --------------------------------------------------------------------------
   Die Dropdowns verschwanden hinter nachfolgenden Sektionen.
   -------------------------------------------------------------------------- */

.navbar .container {
    position: relative;
    z-index: 1;
    overflow: visible;
}

.navbar .dropdown-menu {
    position: absolute;
    z-index: 1045;
    border-radius: var(--fest-radius-s);
    border: 1px solid var(--fest-linie);
    box-shadow: var(--fest-schatten-hoch);
}
