/* ==========================================================================
  lenziloeffler.de — Base Stylesheet
   Plain CSS, kein Framework. Aufbau:
   1. Font
   2. Design-Tokens
   3. Reset / Basis
   4. Intro-Animation (portiert aus dem Aerial-Template)
   5. Layout (Seite, Hero, Footer)
   6. Icon-Buttons
   7. Panels — Basis + Contact
   8. About-Me-Overlay
   9. Subseiten (Impressum / Datenschutz)
   10. Responsive
   ========================================================================== */

/* 1. Font ----------------------------------------------------------------- */

/* Ersatzschrift mit erzwungenen Metriken. Die Seite leitet zwei Maße aus der
   Schrift ab — die Versalhöhe (--cap-height, bestimmt die Logohöhe) und den
   Anteil des Zeilenkastens unter der Grundlinie (--claim-descent, verankert
   das Contact-Panel). Ohne diese Überschreibungen stimmten beide nicht mehr,
   solange Saira noch lädt oder gar nicht geladen werden kann.

   ascent/descent auf Sairas Werte: die Grundlinie liegt bei line-height: 1
   genau 84,8 % unter der Kastenoberkante. size-adjust gleicht die Versalhöhe
   an (Saira 0,688em, Helvetica/Arial 0,716em → 96,1 %). */
@font-face {
  font-family: "Saira Fallback";
  src: local("Helvetica Neue"), local("Helvetica"), local("Arial"),
       local("Segoe UI"), local("Roboto"), local("Liberation Sans"),
       local("DejaVu Sans");
  ascent-override: 84.8%;
  descent-override: 15.2%;
  line-gap-override: 0%;
  size-adjust: 96.1%;
}

@font-face {
  font-family: "Saira";
  /* Variable Font: eine Datei deckt 200–800 ab.
     Pfad ist relativ zu dieser CSS-Datei (assets/css/). */
  src: url("../fonts/Saira/Saira-VariableFont_wdth,wght.ttf") format("truetype");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

/* 2. Design-Tokens -------------------------------------------------------- */

:root {
  /* Farben — ausschließlich diese drei Werte, keine abgeschwächten Varianten
     für Text oder Linien.

       --color-dark    Grund aller Seiten und aller Pop-ups. Zugleich die
                       Glyphenfarbe im Icon-Button beim Hover, wo sich das
                       Verhältnis umkehrt.
       --color-orange  Text und Icons auf der Startseite.
       --color-light   Text auf den Rechtsseiten und in allen Pop-ups,
                       Wortmarke, Trennlinie, Zurück-Pfeil.
       --color-ruby    Der erste der beiden Querbalken der Wortmarke. Trägt
                       im Racing-Pop-up die äußere Linie des Streckenverlaufs
                       und die erste Spalte der Countdown-Kacheln — der
                       Balkenwechsel Rubinrot/Orange der Wortmarke findet
                       sich dort wieder.                                     */
  --color-dark: #0A1F33;
  --color-orange: #FC4615;
  --color-light: #E2E2E2;
  --color-ruby: #770B23;

  /* Typo -----------------------------------------------------------------
     Saira, lokal ausgeliefert. Der Fallback bekommt weiter unten dieselben
     senkrechten Metriken aufgezwungen (siehe @font-face), damit die aus der
     Schrift abgeleiteten Maße auch ohne sie stimmen. */
  --font-sans: "Saira", "Saira Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Nur zwei Stärken auf der ganzen Seite. Fett steht ausschließlich auf den
     Titeln und Abschnittsüberschriften der Rechtsseiten sowie auf den
     Kapitelüberschriften in den Pop-ups. */
  --weight-regular: 400;
  --weight-bold: 700;

  /* Laufweite nach Schriftgrad: je größer der Grad, desto enger. */
  --tracking-display: -0.03em;   /* Hero-Claim, 96px          */
  --tracking-title: -0.02em;     /* Seitentitel, 30px         */
  --tracking-subhead: -0.01em;   /* 17–28px                   */
  --tracking-small: 0.01em;      /* Bildunterschrift, 12px    */

  /* Zeilenhöhe nach Funktion. */
  --lh-display: 1;      /* Hero-Claim                                    */
  --lh-title: 1.15;     /* Seitentitel der Rechtsseiten                  */
  --lh-tight: 1.2;      /* Tagline, „Follow Lenzi", Kapitelüberschrift   */
  --lh-ui: 1.4;         /* Fußnote, Bildunterschrift, Rechtsseitentext   */
  --lh-read: 1.5;       /* Fließtext in den Pop-ups                      */
  --lh-base: 1.6;       /* Grundwert für alles ohne eigene Angabe        */

  /* Lesegrößen. Zwei Werte, mehr braucht die Seite nicht. */
  --text-body: 1rem;          /* Rechtsseiten                  */
  --text-body-compact: 0.9375rem; /* Pop-ups; ab 820px 15 -> 14px */

  /* Grad der Kapitelüberschriften in den Pop-ups. Steht als Token, weil ihn
     zwei Stellen teilen: die Überschriften selbst („Standings & Results",
     „Building Towards 2027") und der Ortsname im Countdown („BRNO"). */
  --text-story-title: clamp(1.0625rem, 1.6vw, 1.25rem);

  /* Hero-Skala: 96px auf Desktop, skaliert proportional nach unten.
     Alle vertikalen Abstände im Hero leiten sich hiervon ab, damit das
     kalibrierte Verhältnis (30px / 60px bei 96px Hero) erhalten bleibt. */
  --hero-size: clamp(2.75rem, 11.5vw, 6rem);          /* max 96px */
  --tagline-size: clamp(1.0625rem, 3.35vw, 1.75rem);  /* max 28px */
  --tagline-line: calc(var(--tagline-size) * var(--lh-tight));

  /* DIE Abstandseinheit der Seite („1u"). Gemessen wird von Elementkante zu
     Elementkante — 30px bei 96px Hero. Alles, was unten mit „1u" oder „2u"
     bezeichnet ist, leitet sich hiervon ab und skaliert mit dem Hero mit:

       Hero-Zeile     → Tagline                 1u
       Tagline        → Icon-Reihe (Oberkante)  1u
       „Follow Lenzi" → Social-Icons            1u
       Social-Icons   → Fußnote (Oberkante)     2u                         */
  --space-unit: calc(var(--hero-size) * 0.3125); /* 96px → 30px */

  /* Icon-Buttons — 33 % größer als zuvor (3.5rem → 4.65rem) */
  --icon-button-size: clamp(4rem, 14.6vw, 4.65rem);
  /* Ein Verhältnis, zwei Schreibweisen: als Prozentwert für die Glyphe im
     Button (bezogen auf dessen Inhaltsfläche) und als Länge für die
     Social-Icons, die keinen Button haben. Beide leiten sich vom selben
     Wert ab, damit sie nicht auseinanderdriften können. */
  --icon-glyph-ratio: 0.42;
  --icon-glyph-size: calc(var(--icon-glyph-ratio) * 100%);
  --icon-glyph-length: calc((var(--icon-button-size) - 2 * var(--icon-border)) * var(--icon-glyph-ratio));
  --icon-gap: clamp(1.25rem, 4.5vw, 1.75rem);
  --icon-border: 2px;

  /* --- Schräger Rahmen der drei Schaltflächen ---------------------------
     Die Umrandung nimmt das i-Pünktchen der Wortmarke auf. Es ist keine
     Schätzung, sondern aus dem Pfad der Klasse .g in
     originale/logo lenzi_bunt blau freigestellt_svg.svg gerechnet:

       untere Waagerechte  (300.10 | 8.56) -> (285.42 | 8.56)   14,68 lang
       obere  Waagerechte  (294.00 | 0.01) -> (308.71 | 0.01)   14,71 lang
       Höhe der Fläche     8,55
       Versatz der Schräge 8,58

       atan(8,58 / 8,55) = 45,10° gegen die Senkrechte

     Das Pünktchen ist also ein Parallelogramm mit waagerechter Ober- und
     Unterkante, 45,10° geneigten Seiten und kräftig gerundeten Ecken — die
     Sehne der Eckrundung misst 3,75, das sind 43,8 % der Höhe.

     Der Button ist breiter als das Pünktchen, weil er eine Beschriftung
     trägt. Gestreckt werden deshalb nur die beiden Waagerechten; Winkel und
     Eckrundung bleiben am Original. Negativ, weil skewX() mit negativem Wert
     die Oberkante nach rechts schiebt — die Fläche lehnt nach vorn, wie das
     Pünktchen.

     Die Reihe behält ihre Höhe (--icon-button-size), damit der Abstand zur
     Hero-Zeile und zur Wortmarke unverändert bleibt; sichtbar ist nur der
     niedrigere Rahmen, senkrecht mittig darin. */
  --slant: -45.1deg;
  /* tan(45,10°). Die Scherung verschiebt die Oberkante um genau diesen Anteil
     der Rahmenhöhe nach rechts — daraus folgen der waagerechte Überhang der
     Fläche über ihren Kasten (die halbe Rahmenhöhe je Seite) und die
     Kantengeometrie, die main.js zeichnet. */
  --slant-tan: 1.0035;
  /* 1 / cos(45,10°) = sqrt(1 + tan²). Wandelt einen Abstand SENKRECHT zur
     45°-Kante in einen waagerechten um — gebraucht überall dort, wo zwei
     geneigte Kanten aufeinandertreffen sollen. */
  --slant-sec: 1.4167;
  /* Rahmenhöhe folgt dem Schriftgrad: Zeilenkasten plus Luft. Bleibt unter
     --icon-button-size, damit die Reihe ihre Höhe behält und Hero-Zeile wie
     Wortmarke stehenbleiben. */
  --action-frame-h: min(calc(var(--action-label-size) * 2), var(--icon-button-size));
  /* --- Breite der Schaltflächen ----------------------------------------
     Alle drei sind gleich breit, das Maß nimmt die längste Beschriftung.
     Sie stehen in Versalien, maßgeblich ist also „PARTNERSHIPS". Die Breiten
     sind gesetzt gemessen, in Saira Regular mit der Laufweite von
     --tracking-small und mit Kerning (Versalien wie „PA" und „TN" ziehen
     sich zusammen, die Fontdatei allein gibt 0,04 em zu viel aus):

       Partnerships    6,0306 em      PARTNERSHIPS    7,6172 em

     Der Innenabstand vom Wort zur Umrandung ist verdoppelt. Vorher blieben
     bei 7,4 em Breite (7,4 − 6,0306) / 2 = 0,6847 em je Seite; jetzt sind es
     1,3694 em:

       7,6172 + 2 × 1,3694 = 10,356 em

     Gemessen ist der Abstand an der Kastenkante, also auf halber Höhe des
     Rahmens — dort, wo die 45°-Schräge die senkrechte Kastenkante schneidet. */
  --action-width-em: 10.356;
  --action-width: calc(var(--action-label-size) * var(--action-width-em));
  /* Abgeleitet vom Grad der Sub-Hero-Zeile „Formula 3 Driver.", davon 53,6 %
     — das sind 67 % davon, um 20 % zurückgenommen. Rahmenhöhe, Rahmenbreite
     und Strichstärke rechnen alle aus diesem Wert, sie werden also im
     gleichen Verhältnis kleiner; --space-actions rechnet aus der Rahmenhöhe
     und hält den Abstand oben und unten weiter exakt gleich.

     Der Faktor sitzt bewusst NUR hier: --action-width und --action-frame-h
     rechnen beide aus diesem Wert, also schrumpfen Breite, Rahmenhöhe und
     Schrift proportional mit. Die Höhe der REIHE hängt dagegen an
     --icon-button-size und bleibt unberührt — der Abstand nach oben zur
     Tagline und nach unten zur Wortmarke ändert sich dadurch nicht. */
  /* Der zweite Wert ist eine Notbremse für schmale Fenster, keine
     Gestaltung: drei Schaltflächen à 10,394 em, zwei Zwischenräume und der
     45°-Überhang der äußeren beiden (je eine halbe Rahmenhöhe, zusammen
     2 × 1,0035 Beschriftungsgrade) müssen zwischen die Seitenränder passen.

       3 × 10,356 + 2 × 1,0035 = 33,075

     Ohne diese Grenze lief die Reihe auf Telefonen über den Seitenrand
     hinaus — mit den breiteren Versalien deutlicher als vorher. Sie greift
     erst unter rund 640px Fensterbreite; darüber gilt unverändert der
     Anteil an „Formula 3 Driver.". */
  --action-label-size: min(
    calc(var(--tagline-size) * 0.536),
    calc((100vw - 2 * var(--page-pad-x) - 2 * var(--icon-gap)) / 33.075)
  );
  /* --- DIE Strichstärke der Seite --------------------------------------
     Eine einzige Linienbreite für alles Gezogene: Umrandung der drei
     Schaltflächen, Umrandung der Countdown-Kacheln, Streckenverlauf,
     Trennlinien in Tabellenstand und Rennliste, Trennlinie der Rechtsseiten.

     Ihr Maß ist nicht gesetzt, sondern gemessen: der senkrechte Stamm des
     „R" auf dem Racing-Button. In Saira Regular läuft seine linke Kante bei
     x = 100, die rechte bei x = 186 (Einheiten, upem = 1000) — der Stamm ist
     also 86/1000 = 0,086 em breit, über alle Schriftgrade konstant.

     Bezug ist der Grad der Button-Beschriftung. Damit ist die Linie exakt so
     dick wie der Buchstabe daneben, auf jedem Fenster.                    */
  --stem-ratio: 0.086;
  --line-w: calc(var(--action-label-size) * var(--stem-ratio));
  /* Eckrundung. Beim Pünktchen sind nur die beiden STUMPFEN Ecken gerundet
     (oben links, unten rechts), die spitzen bleiben scharf.

     Maßgeblich ist die SEHNE der Rundung: im Original misst sie 3,75 bei
     8,55 Höhe, also 43,8 % der Rahmenhöhe. Aus Sehne und Öffnungswinkel
     (180° − 45,10°) rechnet main.js den Radius des echten Kreisbogens.

     --action-frame-radius bleibt daneben stehen: es ist der Näherungswert
     für die Rückfallebene ohne JavaScript, wo der Rahmen weiterhin eine
     gescherte border-radius-Rundung ist. skewX() verzerrt die zwangsläufig
     zur Ellipse — 0,30 der Rahmenhöhe ist der Wert, bei dem die Silhouette
     im Übereinanderlegen am nächsten am Original liegt. */
  --action-frame-chord: 0.438;
  --action-frame-radius: calc(var(--action-frame-h) * 0.30);

  /* --- Wortmarke zwischen Icon-Reihe und „Follow Lenzi" ------------------
     Ober- und Unterkante des Logos liegen auf Ober- und Unterkante des „N"
     im Hero-Claim. Das „N" steht auf der Grundlinie (keine Unterlänge), seine
     sichtbare Höhe ist also die Versalhöhe: in Saira gemessene 0,688 em,
     über alle Schriftgrößen und Schriftstärken konstant.

       Höhe  = 96px × 0,688            =  66,05px
       Breite = 66,05px × 308,7 / 37,55 = 542,99px

     Die Wortmarke füllt ihre Zeichenfläche senkrecht vollständig aus, ihre
     sichtbare Kante ist damit die Elementkante.

     max-width greift erst auf schmalen Schirmen, wo das Logo sonst über den
     Seitenrand liefe; dort wird es proportional kleiner. */
  --logo-ratio: 308.7 / 37.55;   /* Seitenverhältnis der Logodatei */
  --cap-height: 0.688;           /* Versalhöhe von Saira, in em */
  /* Anteil des Zeilenkastens ÜBER der Grundlinie bei line-height: 1. Saira
     bringt ascent 1,135 und descent 0,439 mit (hhea), zusammen 1,574 em; bei
     line-height: 1 wird dieser Kasten mit −0,287 em Halbdurchschuss auf 1 em
     zentriert, die Grundlinie liegt dann 1,135 − 0,287 = 0,848 em unter der
     Oberkante. Derselbe Wert steckt in den Overrides des Ersatzfonts. */
  --sf-ascent: 0.848;
  /* Anteil des Zeilenkastens unterhalb der Grundlinie bei line-height: 1.
     Gemessen 0,152em. Der Hero-Claim hat keine Unterlängen, seine letzte
     sichtbare Pixelzeile ist also die Grundlinie — der Kasten reicht 14,6px
     darunter. Ohne diese Korrektur sähe der Abstand über dem Contact-Panel
     um genau diesen Betrag größer aus als der darunter.

     Maßgeblich ist nicht die Grundlinie, sondern die letzte sichtbare
     Pixelzeile: der Schlusspunkt (und jede Rundung) reicht --ink-overshoot
     unter die Grundlinie. Es bleiben

       1 − --sf-ascent − --ink-overshoot = 0,152 − 0,008 = 0,144 em

     Der Wert stand vorher als 0,1482 hier — gerechnet mit 0,0038 statt der
     gemessenen 0,008 em Überschuss. Der Unterschied ist 0,4px bei 96px Grad
     und wanderte über --space-actions und die Verankerung des
     Partnerships-Pop-ups in die Seite; als Formel kann er nicht mehr
     auseinanderlaufen. */
  --claim-descent: calc(1 - var(--sf-ascent) - var(--ink-overshoot));
  --logo-height: calc(var(--hero-size) * var(--cap-height));
  --logo-width: calc(var(--logo-height) * 308.7 / 37.55);

  /* --- Bewegung: EIN Tempo und EINE Kurve für die ganze Seite ------------
     Dauer 1s stammt aus dem Aerial-Template (animation: header 1s …).
     Alles was ein- oder ausblendet nutzt diese beiden Werte: Intro, Panels,
     Bild-Reveals im Overlay. */
  --fade: 1s;
  /* Panels gehen zügiger auf als die Bild-Reveals laufen — beim Aufklappen
     wartet man, beim Lesen nicht. */
  --fade-panel: 0.45s;
  --ease-soft: cubic-bezier(0.25, 0.46, 0.45, 0.94); /* weiche Landung */

  /* Der Seitenaufbau läuft langsamer als die Interaktionen. Aerial konnte
     schneller sein, weil dort zuerst ein Foto einfadet und das Auge schon
     beschäftigt ist — auf einer einfarbigen Fläche wirkt dasselbe Tempo
     hektisch. Einzige Stellschraube fürs Intro. */
  --fade-intro: 1.7s;

  /* Weg des Einblendens — absichtlich klein und für alle Elemente gleich,
     damit sich die Abstände zwischen Hero und Tagline beim Einfaden nicht
     verändern. (Aerial nutzte 1em, was pro Schriftgröße unterschiedlich
     weit ist und die Zeilen ineinanderlaufen lässt.) */
  --intro-rise: 8px;
  /* Staffelung mitgewachsen, damit das Verhältnis zur Dauer wie bei Aerial
     bleibt (dort 0.25s auf 1s). */
  --intro-stagger: 0.4s;
  /* Basis-Delay. Aerial startet bei 2.25s, weil dort zuerst das
     Hintergrundbild 3s lang einfadet. Hier erscheint der gelbe Grund
     sofort, deshalb rückt die Kette nach vorn. */
  --intro-base: 0.25s;

  /* --- Abstand von „Formula 3 Driver." zur Umrandung der Schaltflächen ---
     Gefordert ist, dass dieser Abstand genauso groß ist wie der von
     „Formula 3 Driver." zur Hero-Zeile darüber. Gemessen wird dabei von
     SICHTBARER Kante zu sichtbarer Kante — der Zeilenkasten trägt oben und
     unten Luft, die nicht mitzählt, und der Rahmen ist niedriger als die
     Reihe, in der er sitzt.

     Oben (Hero-Zeile → Tagline):
       Kastenrest unter der letzten sichtbaren Zeile des Claims
         --hero-size × --claim-descent
       + der Abstand der Kästen (1u)
       + von der Kastenoberkante der Tagline bis zu ihrer Versalhöhe
         --tagline-size × (Halbdurchschuss + --sf-ascent − --cap-height)

     Unten (Tagline → Rahmen):
       Kastenrest unter der Grundlinie der Tagline
         --tagline-size × (Halbdurchschuss + --claim-descent)
       + dieser Abstand
       + der Einzug des Rahmens in der Reihe
         (--icon-button-size − --action-frame-h) / 2

     Gleichgesetzt fällt der Halbdurchschuss der Tagline auf beiden Seiten
     heraus, es bleibt:

       1u + hero × CD + tagline × (ascent − cap − CD) − Rahmeneinzug

     1440 × 900: 30 + 14,23 + 0,33 − 18,44 = 26,12px. Der Wert darf auf sehr
     flachen Fenstern negativ werden — dann steht der Rahmen näher an der
     Tagline als die Reihe hoch ist, und weil der Rest der Reihe leer ist,
     stößt dabei nichts zusammen. */
  --space-actions: calc(var(--space-unit)
                        + var(--hero-size) * var(--claim-descent)
                        + var(--tagline-size)
                          * (var(--sf-ascent) - var(--cap-height) - var(--claim-descent))
                        - (var(--icon-button-size) - var(--action-frame-h)) / 2);

  /* --- Sichtbare Kanten: Ink-Maße der Schrift --------------------------
     Alles, was ab hier „oberster" oder „unterster Punkt" heißt, meint die
     letzte gezeichnete Pixelzeile, nicht die Kante des Zeilenkastens. Die
     drei fehlenden Maße sind in Saira gemessen (upem = 1000, canvas
     actualBoundingBox):

       Oberlänge von „l" / „f" / „d"        743  ->  0,743 em
       Überschuss der Rundungen und des
       Schlusspunkts unter der Grundlinie     8  ->  0,008 em

     --cap-height (0,688) bleibt daneben stehen: Versalien ohne Oberlänge
     („I" in „Impressum", „N" im Claim) enden dort. Maßgeblich ist immer der
     höchste bzw. tiefste Buchstabe der jeweiligen Zeile:

       „Formula 3 Driver."  oben Oberlänge („l"),  unten Überschuss („.")
       „Follow Lenzi"       oben Oberlänge („ll"), unten Überschuss („o")
       „Impressum"          oben Versalhöhe („I")                          */
  --ink-ascender: 0.743;
  --ink-overshoot: 0.008;

  /* Zeilen im Grad der Tagline (--tagline-size, --lh-tight): Weg von der
     Kastenkante zur sichtbaren Kante. Gilt für „Formula 3 Driver." und für
     „Follow Lenzi" — gleicher Grad, gleiche Zeilenhöhe, und beide haben
     oben eine Oberlänge und unten nur den Überschuss. */
  --subhead-ink-top: calc(var(--tagline-size)
                          * ((var(--lh-tight) - 1) / 2
                             + var(--sf-ascent) - var(--ink-ascender)));
  --subhead-ink-bottom: calc(var(--tagline-size)
                             * ((var(--lh-tight) - 1) / 2
                                + 1 - var(--sf-ascent) - var(--ink-overshoot)));

  /* Fußnotenzeile: von der Kastenoberkante zur Versalhöhe des „I" von
     „Impressum". */
  --footer-ink-top: calc(var(--footer-size)
                         * ((var(--lh-ui) - 1) / 2
                            + var(--sf-ascent) - var(--cap-height)));

  /* Die drei Social-Icons stehen in quadratischen Masken (640 × 640), die
     Zeichnung darin füllt sie nicht aus. Höchster und tiefster Punkt der
     Reihe gehören beide dem Facebook-Kreis — er ist von allen drei der
     größte. Aus der Datei gerechnet:

       Facebook   y 64,00 .. 574,10   ->  oben 10,00 %, unten 10,30 %
       Instagram  y 95,82 .. 543,98   ->  oben 14,97 %, unten 15,00 %
       YouTube    y 128,00 .. 512,00  ->  oben 20,00 %, unten 20,00 %       */
  --social-ink-top: calc(var(--icon-glyph-length) * 0.1);
  --social-ink-bottom: calc(var(--icon-glyph-length) * 0.10297);

  /* Einzug des Rahmens in der Icon-Reihe. Die Reihe ist
     --icon-button-size hoch, der gezeichnete Rahmen nur --action-frame-h und
     sitzt mittig darin — oberster und unterster Punkt der drei
     Schaltflächen liegen also um diesen Betrag innerhalb der Reihe. */
  --action-frame-inset: calc((var(--icon-button-size) - var(--action-frame-h)) / 2);

  /* Versatz der Wortmarke aus der Mitte ihres Slots. Zwei Nutzer: das Logo
     selbst und das Partnerships-Pop-up, dessen Unterkante sich an der
     Oberkante des Logos ausrichtet. Herleitung bei .hero__logo. */
  --logo-shift: calc((var(--subhead-ink-top) - var(--action-frame-inset)) / 2);

  /* --- DAS Referenzmaß der unteren Seitenhälfte -------------------------
     Abstand vom untersten sichtbaren Punkt des Hero-Claims (Schlusspunkt
     von „Nicolas Lenzi Loeffler.") zum obersten sichtbaren Punkt von
     „Formula 3 Driver." (Oberlänge des „l"). Von Ink-Kante zu Ink-Kante,
     nicht von Kasten zu Kasten:

       Kastenrest unter dem Claim      --hero-size × --claim-descent
       + Abstand der Kästen            1u
       + Weg zur Oberlänge der Tagline --subhead-ink-top

     1440 × 900: 14,23 + 30 + 5,74 = 49,97px

     Dieses Maß gilt an drei weiteren Stellen (Vorgabe):
       „Follow Lenzi"  -> Social-Icons        (--space-follow-icons)
       Social-Icons    -> „Impressum"         (--space-icons-footer)
       und halbiert als Luft über und unter dem Partnerships-Pop-up.        */
  --space-claim-tagline: calc(var(--hero-size) * var(--claim-descent)
                              + var(--space-unit)
                              + var(--subhead-ink-top));

  /* „Follow Lenzi" (Unterkante der Rundungen) -> oberster Punkt der
     Social-Icons. Gemessen wird von Ink zu Ink, gesetzt wird ein
     Kastenabstand — also beide Einzüge herausrechnen.
     1440 × 900: 49,97 − 6,83 − 2,96 = 40,18px */
  --space-follow-icons: calc(var(--space-claim-tagline)
                             - var(--subhead-ink-bottom)
                             - var(--social-ink-top));

  /* Unterster Punkt der Social-Icons -> Versalhöhe von „Impressum".
     1440 × 900: 49,97 − 3,04 − 5,04 = 41,88px */
  --space-icons-footer: calc(var(--space-claim-tagline)
                             - var(--social-ink-bottom)
                             - var(--footer-ink-top));

  /* --- Seitliche Bezugskanten für das Partnerships-Pop-up ---------------
     Halbe Breite der Icon-Reihe bis zu ihrem äußersten SICHTBAREN Punkt.
     Die drei Rahmen lehnen nach vorn, ihre Fläche ragt deshalb je Seite um
     eine halbe Rahmenhöhe (waagerecht: × tan 45,10°) über den Kasten hinaus.
     Links ist der äußerste Punkt die UNTERE Ecke des „Profile"-Rahmens,
     rechts die OBERE Ecke des „Partnerships"-Rahmens — genau die beiden
     Punkte, die die Vorgabe nennt. Beide liegen gleich weit von der
     Mittelachse, die Reihe ist symmetrisch.

       1440 × 900: 1,5 × 155,42 + 28 + 15,06 = 276,19px                    */
  --actions-half: calc(1.5 * var(--action-width)
                       + var(--icon-gap)
                       + 0.5 * var(--action-frame-h) * var(--slant-tan));

  /* Mittelachse -> sichtbare Außenkanten des Hero-Claims. Nicht die Kanten
     des Zeilenkastens: links beginnt das „N" erst nach seiner Vorbreite,
     rechts endet der Schlusspunkt vor seiner Nachbreite. Der Claim steht
     dadurch minimal unsymmetrisch zur Mittelachse — deshalb zwei Werte.

     Gemessen an der gesetzten Zeile („Nicolas Lenzi Loeffler.", Saira
     Regular, --tracking-display) und auf --hero-size normiert; die Zeile
     skaliert linear mit dem Grad, die Verhältnisse sind also konstant:

       Grad 96px:  Mitte -> linke  Ink-Kante des „N"   440,77px
                   Mitte -> rechte Ink-Kante des „."   447,05px            */
  --claim-ink-left: calc(var(--hero-size) * 4.5913);
  --claim-ink-right: calc(var(--hero-size) * 4.65676);

  /* Seitenrahmen */
  --page-pad-y: clamp(1.5rem, 5vw, 3rem);
  --page-pad-x: clamp(1.25rem, 5vw, 2.5rem);
  --footer-inset: clamp(0.75rem, 1.6vh, 1.25rem); /* Footer zur Unterkante */

  /* Overlay — mittig im Viewport, dadurch oben und unten automatisch
     derselbe Abstand. Der Footer bleibt darunter sichtbar. */
  /* Größe nach der markierten Fläche: rund 62 % der Fensterbreite und 78 %
     der Höhe, mittig — also ca. 19vw Rand seitlich und 11vh oben/unten. */
  --about-width: min(62vw, 1060px);

  /* Höhe: das Overlay soll die Social-Icons vollständig verdecken, die
     Fußnote aber sichtbar lassen. Es ist mittig im Viewport, oben und unten
     bleibt also automatisch derselbe Rand:

       Rand = Fußnoten-Abstand + Fußnotenzeile + 1u
       1440×900 → 14,4 + 19,6 + 30 = 64px

     Das 1u stand ursprünglich für die halbe Strecke zwischen Social-Icons
     und Fußnote (damals 2u) — die Unterkante des Overlays lag damit genau
     mittig darin. Diese Strecke ist inzwischen --space-icons-footer
     (41,4px bei 1440 × 900, siehe .social), der Rand hier ist bewusst
     unverändert geblieben: das Overlay deckt die Icons weiter vollständig
     ab (11,5px Überstand) und lässt der Fußnote 30px Luft. */
  /* Schriftgrad der Fußnote. Steht als Token, weil drei Stellen ihn teilen:
     die Fußnote selbst, ihre Zeilenhöhe für die Overlay-Rechnung und der Weg
     von der Kastenkante zur Versalhöhe von „Impressum". */
  --footer-size: 14px;
  --footer-line: calc(var(--footer-size) * var(--lh-ui)); /* Grad × Zeilenhöhe */
  --about-inset: calc(var(--footer-inset) + var(--footer-line) + var(--space-unit));
  --about-height: calc(100vh - 2 * var(--about-inset));

  /* Innenrand. Links, rechts und oben doppelt so groß wie zuvor — der graue
     Rahmen um das Bild ist damit verdoppelt. Unten bleibt es beim alten Maß,
     dort sitzt der Anschnitt des nächsten Kapitels. */
  --about-pad: clamp(1.25rem, 2.2vw, 2rem);
  --about-frame: calc(var(--about-pad) * 1.5);
  /* Abstand zwischen zwei Kapiteln: der frühere Wert (zweimal der Innenrand)
     minus 33 %. Desktop: 2 × 47,52 × 0,67 = 63,68px. */
  --story-gap: calc(2 * var(--about-frame) * 0.67);

  /* Höhe, die einem Bildkasten innerhalb eines Kapitels maximal bleibt:
     Overlay ohne Anschnitt und ohne Innenrand oben/unten. Bei den
     nebeneinander gesetzten Kapiteln leitet sich daraus auch die
     Spaltenbreite ab — Bild und Bildunterschrift sind dadurch immer gleich
     breit (siehe .story--split .story__figure). */
  --split-media-max-h: calc(var(--about-height) - 2 * var(--about-frame));

  /* --- Halbierte Kapitel ------------------------------------------------
     Textfläche des Pop-ups, davon die halbe Breite minus Spaltenabstand für
     den Bildkasten, und daraus über 4:5 seine Höhe. Der Countdown übernimmt
     genau diese Höhe, damit seine Kachelreihe auf der Unterkante des Bildes
     endet und nicht auf der Unterkante seiner Bildunterschrift. */
  --split-gap: clamp(1.25rem, 2.5vw, 2.25rem);
  --content-w: calc(var(--about-width) - 2 * var(--about-frame));
  --half-figure-w: min(calc(var(--content-w) / 2 - var(--split-gap)),
                       calc(var(--split-media-max-h) * 4 / 5));
  --half-media-h: calc(var(--half-figure-w) * 5 / 4);

  /* --- Breite des Blocks unter „Standings & Results" -------------------
     Tabellenstand und Rennliste enden rechts genau auf der rechten Kante des
     Helmbildes im Kapitel darüber und beginnen an derselben linken Kante.
     Das Helmbild ist --half-figure-w breit, also ist es auch dieser Block.

     Beide teilen ihn in DREI GLEICHE Spalten: Strecke | Austria Formel Cup |
     TopJet F2000. Die mittlere liegt damit zwangsläufig mittig — ihr Titel
     „Austria Formel Cup | AF3-T" steht genau über der Mitte des Helmbildes.
     Die dritte endet auf dessen rechter Kante, dort sitzt die Punktespalte. */
  --results-w: var(--half-figure-w);

  /* EIN Schriftgrad für den ganzen Block — Tabellenstand, Rennliste und
     Quellenangabe. Gefordert ist der Grad des Fließtextes; er steht als
     Obergrenze, darunter entscheidet die Spaltengeometrie (Rechnung bei
     .results__col-*): die Tabelle steht in drei gleichen Dritteln, und in
     ein Drittel muss der längere der beiden Serientitel passen —
     „Austria Formel Cup · AF3-T", im Satz gemessene 12,181em:

       12,181em <= --results-w / 3   ->   Grad <= --results-w / 36,54

     Gerundet auf /37,5, damit links und rechts des Titels noch etwas Luft
     im Drittel bleibt. */
  --results-font: min(var(--text-body-compact), calc(var(--results-w) / 37.5));

  --panel-radius: 18px;
}

/* svh kennt nicht jeder ältere Android-Browser; die vh-Zeile oben bleibt als
   Rückfall stehen. */
@supports (height: 100svh) {
  :root {
    --about-height: calc(100svh - 2 * var(--about-inset));
  }
}

/* 3. Reset / Basis -------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /* Android Chrome: verhindert, dass ein Zug nach unten die Seite neu lädt
     (pull-to-refresh) — im geöffneten About-Overlay würde das mitten im Lesen
     alles zurücksetzen. Die Seite selbst scrollt nicht, es geht also nichts
     verloren. Auf iOS und am Desktop ohne Wirkung. */
  overscroll-behavior-y: contain;
}

body {
  margin: 0;
  /* svh zuerst als Rückfall für ältere iOS-Versionen, die es nicht kennen */
  min-height: 100vh;
  min-height: 100svh;
  -webkit-tap-highlight-color: transparent;
  font-family: var(--font-sans);
  font-weight: var(--weight-regular);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: inherit;
}

img,
video {
  max-width: 100%;
  display: block;
}

button {
  font: inherit;
  color: inherit;
  margin: 0;
}

figure {
  margin: 0;
}

/* Nur für Screenreader sichtbar */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* 4. Intro-Animation ------------------------------------------------------ */
/* Portiert aus assets/css/main.css des Aerial-Templates:
   - Mechanik:  body.is-preload friert alle Animationen ein, window.onload
                entfernt die Klasse  (identisch zum Original)
   - Dauer 1s, Staffelung 0.25s, Reihenfolge Text → Icons → Footer
   Geändert gegenüber Aerial: der Weg ist auf 8px begrenzt (statt 1em, das
   pro Schriftgröße unterschiedlich weit ist) und die Kurve ist ein weiches
   Ease-Out statt des Standard-ease.

   Alle Regeln hängen an html.js — ohne JavaScript wird die Klasse nie
   gesetzt, dann ist die Seite von Anfang an vollständig sichtbar. */

body.is-preload *,
body.is-preload *::before,
body.is-preload *::after {
  animation: none !important;
  transition: none !important;
}

@keyframes intro-rise {
  0% {
    transform: translate3d(0, var(--intro-rise), 0);
    opacity: 0;
  }
  100% {
    /* none statt translate3d(0,0,0): mit fill-mode forwards bliebe sonst eine
       Transformation stehen, das Element behielte seine eigene Compositing-
       Ebene und würde auf ganze Gerätepixel gerundet gezeichnet. Die
       kalibrierten Abstände (1u / 2u) verschöben sich dadurch um bis zu einen
       halben Pixel. */
    transform: none;
    opacity: 1;
  }
}

/* Schritt 2 — Hero Line + Tagline, gemeinsam und mit identischem Weg,
   deshalb bleibt ihr Abstand während des Einfadens konstant. */
.js .hero__title,
.js .hero__tagline {
  opacity: 0;
  animation: intro-rise var(--fade-intro) var(--ease-soft) var(--intro-base) forwards;
}

/* Schritt 3 — Icon-Buttons, nacheinander (Aerial: #header nav li).
   Die Animation sitzt auf .action-shell und nicht auf .action-wrap:
   animation-fill-mode: forwards lässt den transform stehen, und ein
   transformierter Vorfahre wird zum Bezugsrahmen für position: fixed —
   das Overlay wäre sonst auf die Buttongröße eingesperrt. */
.js .action-shell {
  opacity: 0;
  animation: intro-rise var(--fade-intro) var(--ease-soft) forwards;
}

.js .action-wrap:nth-child(1) .action-shell {
  animation-delay: calc(var(--intro-base) + 1 * var(--intro-stagger));
}

.js .action-wrap:nth-child(2) .action-shell {
  animation-delay: calc(var(--intro-base) + 2 * var(--intro-stagger));
}

.js .action-wrap:nth-child(3) .action-shell {
  animation-delay: calc(var(--intro-base) + 3 * var(--intro-stagger));
}

/* Schritt 4 — Wortmarke unter der Icon-Reihe */
.js .hero__logo {
  opacity: 0;
  animation: intro-rise var(--fade-intro) var(--ease-soft) forwards;
  animation-delay: calc(var(--intro-base) + 4 * var(--intro-stagger));
}

/* Schritt 5 — Social-Block als Ganzes (Zeile „Follow Lenzi" und die drei
   Profile bewegen sich gemeinsam, ihr Abstand bleibt dabei konstant). */
.js .social {
  opacity: 0;
  animation: intro-rise var(--fade-intro) var(--ease-soft) forwards;
  animation-delay: calc(var(--intro-base) + 5 * var(--intro-stagger));
}

/* Footer zuletzt */
.js .page--home .footer {
  opacity: 0;
  animation: intro-rise var(--fade-intro) var(--ease-soft) forwards;
  animation-delay: calc(var(--intro-base) + 6 * var(--intro-stagger));
}

/* 5. Layout --------------------------------------------------------------- */

/* Gemeinsames Grundgerüst aller Seiten: Inhalt oben, Footer unten in den
   beiden Ecken, eng an der Unterkante. */
.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  min-height: 100svh;
  /* env(safe-area-inset-*) greift nur mit viewport-fit=cover im <meta>.
     Auf dem iPhone hält es Fußnote und Hero aus Notch, Home-Indicator und
     den abgerundeten Ecken heraus, auf Android aus der Gestenleiste.
     max() statt Addition: der Designabstand bleibt die Untergrenze. */
  padding-block: max(var(--page-pad-y), env(safe-area-inset-top))
                 max(var(--footer-inset), env(safe-area-inset-bottom));
  padding-inline: max(var(--page-pad-x), env(safe-area-inset-left))
                  max(var(--page-pad-x), env(safe-area-inset-right));
}

/* Startseite: CDF-Dunkelblau als Grund, darauf das Hintergrundfoto.

   Das Foto ist nicht roh eingesetzt, sondern in der Datei schon auf den
   Grund heruntergerechnet: entsättigt, weichgezeichnet und in eine
   Duotone-Rampe von #071523 nach #102B41 gelegt — beide Enden liegen knapp
   unter und knapp über #0A1F33. Der hellste Punkt der Fläche steht damit auf
   4,17:1 gegen CDF-Orange (reiner Grund: 4,80:1), und weil Hero-Zeile,
   Tagline und „Follow Lenzi" grosse Schrift sind, bleibt der Abstand zur
   AA-Schwelle von 3:1 überall gross. Das untere Drittel der Datei läuft auf
   reinem #0A1F33 aus: dort stehen Social-Icons und Fussnote, die einzigen
   Elemente in 14 px, und die behalten ihre 4,80:1 unverändert.

   Deshalb braucht die Seite hier keinen zusätzlichen Abdunkler und keine
   Änderung an Hero-Zeile, Wortmarke oder Icons — die Icon-Buttons tragen
   ohnehin eine deckende Fläche in --color-dark und stehen unberührt vor dem
   Bild. Aufbereitung siehe assets/images/bg/README.md.

   Zwei background-image-Zeilen: image-set() wählt AVIF (8 KB) und fällt auf
   JPEG zurück; Browser ohne image-set() nehmen die erste Zeile. */
.page--home {
  background-color: var(--color-dark);
  background-image: url("/assets/images/bg/home.jpg");
  background-image: image-set(url("/assets/images/bg/home.avif") type("image/avif"),
                              url("/assets/images/bg/home.jpg") type("image/jpeg"));
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: var(--color-orange);
}

/* Rechtsseiten: CDF-Dunkelblau als Grund, CDF-Hellgrau für Text, Trennlinie
   und Zurück-Pfeil (der Pfeil ist eine Maske und nimmt currentColor an). */
.page--dark {
  background-color: var(--color-dark);
  color: var(--color-light);
}

/* Hero: eine zentrierte Flex-Column — Hero Line, Tagline und Icon-Reihe
   liegen dadurch automatisch auf einer gemeinsamen Mittelachse. */
.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 100%;
}

.hero__title {
  margin: 0;
  font-size: var(--hero-size);
  font-weight: var(--weight-regular);
  line-height: var(--lh-display);
  letter-spacing: var(--tracking-display);
}

.hero__tagline {
  margin: var(--space-unit) 0 0;
  font-size: var(--tagline-size);
  font-weight: var(--weight-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-subhead);
}

/* Der freie Raum im Hero wird auf zwei gleich große Flächen aufgeteilt:
   ::before über der Hero-Zeile, .hero__logo-slot unter der Icon-Reihe. Beide
   wachsen mit demselben Faktor, die Hero-Zeile steht dadurch genauso hoch
   wie bei justify-content: center.

   Die untere Fläche reicht von der Unterkante der Icon-REIHE bis zur
   Oberkante des Kastens von „Follow Lenzi" (der Hero endet dort, wo der
   Social-Block beginnt). Beides sind Kastenkanten, nicht die sichtbaren
   Kanten — das Logo liegt deshalb nicht mittig im Slot, sondern um
   --logo-shift versetzt (siehe .hero__logo). */
.hero::before {
  content: "";
  flex: 1 1 0;
}

.hero__logo-slot {
  /* Bezugsrahmen für das Contact-Panel, siehe dort. */
  position: relative;
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 0;
}

/* Freigestellte Wortmarke, so hoch wie der Hero-Claim.
   Die dunkelblauen Flächen der Wortmarke laufen in der Datei im CDF-Hellgrau
   — auf dunkelblauem Grund wären sie sonst unsichtbar. */
/* Gefordert: der Abstand vom obersten Punkt des Logos zum untersten Punkt
   der drei Schaltflächen ist genauso groß wie der vom untersten Punkt des
   Logos zum obersten Punkt von „Follow Lenzi". Beide Enden des Slots sind
   Kastenkanten, die sichtbaren Kanten liegen jeweils weiter innen:

     oben   der gezeichnete Rahmen endet --action-frame-inset über der
            Unterkante der Icon-Reihe (22,19px bei 1440 × 900)
     unten  die Oberlänge des „ll" von „Follow Lenzi" beginnt erst
            --subhead-ink-top unter der Kastenoberkante (5,74px)

   Ein mittig sitzendes Logo hätte damit oben 22,19px mehr Luft als gefordert
   und unten 5,74px zu wenig. Der Ausgleich ist die halbe Differenz:

     --logo-shift = (--subhead-ink-top − --action-frame-inset) / 2
     1440 × 900: (5,74 − 22,19) / 2 = −8,22px  (das Logo rückt nach oben)

   Gesetzt wird der doppelte Wert als margin-top: bei align-items: center
   verteilt der Flexbox-Algorithmus den Rest um den MARGIN-Kasten, ein
   oberer Rand verschiebt das Element deshalb nur um seine Hälfte. */
.hero__logo {
  margin-top: calc(2 * var(--logo-shift));
  width: var(--logo-width);
  max-width: 100%;
  /* Notbremse für sehr flache Fenster: lieber ein kleineres Logo als eines,
     das über die Icon-Reihe und „Follow Lenzi" läuft. */
  max-height: 100%;
  aspect-ratio: var(--logo-ratio);
  background: url("/assets/images/lenzi-logo.svg") center / contain no-repeat;
}

/* Footer — © links unten, Links rechts unten. Auseinandergezogen, damit er
   neben dem geöffneten Overlay nicht dominiert. */
.footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  margin-top: clamp(1.5rem, 4vh, 2.5rem);
  font-size: var(--footer-size);
  font-weight: var(--weight-regular);
  line-height: var(--lh-ui);
}

/* Auf der Startseite trägt der Social-Block den gesamten Abstand nach oben
   (2u bis zur Oberkante dieser Zeile), der Footer selbst bringt keinen
   eigenen mit. */
.page--home .footer {
  margin-top: 0;
}

.footer__copy,
.footer__links {
  flex: 0 0 auto;
}

.footer__links {
  display: flex;
  align-items: baseline;
}

.footer a {
  text-decoration: none;
  color: inherit;
}

.footer a:hover,
.footer a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Trenner in derselben Farbe wie der Text, doppelter Abstand */
.footer__sep {
  margin: 0 0.75em;
}

/* Social — „Follow Lenzi" plus die drei Profil-Icons, mittig zentriert über
   der Fußnote.

   Beide Abstände sind nicht mehr 1u und 2u, sondern das Referenzmaß der
   unteren Seitenhälfte: von Ink-Kante zu Ink-Kante genauso groß wie der
   Abstand zwischen dem Schlusspunkt von „Nicolas Lenzi Loeffler." und der
   Oberlänge von „Formula 3 Driver." (Rechnung bei --space-claim-tagline).

     unterster Punkt der Icons -> Versalhöhe „Impressum"  --space-claim-tagline
     Unterkante „Follow Lenzi" -> oberster Punkt der Icons --space-claim-tagline

   Gesetzt werden Kastenabstände, gemessen wird Ink — die Einzüge stecken
   deshalb in --space-icons-footer und --space-follow-icons. */
.social {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  margin-bottom: var(--space-icons-footer);
  text-align: center;
}

/* Gleiche Größe, Laufweite und Stärke wie „Formula 3 Driver". */
.social__label {
  margin: 0 0 var(--space-follow-icons);
  font-size: var(--tagline-size);
  font-weight: var(--weight-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-subhead);
}

.social__icons {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--icon-gap);
}

/* Ohne Umkreisung, aber mit exakt derselben Glyphenfläche wie in den
   Buttons (--icon-glyph-length = 42 % der Buttongröße). */
.social__link {
  display: grid;
  place-items: center;
  color: inherit;
  text-decoration: none;
  transition: transform 180ms var(--ease-soft);
}

.social__link .icon {
  width: var(--icon-glyph-length);
  height: var(--icon-glyph-length);
}

.social__link:focus-visible {
  outline: 2px solid var(--color-orange);
  outline-offset: 4px;
  border-radius: 2px;
  transform: translateY(-2px);
}

/* Wie bei den Buttons: nur mit echtem Zeiger, sonst bliebe der Zustand auf
   Touch-Geräten hängen. Ohne Fläche bleibt als Rückmeldung die Bewegung. */
@media (hover: hover) and (pointer: fine) {
  .social__link:hover {
    transform: translateY(-2px);
  }
}

/* 6. Icon-Buttons --------------------------------------------------------- */

/* Positionierungs-Anker für das Contact-Panel, das sich an der Mittelachse
   der ganzen Reihe ausrichtet statt am einzelnen Button. */
.actions {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--icon-gap);
  /* So weit unter der Tagline, dass der Abstand von der Tagline zur
     Umrandung genauso groß ist wie der von der Tagline zur Hero-Zeile —
     Rechnung bei --space-actions. */
  margin-top: var(--space-actions);
}

/* Wrapper hält Button + zugehöriges Panel zusammen (Positionierungs-Anker,
   Hover bleibt beim Weg zum Panel erhalten). */
.action-wrap {
  position: relative;
  display: flex;
}

/* Träger der Intro-Animation — hält den transform vom Panel fern. */
.action-shell {
  display: flex;
}

/* Schräger Rahmen mit Wortbeschriftung.

   Die Schaltfläche selbst ist unsichtbar und trägt nur die Beschriftung; der
   Rahmen ist ein eigenes Kästchen (::before). Nur dieses wird geschert — die
   Schrift bleibt dadurch aufrecht und unverzerrt stehen. Eine gescherte
   Schaltfläche mit gegengescherter Schrift käme aufs selbe Bild heraus,
   würde aber die Schrift zweimal durch eine Matrix schicken und sichtbar
   verschmieren.

   Die Höhe der Schaltfläche bleibt --icon-button-size: das ist zugleich das
   alte Maß der Reihe (Hero-Zeile und Wortmarke bleiben stehen) und ein
   Fingerziel von 64–74px. Der Rahmen ist niedriger und sitzt mittig darin. */
.action {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--action-width);
  height: var(--icon-button-size);
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-orange);
  font-family: inherit;
  font-size: var(--action-label-size);
  /* Regular, wie die Tagline direkt darüber: Beschriftung und „Formula 3
     Driver." tragen damit denselben Grad UND dieselbe Stärke. Fett stünde auf
     der Startseite sonst als einzige Stelle — die Seite hält es den Titeln
     und Kapitelüberschriften vor. */
  font-weight: var(--weight-regular);
  line-height: var(--lh-ui);
  letter-spacing: var(--tracking-small);
  text-transform: uppercase;
  cursor: pointer;
  text-decoration: none;
  transition: transform 180ms var(--ease-soft);
}

/* --- Der Rahmen: eine gezeichnete Kontur, keine gescherte Umrandung ----
   Bis hierher war der Rahmen ein Kästchen mit CSS-Umrandung, das per
   skewX() geschert wurde. Das erzeugt zwangsläufig UNTERSCHIEDLICH DICKE
   Linien, und zwar messbar:

     · Ober- und Unterkante bleiben waagerecht, ihre Dicke bleibt 1,0×.
     · Die beiden 45°-Kanten entstehen aus den senkrechten Umrandungen. Deren
       waagerechte Breite überlebt die Scherung, senkrecht zur Kante gemessen
       bleiben davon aber nur cos(45,1°) = 0,71×.
     · Im gerundeten Eck dreht sich die Tangente durch die Richtung, die die
       Scherung staucht — dort läuft die Linie auf 1,41× auf.

   Zwischen dünnster und dickster Stelle liegt damit ein Faktor 2. Genau das
   war auf den Schaltflächen zu sehen. Reparieren lässt sich das in CSS nicht:
   eine Scherung ist nicht winkeltreu, ein Ring gleicher Dicke kann unter ihr
   gar nicht entstehen.

   Deshalb wird die Kontur gerechnet und als SVG-Pfad gezeichnet (main.js,
   Abschnitt „Umrandungen"). Ein Strich auf einem Pfad ist überall gleich
   dick, unabhängig von der Richtung. Zwei Dinge werden dadurch sogar
   originalgetreuer als vorher:

     · Der Winkel ist exakt 45,10°, in jeder Kastengröße — vorher hätte jede
       Abweichung des Seitenverhältnisses ihn verzogen.
     · Die beiden stumpfen Ecken tragen einen echten KREISBOGEN mit der Sehne
       des Originals (43,8 % der Höhe). Unter der Scherung war nur eine
       Ellipse möglich; der Kommentar bei --action-frame-chord hielt das als
       Einschränkung fest, sie ist damit aufgehoben.

   Die deckende Fläche in --color-dark bleibt: der Grund der Startseite ist
   seit dem Hintergrundfoto nicht mehr überall reines #0A1F33 — hinter der
   Icon-Reihe steht das Bild bei bis zu #102C44, und CDF-Orange käme darauf
   nur auf 4,11:1. Mit der Fläche bleiben es die 4,80:1 des reinen Grundes. */
/* Breite und Höhe stehen ausdrücklich, nicht über left/right bzw. top/bottom:
   ein <svg> ohne viewBox ist ein ersetztes Element ohne eigenes
   Seitenverhältnis. Bei width/height: auto bekäme es die Ersatzgröße
   300 × 150px, und die gegenüberliegende Kante würde ignoriert.

   overflow: visible, damit die Gehrung der beiden SPITZEN Ecken vollständig
   gezeichnet wird. Sie reicht genau bis auf die Kastenkante — der Pfad liegt
   um die halbe Strichstärke senkrecht nach innen, und an einer 45°-Spitze
   holt die Gehrung genau diesen Versatz wieder heraus. */
.frame {
  position: absolute;
  pointer-events: none;
  overflow: visible;
}

.frame__shape {
  fill: none;
  stroke-width: var(--line-w);
}

/* Der Kasten des Rahmens ist links und rechts um den halben 45°-Versatz
   breiter als die Schaltfläche: die gescherte Fläche ragte genau so weit über
   ihren Kasten hinaus, und dieses Bild bleibt unverändert. */
.action > .frame {
  top: calc((var(--icon-button-size) - var(--action-frame-h)) / 2);
  left: calc(-0.5 * var(--action-frame-h) * var(--slant-tan));
  width: calc(100% + var(--action-frame-h) * var(--slant-tan));
  height: var(--action-frame-h);
  opacity: 0.68;
  transition: opacity 180ms var(--ease-soft);
}

.action > .frame .frame__shape {
  fill: var(--color-dark);
  stroke: var(--color-orange);
}

/* Rückfallebene ohne JavaScript: dort bleibt es bei der gescherten
   Umrandung — lieber eine ungleichmäßige Linie als keine. Mit JavaScript
   zeichnet .frame darüber, das Kästchen wird dann abgeschaltet. */
.action::before {
  content: "";
  position: absolute;
  inset: calc((var(--icon-button-size) - var(--action-frame-h)) / 2) 0;
  border: var(--line-w) solid var(--color-orange);
  border-radius: var(--action-frame-radius) 0 var(--action-frame-radius) 0;
  background-color: var(--color-dark);
  transform: skewX(var(--slant));
  opacity: 0.68;
  transition: opacity 180ms var(--ease-soft);
}

.js .action::before,
.js .countdown__tile::before {
  display: none;
}

/* Die Beschriftung liegt über dem Rahmen und wird nicht geschert. */
.action__label {
  position: relative;
}

/* Rückmeldung bleibt bewusst klein: der Rahmen wird voll deckend, die Fläche
   rückt 2px in Neigungsrichtung — nach vorn. Keine Skalierung, keine
   Farbumkehr, keine Geometrieänderung. */
.action:focus-visible::before,
.action[aria-expanded="true"]::before,
.action:focus-visible > .frame,
.action[aria-expanded="true"] > .frame {
  opacity: 1;
}

.action:focus-visible,
.action[aria-expanded="true"] {
  transform: translateX(2px);
}

/* Nur mit echtem Zeiger. Auf Touch-Geräten bliebe :hover nach dem Antippen
   hängen, bis woanders hingetippt wird. */
@media (hover: hover) and (pointer: fine) {
  .action:hover::before,
  .action:hover > .frame {
    opacity: 1;
  }

  .action:hover {
    transform: translateX(2px);
  }
}

.action:focus-visible {
  outline: 2px solid var(--color-orange);
  outline-offset: 3px;
}

/* Icons als CSS-Mask, damit die SVG-Dateien unverändert bleiben und
   trotzdem über die Textfarbe eingefärbt werden können. */
.icon {
  width: var(--icon-glyph-size);
  height: var(--icon-glyph-size);
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.icon--user {
  -webkit-mask-image: url("/assets/icons/user.svg");
  mask-image: url("/assets/icons/user.svg");
}

.icon--racing {
  -webkit-mask-image: url("/assets/icons/flag-checkered-solid-full.svg");
  mask-image: url("/assets/icons/flag-checkered-solid-full.svg");
}

.icon--youtube {
  -webkit-mask-image: url("/assets/icons/youtube-brands-solid-full.svg");
  mask-image: url("/assets/icons/youtube-brands-solid-full.svg");
}

.icon--instagram {
  -webkit-mask-image: url("/assets/icons/instagram-brands-solid-full.svg");
  mask-image: url("/assets/icons/instagram-brands-solid-full.svg");
}

.icon--facebook {
  -webkit-mask-image: url("/assets/icons/facebook-brands-solid-full.svg");
  mask-image: url("/assets/icons/facebook-brands-solid-full.svg");
}

.icon--linkedin {
  -webkit-mask-image: url("/assets/icons/linkedin-in-brands-solid-full_new_.svg");
  mask-image: url("/assets/icons/linkedin-in-brands-solid-full_new_.svg");
}

.icon--envelope {
  -webkit-mask-image: url("/assets/icons/envelope.svg");
  mask-image: url("/assets/icons/envelope.svg");
}

.icon--arrow-left {
  -webkit-mask-image: url("/assets/icons/arrow-left-solid-full.svg");
  mask-image: url("/assets/icons/arrow-left-solid-full.svg");
}

/* 7. Panels — Basis + Contact --------------------------------------------- */

.panel {
  position: absolute;
  top: calc(100% + 0.875rem);
  left: 50%;
  z-index: 20;
  width: max-content;
  max-width: min(520px, calc(100vw - 2.5rem));
  padding: clamp(1.25rem, 3vw, 1.75rem);
  border-radius: var(--panel-radius);
  background-color: var(--color-dark);
  color: var(--color-light);
  text-align: left;
  box-shadow: 0 18px 45px rgba(10, 10, 10, 0.28);

  /* Ein- und Ausblenden im Seitentempo. visibility schaltet verzögert erst
     nach dem Fade, damit das Panel beim Ausblenden nicht klickbar bleibt. */
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, var(--intro-rise));
  transition: opacity var(--fade-panel) var(--ease-soft),
              transform var(--fade-panel) var(--ease-soft),
              visibility 0s linear var(--fade-panel);
}

.panel.is-open {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition: opacity var(--fade-panel) var(--ease-soft),
              transform var(--fade-panel) var(--ease-soft),
              visibility 0s linear 0s;
}

.panel p {
  margin: 0;
}

/* Der Zeiger bleibt über der Panelfläche der normale Pfeil. Ohne diese
   Regel schlägt er über jedem Textblock in den Text-Cursor um — bei einem
   Panel, das beim Hovern aufgeht, wirkt das unruhig, und es signalisiert
   eine Eingabe, die es hier nicht gibt. Markieren und Kopieren bleibt
   möglich, nur echte Links zeigen die Hand. */
.panel,
.panel * {
  cursor: default;
}

.panel a {
  cursor: pointer;
}

/* Contact-Panel — liegt über Tagline und Icon-Reihe, aber nie über der
   Wortmarke. Es steht deshalb im Logo-Slot und ist an allen vier Kanten
   fest verankert; height und width bleiben auto und ergeben sich aus den
   vier Offsets. Alle vier Kanten sind SICHTBARE Kanten des Pop-ups, es
   verteilt also nichts mehr über auto-Ränder.

   Senkrecht: das Pop-up hält oben und unten den GLEICHEN Abstand, ein
   Viertel der Referenzstrecke --space-claim-tagline.

     1440 × 900: Streifen 237,0px, Luft je 12,4px, Kasten 212,2px

     top     --space-claim-tagline / 4 unter dem Schlusspunkt von
             „Nicolas Lenzi Loeffler.".
     bottom  --space-claim-tagline / 4 über der Oberkante der Wortmarke.

   Waagerecht (Vorgabe): jede Kante liegt mittig zwischen der Icon-Reihe und
   dem Hero-Claim.

     left    auf der linken Ink-Kante des „N" von „Nicolas ...".
     right   auf der rechten Ink-Kante des Schlusspunkts von „... Loeffler.".

   Das Pop-up ist damit genau so breit wie der Hero-Name darüber. Weil der
   Claim nicht exakt symmetrisch zur Mittelachse steht (Vorbreite des „N"
   9,58px, Nachbreite des Punkts 3,26px bei 96px Grad), sind die beiden
   Offsets unterschiedlich groß — das Pop-up steht dadurch um 3,1px rechts
   der Mittelachse. Deshalb left UND right statt left: 50 % mit
   translateX(-50 %).

     1440 × 900: 279,24px .. 1167,05px  (887,81px breit, 212,2px hoch)     */
.panel--contact {
  /* Von der Oberkante des Logo-Slots aufwärts zum Schlusspunkt des Claims:
     die Icon-Reihe, ihr Abstand zur Tagline (--space-actions), die
     Taglinezeile, deren Abstand zur Hero-Zeile (1u) und der Kastenrest unter
     dem Claim. Davon die halbe Referenzstrecke wieder nach unten. */
  top: calc(var(--space-claim-tagline) / 4
            - (var(--icon-button-size)
               + var(--space-actions)
               + var(--space-unit)
               + var(--tagline-line)
               + var(--hero-size) * var(--claim-descent)));
  /* Oberkante der Wortmarke: eine halbe Logohöhe über der Slotmitte, um
     --logo-shift versetzt (siehe .hero__logo). Davon die halbe
     Referenzstrecke nach oben. */
  bottom: calc(50% + var(--logo-height) / 2
               - var(--logo-shift)
               + var(--space-claim-tagline) / 4);
  /* max(0px, …) ist eine Notbremse für schmale Fenster: dort bricht der
     Claim in zwei Zeilen, seine Ink-Kanten liegen dann weiter außen als das
     Fenster breit ist. Das Pop-up geht in diesem Fall bis an die
     Seitenränder — wie vorher über calc(100vw − 2.5rem). */
  left: max(0px, calc(50% - var(--claim-ink-left)));
  right: max(0px, calc(50% - var(--claim-ink-right)));
  /* Beide Achsen kommen aus den Offsets. Die Basis .panel setzt width:
     max-content und deckelt max-width bei 520px — beides muss weg. */
  width: auto;
  max-width: none;
  height: auto;
  /* Innenabstand: der Kasten steht fest, das Padding rückt nur den Text von
     den Kanten ab. Waagerecht bleiben es die 28px der Basis .panel, senkrecht
     rund die Hälfte davon — der Kasten ist deutlich breiter als hoch, ein
     gleich großer Rand oben und unten liesse ihn gedrungen wirken. Der clamp
     hält den Rand auf kurzen Fenstern klein, wo der Kasten selbst flacher
     wird. */
  padding-block: clamp(0.35rem, 0.8vh, 0.7rem);
  /* Der Grad hängt an der Fensterhöhe, nicht an einem festen Wert.

     EIN Grad für das ganze Pop-up, und zwar der der Pop-up-Fließtexte in
     Profile und Racing (.story__body: --text-body-compact). Vorher hing der
     Grad der beiden Absätze an der Fensterhöhe und lag damit unter dem der
     Kontaktzeilen — im selben Kasten standen zwei Größen.

     Der Kasten wächst dafür in der Höhe mit (siehe bottom), statt den Text
     zu verkleinern. */
  font-size: var(--text-body-compact);
  /* Notausgang, keine Gestaltung: sollte der Text den festen Kasten doch
     sprengen, scrollt er DARIN, statt über Claim und Wortmarke zu laufen.
     overscroll-behavior hält den Zug im Pop-up. */
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Nur Opazität animieren — jede Bewegung würde die Ausrichtung stören.
     Ohne left: 50 % gibt es auch kein translateX mehr zu halten. */
  transform: none;
}

.panel--contact.is-open {
  transform: none;
}

/* Auf kurzen Schirmen wird der Streifen zwischen Hero-Zeile und Wortmarke
   knapp. Statt das Panel dort herauszulösen und irgendwo hin zu zentrieren,
   wird es kompakter — die Verankerung und damit die exakt gleichen Abstände
   nach oben und unten bleiben so auf jedem Gerät erhalten. */
@media (max-height: 700px) {
  .panel--contact {
    /* Nur waagerecht kürzen. Den Grad regelt der clamp oben — er hängt
       selbst an der Fensterhöhe und liegt hier bereits unter 12px; ein Pin
       an dieser Stelle würde ihn nur wieder anheben. */
    padding-inline: 1.15rem;
  }

  .contact__line + .contact__line {
    margin-top: 0.4rem;
  }
}

/* Einleitungsabsatz und Kontaktzeilen. Über .panel--contact geschrieben,
   weil .panel p weiter oben margin: 0 setzt. Beide auf derselben Zeilenhöhe
   — vorher liefen sie im selben Kasten auf 1,55 und 1,5 auseinander. */
/* Absatz- und Zeilenabstand zurückgenommen: die fünfte Zeile
   (Ansprechpartner) braucht den Platz, und der Streifen zwischen Hero-Zeile
   und Wortmarke gibt keinen her. 0,7rem zwischen den Absätzen, 0,1rem
   zwischen den drei Kontaktzeilen — die lesen sich dadurch als ein Block,
   die beiden Absätze darüber weiter als Prosa. */
/* Absatzabstand knapp gehalten: der Kasten steht fest, und mit der
   Überleitungszeile stehen darin fünf Textblöcke statt vier. */
.panel--contact .contact__lead {
  margin-bottom: 0.4rem;
  line-height: var(--lh-read);
}

.panel--contact .contact__line {
  line-height: var(--lh-read);
}

/* Die Überleitung und die Zeile des Ansprechpartners stehen fett, die
   Kontaktwege darunter im normalen Schnitt. Grad, Zeilenhöhe und Farbe
   bleiben unverändert — die Gliederung trägt allein die Strichstärke der
   Schrift, keine zweite Größe und keine zweite Farbe. */
.panel--contact .contact__line--strong {
  font-weight: var(--weight-bold);
}

.panel--contact .contact__line + .contact__line {
  margin-top: 0.1rem;
}

/* --- LinkedIn-Zeichen in der Zeile des Ansprechpartners ----------------
   Es steht inline hinter „Commercial Partnerships" und gehört damit sichtbar
   zu dieser Zeile — kein margin-left: auto, keine absolute Position, keine
   Fläche, kein Schatten.

   Maskenmaße von linkedin-in-brands-solid-full_new_.svg, aus der Datei
   gerechnet (viewBox 640):

     x 96 .. 544   ->  Zeichnung 70,0 % der Kastenbreite, links 15,0 % leer
     y 64 .. 512   ->  Zeichnung 70,0 % der Kastenhöhe,   oben 10,0 % leer

   DER KASTEN DES LINKS IST DIE ZEICHNUNG, nicht die Maske. Vorher trug das
   <a> die Maske samt ihrer leeren Ränder und zusätzlich einen Innenabstand
   als Tippziel; seine anklickbare Fläche reichte dadurch rund 9px nach links
   über die Marke hinaus — der Zeiger sprang schon neben dem Zeichen auf die
   Hand. Jetzt ist das <a> genau so groß wie die gezeichnete Marke, und die
   Maske hängt als absolut gesetztes Kind darin: um ihre leeren Ränder nach
   links und oben herausgerückt, sodass die Zeichnung den Linkkasten exakt
   ausfüllt.

   Höhe: 10 % über der Versalhöhe der Zeile — so groß wie die Großbuchstaben
   daneben, nur eine Spur kräftiger.

   Ausrichtung: das <a> hat keinen Inhalt im Textfluss (Maske und
   Screenreader-Text stehen beide absolut), seine Grundlinie ist damit seine
   untere Randkante. Mit vertical-align: baseline steht die Unterkante der
   Zeichnung deshalb auf der Grundlinie der Zeile — wie die Großbuchstaben
   daneben.

   Der linke Rand ist der SICHTBARE Abstand zum Wort davor, weil der
   Linkkasten an der Zeichnung beginnt. */
.contact__social {
  --linkedin-ink: 0.7;
  --linkedin-lead: 0.15;
  --linkedin-top: 0.1;
  --linkedin-mark: calc(1.1em * var(--cap-height));
  --linkedin-box: calc(var(--linkedin-mark) / var(--linkedin-ink));
  position: relative;
  display: inline-block;
  width: var(--linkedin-mark);
  height: var(--linkedin-mark);
  margin-left: 0.45em;
  vertical-align: baseline;
}

/* Die Marke nimmt die Textfarbe des Pop-ups an (CDF-Hellgrau über
   currentColor in .icon) — kein LinkedIn-Blau. */
.contact__social .icon {
  position: absolute;
  top: calc(-1 * var(--linkedin-top) * var(--linkedin-box));
  left: calc(-1 * var(--linkedin-lead) * var(--linkedin-box));
  width: var(--linkedin-box);
  height: var(--linkedin-box);
  /* Die Maske ragt mit ihren leeren Rändern über den Linkkasten hinaus. Als
     Kind des <a> würde sie dort Klicks fangen — genau die unsichtbare Fläche
     neben dem Zeichen. Anklickbar ist deshalb allein der Kasten des <a>, und
     der IST die gezeichnete Marke. */
  pointer-events: none;
}

/* Aus demselben Grund die Screenreader-Zeile: sie steht absolut und liegt mit
   ihrem negativen Rand einen Pixel über der linken oberen Ecke des Links.
   Vorgelesen wird sie unverändert, angeklickt werden kann sie nicht mehr. */
.contact__social .sr-only {
  pointer-events: none;
}

/* Mailadresse als sichtbarer Link, Telefonnummer optisch wie Text —
   gleiche Logik wie auf den Rechtsseiten. */
/* Mail- und Telefonlinks verhalten sich wie die Footer-Links: im Ruhezustand
   ohne Unterstreichung, sie erscheint erst unter dem Zeiger. */
.panel--contact a {
  text-decoration: none;
}

.panel--contact a:hover,
.panel--contact a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Der Inhalt klart auf wie im About-Overlay: das Panel selbst blendet zügig
   ein (--fade-panel), der Text löst sich darin über --fade aus der
   Weichzeichnung. Gleiche Dauer, gleiche Kurve, gleicher Versatz wie bei den
   Kapiteltexten. */
.panel--contact .contact__lead,
.panel--contact .contact__line {
  opacity: 0.3;
  filter: blur(5px);
  transition: opacity var(--fade) var(--ease-soft) 100ms,
              filter var(--fade) var(--ease-soft) 100ms;
}

.panel--contact.is-open .contact__lead,
.panel--contact.is-open .contact__line {
  opacity: 1;
  filter: blur(0);
}

/* 8. About-Me-Overlay ----------------------------------------------------- */
/* Mittig im Viewport, dadurch oben und unten automatisch derselbe Abstand.
   Der Footer bleibt darunter sichtbar.

   Aufbau je Kapitel (von oben nach unten):
     Medium  →  Text  →  angeschnittenes nächstes Kapitel (unscharf)
   Jedes Kapitel ist so hoch wie das Overlay minus Anschnitt und rastet beim
   Scrollen ein. Der Anschnitt ist kein separates Element, sondern der obere
   Rand des echten nächsten Bildes: solange es nicht aufgedeckt ist, liegt
   eine Weichzeichnung darauf, die beim Einrasten weichblendet. */

.panel--story {
  position: fixed;
  top: 50%;
  right: auto;
  bottom: auto;
  left: 50%;
  width: var(--about-width);
  max-width: none;
  height: var(--about-height);
  padding: 0;
  overflow: hidden;
  /* Nur Opazität animieren — ruhiger als eine zusätzliche Bewegung. */
  transform: translate(-50%, -50%);
}

.panel--story.is-open {
  transform: translate(-50%, -50%);
}

/* Scroll-Container */
.about {
  height: 100%;
  padding: var(--about-frame);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* proximity statt mandatory: Kapitel, die länger als eine Overlay-Höhe
     sind, lassen sich sonst nicht durchscrollen. */
  scroll-snap-type: y proximity;
  scrollbar-width: none;
}

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

/* --- Sichtbare Scrollleiste in den Pop-ups -----------------------------
   Die nativen Leisten bleiben aus: bei .about oben ausgeblendet, im
   Contact-Panel waren sie ohnehin nie zu sehen. An ihrer Stelle haengt
   main.js in jedes der drei Pop-ups diese eigene Leiste ein. Sie ist reine
   Anzeige — dass unterhalb noch Inhalt liegt — und ersetzt kein Scrollen.

   Warum nicht die native Leiste gestalten: sobald ::-webkit-scrollbar eine
   Breite bekommt, ist sie keine Overlay-Leiste mehr und BELEGT Layoutbreite.
   Der Inhalt aller Pop-ups wuerde um ihre Breite schmaler — und damit auch
   --content-w und jedes Mass, das daraus faellt. Diese Leiste liegt statt
   dessen UEBER dem Inhalt und nimmt keinen Platz weg.

   pointer-events: none haelt sie vollstaendig aus jeder Interaktion heraus:
   Rad, Trackpad und Touch scrollen unveraendert, die Leiste zeigt nur an,
   wo man steht. Ziehen laesst sie sich deshalb bewusst nicht. */
.panel__scroll {
  /* Derselbe Randabstand, den auch .panel__close nach oben und rechts haelt
     — die Leiste laeuft dadurch rechtsbuendig mit dem Schliesskreuz. */
  --panel-scroll-edge: clamp(0.5rem, 1.5vw, 0.9rem);
  position: absolute;
  top: var(--panel-scroll-edge);
  right: var(--panel-scroll-edge);
  bottom: var(--panel-scroll-edge);
  z-index: 1;
  width: 3px;
  pointer-events: none;

  /* Im Ruhezustand unsichtbar. Sichtbar wird die Leiste nur, solange
     tatsaechlich gescrollt wird — main.js setzt dafuer .is-scrolling und
     nimmt es nach kurzer Ruhe wieder weg. Ausgeblendet wird etwas
     gemaechlicher als eingeblendet, damit das Verschwinden nicht abreisst.
     Der Daumen behaelt dabei seine eigene Deckkraft; beide multiplizieren
     sich, sichtbar sind also weiterhin die 0,32. */
  opacity: 0;
  transition: opacity 260ms var(--ease-soft);
}

.panel__scroll.is-scrolling {
  opacity: 1;
  /* Beim Auftauchen praktisch sofort da. */
  transition-duration: 120ms;
}

/* Profile und Racing tragen oben rechts den Close-Button (2rem hoch, mit
   ebendiesem Randabstand). Die Leiste setzt darunter an, damit sie ihn nicht
   kreuzt. Das Contact-Panel hat keinen und braucht den Versatz nicht. */
.panel--story .panel__scroll {
  top: calc(var(--panel-scroll-edge) * 2 + 2rem);
}

.panel__scroll[hidden] {
  display: none;
}

/* Nur der Daumen ist sichtbar — eine Trackflaeche gibt es bewusst nicht.
   Farbe ist das CDF-Hellgrau der Pop-up-Texte, deutlich zurueckgenommen:
   ein Hinweis, kein Bedienelement. Hoehe und Position setzt main.js. */
.panel__scroll-thumb {
  position: absolute;
  right: 0;
  left: 0;
  border-radius: 999px;
  background-color: var(--color-light);
  opacity: 0.32;
}

/* Kapitel wachsen mit ihrem Inhalt, sie werden nicht mehr auf Overlay-Höhe
   gestreckt. Mit den kurzen Texten blieben sonst große Leerflächen stehen.

   Zwischen zwei Kapiteln liegt überall derselbe Abstand: --story-gap. Er ist
   der frühere Abstand (zweimal der Innenrand, gemessene 94,9px) minus 33 %,
   also 63,6px auf dem Desktop. Weil die Kapitel keinen eigenen Innenrand mehr
   haben, gilt dieser Abstand gleichermaßen zwischen Text und nächster
   Überschrift wie zwischen Bildunterschrift und nächstem Bild. */
/* --- Anschnitt des nächsten Kapitels ----------------------------------
   Steht ein Kapitel eingerastet, ist der obere Rand des nächsten unten am
   Bildschirmrand sichtbar. Er ist kein eigenes Element und kein Verlauf — es
   ist das echte nächste Kapitel, und solange dieses nicht aufgedeckt ist,
   liegt die Weichzeichnung des Reveals darauf (Bild blur(14px), Text
   blur(5px) bei 0,3 Deckkraft). Beim Hineinscrollen klart es auf.

   Die Kapitel sind dabei genau so hoch wie ihr Inhalt. Eine Mindesthöhe
   hatte den Anschnitt zwar überall gleich schmal gemacht, hängte kurzen
   Kapiteln aber Leerraum an — im Racing-Pop-up standen zwischen Kapitel 1
   und 2 dann 63,7px, zwischen 2 und 3 aber 243,9px. Gleiche Abstände
   zwischen den Kapiteln wiegen schwerer als ein überall gleich breiter
   Anschnitt. */
.story {
  display: flex;
  min-height: 0;
  scroll-snap-align: start;
  /* Der Fangpunkt liegt um den Kapitelabstand vor dem Kapitel, sonst klebt
     die Überschrift beim Einrasten an der Oberkante. */
  scroll-margin-top: var(--story-gap);
}


.story + .story {
  margin-top: var(--story-gap);
}

/* Reihenfolge im Markup: Text, dann Medium. Kapitel 1 und 4 stapeln
   (Überschrift, Text, Bild darunter), Kapitel 2 und 3 stellen beides
   nebeneinander — bei 2 liegt das Bild links, bei 3 rechts. */
.story__frame {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  gap: clamp(0.875rem, 2vh, 1.5rem);
  width: 100%;
  min-height: 0;
}

.story__text {
  flex: 0 0 auto;
  transition: opacity var(--fade) var(--ease-soft) 100ms,
              filter var(--fade) var(--ease-soft) 100ms;
}


/* Enge Zeilenhöhe: bei den nebeneinander gesetzten Kapiteln liegt die
   Oberkante der Überschrift damit auf der Oberkante des Bildes. Mit der
   geerbten 1.6 säße sie rund 8px tiefer. */
.story__title {
  margin: 0 0 0.4rem;
  font-size: var(--text-story-title);
  font-weight: var(--weight-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-subhead);
}

.story__body {
  margin: 0;
  font-size: var(--text-body-compact);
  line-height: var(--lh-read);
}

/* Absatzabstand. Muss spezifischer sein als .panel p { margin: 0 }. */
.story__body + .story__body {
  margin-top: 0.55rem;
}

/* Das Bild nimmt den Platz, den der Text übrig lässt — dadurch füllt jedes
   Kapitel die Overlay-Fläche exakt aus, egal wie lang sein Text ist, und
   das Bild wird so groß wie eben möglich. */
/* Bild und Bildunterschrift bilden eine Einheit: <figure> übernimmt die
   Rolle, die vorher das Medium im Flex-Layout hatte, das Medium selbst ist
   jetzt der Bildkasten darin. */
.story__figure {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.45rem;
  min-height: 0;
  width: 100%;
  margin: 0;
}

.story__media {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  border-radius: 4px;
  background-color: var(--color-dark);
  transition: filter var(--fade) var(--ease-soft);
}

/* Motiv, Ort und Aufnahmedatum. Bündig mit der Außenkante des Bildes —
   bei Kapitel 1 und 3 rechts, bei 2 und 4 links. Dadurch läuft die Zeile
   immer von der Bildkante nach innen. */
.story__caption {
  flex: 0 0 auto;
  font-size: clamp(0.6875rem, 0.85vw, 0.75rem);
  line-height: var(--lh-ui);
  letter-spacing: var(--tracking-small);
  transition: opacity var(--fade) var(--ease-soft) 100ms,
              filter var(--fade) var(--ease-soft) 100ms;
}

.story--caption-right .story__caption {
  text-align: right;
}

.story--caption-left .story__caption {
  text-align: left;
}

/* <picture> ist nur ein Container für die Format-Auswahl (AVIF/JPEG) —
   display: contents nimmt es aus dem Layout, das <img> zählt direkt. */
.story__media picture {
  display: contents;
}

/* Nimmt <img> und <video> gleichermaßen auf. */
/* Leichte, einheitliche Anmutung über alle vier Motive: die Aufnahmen
   stammen aus verschiedenen Jahren, Kameras und Lichtsituationen. Etwas
   zurückgenommene Sättigung und leicht angehobener Kontrast lassen sie als
   eine Serie lesen, ohne die Farben zu opfern. Beim Hover geht die Sättigung
   auf volle Stärke — das Bild wacht unter dem Zeiger auf. */
.story__media img,
.story__media video {
  /* Bildausschnitt. Standard ist 1 — das Bild füllt seinen Kasten und wird
     nicht beschnitten. Ein Kapitel kann den Wert erhöhen, um enger auf sein
     Motiv zu gehen (siehe .story--imola); die Verschiebung rückt dabei den
     gewünschten Punkt des Bildes auf die Kastenmitte. Weil skaliert und nicht
     verzerrt wird, bleibt das Seitenverhältnis des Motivs erhalten. */
  --media-zoom: 1;
  --media-shift-x: 0%;
  --media-shift-y: 0%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.88) contrast(1.06) brightness(0.97);
  transition: transform var(--fade) var(--ease-soft),
              opacity var(--fade) var(--ease-soft),
              filter var(--fade) var(--ease-soft);
}

@media (hover: hover) and (pointer: fine) {
  .story.is-revealed .story__media:hover img,
  .story.is-revealed .story__media:hover video {
    filter: saturate(1.04) contrast(1.06) brightness(1);
  }
}

/* --- Imola: enger auf den silbernen Wagen ------------------------------
   Die Aufnahme ist 904 x 1130 und damit genau im 4:5-Kasten — ohne Zutun
   wird nichts beschnitten, und rund ein Drittel der Fläche ist leere
   Fahrbahn über dem Wagen. Nicolas' Wagen liegt bei 37 % / 55 % der
   Bildfläche.

   Der Zoom von 1,4 zeigt 71 % der Kantenlängen, die Verschiebung rückt
   diesen Punkt auf die Kastenmitte. Beschnitten wird dadurch oben 218px und
   unten 105px — überwiegend aus dem oberen Bereich, der Kies- und Grünstreifen
   am unteren Rand bleibt stehen. Der Ausschnitt misst 646 x 807 Pixel und ist
   damit immer noch fast doppelt so breit wie der Kasten (363px): es wird
   weiterhin herunter- und nicht heraufgerechnet. */
.story--imola .story__media img {
  --media-zoom: 1.4;
  --media-shift-x: 13%;
  --media-shift-y: -5%;
}

/* Genau zwei Zeilen. Der Umbruch steht im Markup; damit die ERSTE Zeile
   nicht ihrerseits umbricht, ist der Grad an die Breite des Bildkastens
   gebunden: „Autodromo Internazionale Enzo e Dino Ferrari · Imola ·
   April 25, 2026" misst im Satz 32,27em (mit --tracking-small), gerechnet
   wird mit 32,5em, damit am Zeilenende etwas Luft bleibt.

     1440 x 900: 362,88px / 32,5 = 11,2px statt der sonstigen 12px          */
.story__caption--two-line {
  font-size: min(clamp(0.6875rem, 0.85vw, 0.75rem),
                 calc(var(--half-figure-w) / 32.5));
}

/* Die Quelle steht in der zweiten Zeile und bleibt dort geschlossen. */
.story__credit {
  white-space: nowrap;
}

/* --- Racing-Hero: Kapitel 1 des Racing-Pop-ups ------------------------
   Streckenverlauf, Rennwochenende und Countdown, sonst nichts. Das Kapitel
   hat kein Bild mehr und damit auch keine zweite Spalte: der Block steht
   mittig auf der vollen Kapitelbreite, seine Maße kommen aus .countdown. */
.story--racing-hero .story__frame {
  justify-content: center;
}

.story--racing-hero .story__text {
  width: 100%;
}

/* --- Kapitel 2 und 3: Bild und Text nebeneinander -------------------- */
/* align-items: stretch gibt der Textspalte automatisch dieselbe Höhe wie
   das Bild, die Überschrift beginnt damit auf der Bildoberkante. */

/* align-items: stretch gibt der Textspalte automatisch dieselbe Höhe wie das
   Bild — die Überschrift beginnt damit auf der Bildoberkante statt mittig
   daneben. Zusammen mit der engen Zeilenhöhe von .story__title (--lh-tight)
   liegen Überschriftoberkante und Bildoberkante auf derselben Linie.

   Das ist der Standard für JEDES Kapitel mit Hochformat in JEDEM
   In-Site-Pop-up, nicht die Ausnahme für eines: die Regel greift über
   .story--split, und genau diese Klasse tragen alle nebeneinander gesetzten
   Kapitel beider Pop-ups. Gleiche Auflösung wie auf manuel-beck.com, damit
   die beiden Auftritte dieselbe Satzlogik zeigen. */
.story--split .story__frame {
  flex-direction: row;
  align-items: stretch;
  justify-content: flex-start;
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
}

/* Kapitel 1 stellt das Bild voran — im Markup steht es dort bereits an
   erster Stelle, es braucht also keine Umkehrung. Die Klasse dient nur der
   Lesbarkeit und als Angriffspunkt für Feinheiten. */
.story--media-first .story__frame {
  flex-direction: column;
  justify-content: center;
}

/* Das vorangestellte Querformat behält sein Seitenverhältnis, statt die
   Resthöhe des Kapitels zu füllen. Vorher war der Kasten flacher als 16:9 und
   cover schnitt das Foto oben und unten an; mit dem festen Verhältnis steht
   das ganze Bild. Auf sehr flachen Fenstern greift die max-height und dann
   schneidet object-position: bottom oben ab — die Unterkante bleibt. */
.story--media-first .story__media {
  flex: 0 0 auto;
  height: auto;
  aspect-ratio: 16 / 9;
  max-height: var(--split-media-max-h);
}

/* Der Kasten richtet sich nach dem Video, nicht umgekehrt.

   .story--media-first deckelt die Hoehe mit --split-media-max-h. Beim Foto
   ist das gewollt: es darf oben anschneiden. Beim Video kippt genau dieser
   Deckel das Seitenverhaeltnis — auf flachen Fenstern wurde aus dem
   16:9-Kasten ein Streifen von bis zu 5,4:1, und object-fit: cover schnitt
   dafuer bis zu zwei Drittel der Bildhoehe weg. Gemessen bei 1280x300:
   709x132 statt 709x399.

   Ohne Deckel folgt die Hoehe immer der Breite ueber die aspect-ratio, das
   Bild bleibt vollstaendig und unverzerrt. Auf sehr flachen Fenstern wird
   das Kapitel dadurch hoeher als der Ausschnitt — das faengt der Scroller
   des Pop-ups ab, so wie bei jedem anderen langen Kapitel auch. Bei
   normalen Fensterhoehen aendert die Regel nichts: dort hat der Deckel nie
   gegriffen. */
.story--racing-video .story__media {
  max-height: none;

  /* Der einzige grossflaechige Hintergrund hinter dem Video ist der des
     Pop-ups. Der Kasten bringt keinen eigenen mehr mit, damit hier nichts
     als zusaetzliche Flaeche gelesen werden kann. */
  background-color: transparent;
}

/* Von der Aufnahme darf nichts wegfallen.

   Der Clip bringt seinen eigenen dunkelblauen Rahmen mit — er gehoert zur
   Komposition, das Logo-Outro am Ende steht darauf. Beschnitten werden darf
   deshalb nur dieser Rahmen, nie die Aufnahme darin: Das TopJet-Wasserzeichen
   sitzt bei x 1557-1588 und hat zur Bildkante bei x 1600 nur zwoelf Pixel
   Luft. Wie weit gezoomt werden darf, steht am --media-zoom unten.

   contain statt des geerbten cover: Kasten und Frame sind beide 16:9, beide
   zeigen also dasselbe Bild. cover wuerde aber bei jeder Rundung im
   Kastenmass die Ueberlaenge abschneiden — contain kann das grundsaetzlich
   nicht und laesst im Zweifel lieber einen Sub-Pixel-Streifen des
   Kastenhintergrunds stehen, der ohnehin dieselbe Farbe traegt. Skaliert
   wird gleichmaessig, das Seitenverhaeltnis bleibt exakt erhalten. */
.story--racing-video .story__media video {
  object-fit: contain;

  /* Warum das Bild vorher so klein wirkte: Der Clip ist 1920x1080, die
     Aufnahme darin aber nur 1280x720, mittig auf einem dunkelblauen Rahmen
     (cropdetect: 1280:720:320:180). Der Kasten stand also bereits auf voller
     Kapitelbreite — zwei Drittel davon waren Rahmen, ein Drittel Luft.

     1,45 holt die Aufnahme auf 96,7 % der Kastenbreite. Weggeschnitten
     werden dabei 298 von 320 Rahmenpixeln je Seite und 167 von 180 oben und
     unten: ausschliesslich Rahmen, kein einziger Bildpunkt. Das TopJet-
     Wasserzeichen endet bei x 1588, sichtbar ist bis x 1622 — 34 Pixel Luft.

     Nicht weiter: bei 1,5 laege die Kante exakt auf der Bildkante und dem
     Wasserzeichen blieben nur noch 12 Pixel. Die verbleibenden 22 Rahmen-
     pixel je Seite sind rund 15 CSS-Pixel und tragen fast denselben Ton wie
     der Pop-up-Hintergrund (#081D32 gegen #0A1F33) — sie lesen sich als
     Hintergrund, nicht als Kasten.

     Skaliert wird gleichmaessig ueber den vorhandenen Mechanismus des
     Designsystems (siehe .story--imola), das Seitenverhaeltnis bleibt
     dadurch exakt erhalten. */
  --media-zoom: 1.45;
}

/* Kapitel 4: Bild links. Das Medium ist im Markup das zweite Element,
   row-reverse zieht es nach vorn. */
.story--media-left .story__frame {
  flex-direction: row-reverse;
}

/* Das Hochformat nimmt einen festen ANTEIL DER BREITE ein, die Höhe folgt
   aus dem Seitenverhältnis. Umgekehrt (Höhe zuerst) fraß das Bild auf hohen,
   schmalen Fenstern die ganze Zeile und die Textspalte kollabierte.

   42 % der Breite — aber nie mehr, als die verfügbare Höhe im 4:5-Format
   trägt. Ohne diese zweite Grenze bliebe die Spalte bei 42 % stehen, während
   aspect-ratio + max-height nur den Bildkasten schmaler machen: die
   Bildunterschrift läge dann auf voller Spaltenbreite und damit neben dem
   Bild statt darunter. Betrifft flache Fenster (Telefon quer). */
.story--split .story__figure {
  flex: 0 0 auto;
  width: min(42%, calc(var(--split-media-max-h) * 4 / 5));
}

/* --- Mittelachse ------------------------------------------------------
   Diese Kapitel teilen die Textfläche exakt in zwei Hälften: die Textspalte
   ist 50 % breit, die Bildspalte 50 % minus Spaltenabstand. Damit fällt EINE
   senkrechte Linie zusammen — die waagerechte Mitte des Pop-up-Fensters:

     · die rechte Kante der Standings-&-Results-Tabelle
     · der linkeste Punkt des Streckenverlaufs
     · die untere linke Ecke der ersten Countdown-Kachel

   Der Bildkasten behält seine Deckelung über die verfügbare Höhe, damit er
   auf flachen Fenstern nicht über das Kapitel hinauswächst. */
.story--half .story__frame {
  gap: var(--split-gap);
}

.story--half .story__text {
  flex: 0 0 50%;
  max-width: 50%;
}

.story--half .story__figure {
  width: min(calc(50% - var(--split-gap)), calc(var(--split-media-max-h) * 4 / 5));
}

.story--split .story__media {
  flex: 0 0 auto;
  width: 100%;
  height: auto;
  min-height: 0;
  aspect-ratio: 4 / 5;
  /* Nie höher als der Innenraum eines Kapitels. */
  max-height: var(--split-media-max-h);
}

.story--split .story__text {
  flex: 1 1 auto;
  align-self: stretch;
}

/* --- Reveal ------------------------------------------------------------ */
/* Kein Einrollen mehr, sondern ein weiches Auftauchen: die Weichzeichnung
   löst sich auf, das Bild fährt minimal auf Maßstab zurück, der Text zieht
   simultan nach. */

.story:not(.is-revealed) .story__media {
  filter: blur(14px);
}

.story:not(.is-revealed) .story__media img,
.story:not(.is-revealed) .story__media video {
  opacity: 0.65;
  transform: scale(calc(var(--media-zoom) * 1.06))
             translate(var(--media-shift-x), var(--media-shift-y));
}

.story:not(.is-revealed) .story__text,
.story:not(.is-revealed) .story__caption,
.story:not(.is-revealed) .countdown {
  opacity: 0.3;
  filter: blur(5px);
}

.story.is-revealed .story__media {
  filter: blur(0);
}

.story.is-revealed .story__media img,
.story.is-revealed .story__media video {
  opacity: 1;
  transform: scale(var(--media-zoom))
             translate(var(--media-shift-x), var(--media-shift-y));
}

.story.is-revealed .story__text,
.story.is-revealed .story__caption,
.story.is-revealed .countdown {
  opacity: 1;
  filter: blur(0);
}

/* Leichter Zoom erst, wenn das Medium fertig aufgedeckt ist */
@media (hover: hover) and (pointer: fine) {
  .story.is-revealed .story__media:hover img,
  .story.is-revealed .story__media:hover video {
    transform: scale(calc(var(--media-zoom) * 1.06))
               translate(var(--media-shift-x), var(--media-shift-y));
  }
}

/* --- Countdown auf das nächste Rennwochenende ------------------------- */
/* Steht in Kapitel 1 des Racing-Pop-ups in der Textspalte, das Hochformat
   liegt links daneben. Die Spalte teilt sich in drei Zeilen: oben die Hälfte
   für den Streckenverlauf, darunter zwei Viertel für Ort/Datum und für die
   Zähler — dieselbe Fläche, die vorher das Querformat einnahm.

   Reveal-Behandlung wie ein Bildkasten, siehe Reveal-Abschnitt. */
.countdown {
  /* --- EIN Faktor für den ganzen Block ---------------------------------
     Ort, Datum, Kicker und die vier Kacheln stehen um ein Drittel größer als
     zuvor. Der Faktor sitzt an genau einer Stelle, damit die drei Zeilen und
     die Kacheln nicht auseinanderlaufen.

     Auf flachen Fenstern fällt er auf 1 zurück: die Spalte ist dort nur so
     hoch wie das Hochformat daneben, und der vergrößerte Satz liefe unten
     aus dem Block heraus. */
  /* 1,8 statt 1,33: das Kapitel trägt seit dem Wegfall des Helmbildes nur
     noch diesen Block, und er nimmt die ganze Kapitelfläche ein. */
  --countdown-scale: 1.8;

  /* Zeilenabstand im Kopf (Kicker → Ort → Datum). */
  --countdown-gap: calc(0.2rem * var(--countdown-scale));

  /* --- Kachel ---------------------------------------------------------
     Die Höhe ist nicht gesetzt, sondern gerechnet: Ziffer, Zwischenraum,
     Einheit und zweimal die Luft an Ober- und Unterkante.

     Die Luft ist an den Zifferngrad gebunden und wächst deshalb mit ihm.
     Ursprünglich waren es 0,1617 des Grades, dann 0,2425; jetzt 0,32 — die
     Ziffer steht dadurch freier im Modul, und weil die Kachelhöhe unten aus
     Ziffer, Einheit und zweimal dieser Luft GERECHNET wird, wächst das Modul
     im selben Verhältnis mit. Oben über der Ziffer und unten unter der
     Einheit steht damit in allen vier Modulen derselbe Betrag. */
  --tile-value-size: calc(clamp(0.8125rem, min(1.5vw, 3.1vh), 1.25rem)
                          * var(--countdown-scale));
  --tile-unit-size: calc(clamp(0.4375rem, min(0.6vw, 1.3vh), 0.5rem)
                         * var(--countdown-scale));
  --tile-inner-gap: calc(0.08rem * var(--countdown-scale));
  --tile-air: calc(var(--tile-value-size) * 0.32);
  /* Fuge zwischen zwei Modulen, senkrecht zur gemeinsamen Kante gemessen.
     Sichtbar, aber zurückhaltend — und wie alles hier an --countdown-scale
     gebunden, damit sie mitwächst. 1440 × 900: 7,2px. */
  --tile-gap: calc(0.25rem * var(--countdown-scale));
  --tile-h: calc(var(--tile-value-size) * 1.05
                 + var(--tile-inner-gap)
                 + var(--tile-unit-size) * 1.25
                 + 2 * var(--tile-air));
  /* Breite: die Blockbreite abzüglich des 45°-Versatzes, geviertelt — das
     Maß der früheren vierreihigen Anordnung — und um denselben Faktor
     gewachsen. */
  --tile-w: calc((100% - var(--tile-h) * var(--slant-tan)) / 4
                 * var(--countdown-scale));

  /* Drei Zeilen, die ihre Höhe aus dem Inhalt nehmen; den freien Rest
     verteilt align-content: space-between zu GLEICHEN Teilen auf die beiden
     Zwischenräume. Genau das ist gefordert: der Abstand vom Streckenverlauf
     zur Zeile „Next race weekend" ist damit derselbe wie der von dort zur
     Oberkante der Kacheln — unabhängig davon, wie groß der Satz ausfällt.

     Oben schliesst der Streckenverlauf an der Kante ab, unten die
     Kachelfläche — und weil die Spalte genau so hoch ist wie der Bildkasten
     daneben, endet sie auf der Unterkante des Helmbildes. */
  display: grid;
  grid-template-rows: auto auto auto;
  align-content: space-between;
  justify-items: stretch;
  /* Der Block steht allein im Kapitel und mittig darin. Seine Höhe ist die
     volle Kapitelhöhe (Overlay ohne Innenrand), seine Breite an dieselbe
     Größe gebunden — dadurch bleibt das Verhältnis von Streckenverlauf,
     Kopfzeilen und Kachelband auf jedem Fenster dasselbe.

     Die dritte Grenze ist die Notbremse für schmale Fenster. Das Kachelband
     ist BREITER als der Block: --tile-w rechnet mit --countdown-scale, und
     dazu kommen die waagerechte Fuge und der 45°-Überhang beider Reihen.
     Seine Gesamtbreite misst

       2 × --tile-w + Fuge × sec + (2 × Kachelhöhe + Fuge) × tan

     Auf dem Desktop liegt sie noch bequem in der Textspalte; auf Telefonen
     lief das Band darüber hinaus und erzeugte waagerechten Scroll. Nach
     --tile-w aufgelöst und in eine Höchstbreite für den Block zurückgerechnet
     bleibt das Band ab hier immer innerhalb der Spalte. */
  width: min(100%,
             calc(var(--split-media-max-h) * 0.8),
             calc((100% - var(--tile-gap) * var(--slant-sec)
                   - (2 * var(--tile-h) + var(--tile-gap)) * var(--slant-tan))
                  * 2 / var(--countdown-scale)
                  + var(--tile-h) * var(--slant-tan)));
  margin-inline: auto;
  height: var(--split-media-max-h);
  min-height: 0;
  transition: opacity var(--fade) var(--ease-soft) 100ms,
              filter var(--fade) var(--ease-soft) 100ms;
}

/* Streckenverlauf. EINE durchgehende Kurve, als geschlossene Bézier-Kette
   gesetzt.

   Strichstärke: doppelt DIE Strichstärke der Seite. Als Haarlinie las sich
   der Verlauf ungleichmäßig, weil die Kantenglättung bei gut 1px je nach
   Winkel unterschiedlich viel Deckung erzeugt. vector-effect (im Markup
   gesetzt) hält den Strich in jeder Größe auf genau diesem Maß, er kann
   also nirgends dünner oder dicker werden. Farbe ist das CDF-Hellgrau des
   Fließtextes, ohne Abschwächung. */
.countdown__track {
  /* aspect-ratio ist die Proportion des KASTENS, nicht die der Zeichnung.

     .countdown ist ein Grid mit FESTER Höhe (--split-media-max-h) und drei
     auto-Zeilen. Eine auto-Zeile schrumpft nicht: wird der Kasten hier zu
     hoch, sprengen die drei Zeilen zusammen die feste Höhe und laufen unten
     aus dem Kapitel heraus — über den Tabellenstand darunter. Der Kasten
     muss also flacher bleiben als das, was Kopf und Kachelband übriglassen.

     Dieser Wert stammt vom Querformat-Verlauf und hält genau diese Reserve
     ein; die drei Zeilen behalten damit ihre gleichmäßigen Abstände aus
     align-content: space-between. Das SVG skaliert seinen viewBox über das
     voreingestellte preserveAspectRatio hinein und zentriert ihn darin, der
     Kasten ist also unabhängig von der Form der jeweiligen Strecke — ein
     Hochformat wie Mugello steht mittig und wird nicht beschnitten.

     Wer hier eine andere Proportion einträgt, muss gegenprüfen, dass
     scrollHeight von .countdown die clientHeight nicht übersteigt. */
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 622.87 / 350.66;
  overflow: visible;
}

.countdown__track-line {
  fill: none;
  stroke: var(--color-light);
  stroke-width: calc(var(--line-w) * 2);
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* Mittig unter dem Streckenverlauf und deutlich größer — der Platz kommt
   daher, dass die vier Kacheln jetzt in einer Reihe stehen statt in zwei. */
.countdown__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--countdown-gap);
  text-align: center;
}

/* Kein Kicker mehr: die Zeile steht in Gemischtschreibung und trägt damit
   auch keine Sperrung — eine Sperrung von 0,2em ist ein Versalien-Mittel und
   zerfiele auf Kleinbuchstaben zu lose gesetzten Einzelzeichen.

   Grad und Farbe wie der Fließtext der Pop-ups: derselbe Grad, den der
   Tabellenstand darunter trägt („Austria Formel Cup"), und das volle
   CDF-Hellgrau ohne Abschwächung. Dasselbe gilt für die Datumszeile. */
.countdown__label {
  margin: 0;
  font-size: calc(var(--text-body-compact) * var(--countdown-scale));
  line-height: var(--lh-ui);
}

/* Vierte Zeile: der volle Reihenname unter dem Datum. Deutlich laenger als die
   drei Zeilen darueber und deshalb bewusst OHNE --countdown-scale.

   .countdown ist ein Grid mit fester Hoehe, dessen auto-Zeilen nicht
   schrumpfen (siehe .countdown__track). Diese Zeile kommt zu einem bereits
   vollen Kopf hinzu, faellt also am staerksten ins Gewicht: mit dem Faktor
   1,8 liefe sie mehrzeilig und schoebe das Kachelband aus dem Kapitel.

   Verschaerfend haengt die Blockbreite selbst an --split-media-max-h — ein
   flaches Fenster macht den Block schmaler UND niedriger, der Text umbricht
   dort also oefter, wo am wenigsten Platz ist. Ein Faktor kleiner 1 scheidet
   aus: auf schmalen Fenstern ist --countdown-scale bereits auf 1 gesetzt, ein
   zusaetzlicher Faktor drueckte den Grad dort unter 9px.

   text-wrap: balance verteilt die Woerter gleichmaessig auf die Zeilen. */
.countdown__series {
  margin: 0;
  /* 0,86 statt voller Fliesstextgrad: bei vollem Grad umbricht die Zeile auf
     schmalen Bloecken zweizeilig, und JEDE weitere Zeile schiebt das
     Kachelband aus dem Kapitel. Bei diesem Wert bleibt sie auf allen
     geprueften Fenstern einzeilig. Weiter zu verkleinern bringt fast nichts:
     den Platz frisst die Zeile selbst, nicht der Grad. */
  font-size: calc(var(--text-body-compact) * 0.86);
  line-height: var(--lh-ui);
  text-wrap: balance;
}

/* Ortsname im Grad der Kapitelüberschrift daneben („Standings & Results").
   align-items: baseline statt center: nur so lässt sich die Flagge auf die
   Versalhöhe des Wortes setzen (siehe .countdown__flag). */
.countdown__place {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  margin: 0;
  font-size: calc(var(--text-story-title) * var(--countdown-scale));
  font-weight: var(--weight-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-subhead);
  text-transform: uppercase;
}

.countdown__date {
  margin: 0;
  font-size: calc(var(--text-body-compact) * var(--countdown-scale));
  line-height: var(--lh-ui);
}

/* Die Flagge ist gezeichnet, kein Emoji: als Emoji brächte sie die
   Systemschrift und deren Rundungen mit. Die Ecken sind abgerundet wie die
   Bildkästen der Pop-ups.

   Sie steht genau auf der Versalhöhe des Ortsnamens: „BRNO" läuft in
   Versalien, seine sichtbare Höhe ist damit die Versalhöhe von Saira
   (--cap-height, 0,688 em) und reicht von der Grundlinie nach oben. Die
   Flagge ist genau so hoch, ihr Seitenverhältnis 3:2 gibt die Breite. Weil
   der Kasten keinen Textinhalt hat, ist seine Grundlinie die Unterkante —
   align-items: baseline im Elternelement setzt sie damit auf die Grundlinie
   des Wortes. Oberkante und Unterkante liegen so auf Oberkante und
   Unterkante von „BRNO". */
.countdown__flag {
  display: block;
  flex: 0 0 auto;
  height: calc(1em * var(--cap-height));
  width: calc(1em * var(--cap-height) * 3 / 2);
  line-height: 0;
}

.countdown__flag svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Vier Kacheln in ZWEI Reihen, mittig unter der Datumszeile:

     DAYS  HRS
     MIN   SEC

   MIN und SEC sind die vormals dritte und vierte Kachel — sie stehen jetzt
   links unter DAYS und HRS statt rechts daneben. Die Kachelbreite bleibt
   dabei genau die von vorher: vier Kacheln, drei Spaltenabstände und der
   45°-Versatz auf der Blockbreite. Nur die Anordnung hat sich geändert, nicht
   das Maß.

   Der Zeilenabstand ist --countdown-gap, also derselbe Abstand, der zwischen
   „Next race weekend" und „BRNO" liegt: die Unterkante der DAYS-Kachel sitzt
   damit genau so weit über der Oberkante der MIN-Kachel, und ebenso HRS über
   SEC.

   justify-content: center stellt den Block auf die Mittelachse des
   Countdowns — dieselbe Achse, auf der Ort und Datum darüber zentriert sind.
   Einen Randausgleich braucht es dafür nicht: skewX() schert um die Mitte des
   Kastens, die Fläche ragt links und rechts um gleich viel darüber hinaus und
   ihre optische Mitte IST die Kastenmitte. */
/* --- Die vier Kacheln sind VIER getrennte Module ----------------------
   Sie stehen in zwei Reihen und zwei Spalten und berühren sich nirgends:

     DAYS  HRS
     MIN   SEC

   Zwischen je zwei benachbarten Modulen liegt derselbe Abstand, und zwar
   SENKRECHT ZUR GEMEINSAMEN KANTE gemessen — nur so ist er an allen vier
   Fugen gleich breit:

     DAYS|HRS und MIN|SEC  treffen sich an ihren 45,10°-Kanten. Ein
       senkrechter Abstand --tile-gap heißt dort waagerecht
       --tile-gap × sec(45,10°).
     DAYS|MIN und HRS|SEC  treffen sich an ihren Waagerechten. Dort IST der
       senkrechte Abstand der Zeilenabstand.

   Die untere Reihe rückt zusätzlich nach links, damit die geneigten Kanten
   der beiden Reihen auf EINER Geraden weiterlaufen — die Fuge unterbricht
   sie, sie knickt aber nicht. Der Versatz bemisst sich an der Strecke, die
   zwischen den beiden Kanten liegt: Kachelhöhe plus Fuge.

   Ausgerichtet wird die GESAMTE Fläche: weil die Anordnung punktsymmetrisch
   ist, fallen Umriss-Mitte und Flächenschwerpunkt zusammen — eine optische
   Korrektur über die geometrische Mitte hinaus braucht es nicht. Gegenüber
   der Rastermitte liegt diese Mitte um die halbe waagerechte Fuge nach
   rechts und um den halben Reihenversatz nach links verschoben; translateX
   nimmt genau diese Differenz zurück. */
.countdown__clock {
  --tile-gap-x: calc(var(--tile-gap) * var(--slant-sec));
  --tile-row-shift: calc((var(--tile-h) + var(--tile-gap)) * var(--slant-tan));

  display: grid;
  grid-template-columns: repeat(2, var(--tile-w));
  justify-content: center;
  gap: 0;
  transform: translateX(calc((var(--tile-row-shift) - var(--tile-gap-x)) / 2));
}

/* Waagerecht wird über left verschoben und nicht über einen Rand: die
   Kachelbreite kommt aus der Rasterspur (--tile-w rechnet mit 100 % der
   Blockbreite und darf deshalb nur DORT stehen), und ein Rand würde sie
   verändern. Senkrecht dagegen muss der Rand greifen — die untere Reihe soll
   die Zeilenhöhe wirklich um eine Strichstärke verkürzen.

   Untere Reihe: an die Waagerechte der oberen herangezogen UND um den
   Schrägversatz nach links, damit die geneigten Kanten durchlaufen.
   Rechte Spalte: an die Schrägkante der linken herangezogen. */
.countdown__tile:nth-child(2) {
  left: var(--tile-gap-x);
}

.countdown__tile:nth-child(3),
.countdown__tile:nth-child(4) {
  margin-top: var(--tile-gap);
}

.countdown__tile:nth-child(3) {
  left: calc(-1 * var(--tile-row-shift));
}

.countdown__tile:nth-child(4) {
  left: calc(var(--tile-gap-x) - var(--tile-row-shift));
}

/* Umrandung wie die Schaltflächen der Startseite: das i-Pünktchen der
   Wortmarke — 45,10° Neigung, gerundet nur auf den beiden stumpfen Ecken.
   Der Rahmen ist ein eigenes Kästchen, damit die Ziffern aufrecht stehen. */
.countdown__tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tile-inner-gap);
  height: var(--tile-h);
  min-width: 0;
  padding: 0 0.3rem;
}

/* Rückfallebene ohne JavaScript, siehe .action::before. Die Farbe steht
   dagegen hier UND auf dem gezeichneten Rahmen: linke Spalte (DAYS, MIN)
   rubinrot, rechte Spalte (HRS, SEC) orange — der Wechsel der beiden
   Querbalken der Wortmarke, senkrecht gestellt. Volle Deckkraft: beides sind
   Akzentfarben, und dem Rubinrot bliebe abgeschwächt kein Kontrast. */
.countdown__tile::before {
  content: "";
  position: absolute;
  inset: 0;
  border: calc(var(--line-w) * 2) solid var(--tile-line);
  border-radius: var(--action-frame-radius) 0 var(--action-frame-radius) 0;
  transform: skewX(var(--slant));
}

/* Spaltenweise: links DAYS und MIN rubinrot, rechts HRS und SEC orange —
   der Balkenwechsel der Wortmarke, senkrecht gestellt. Die Beschriftung
   bleibt in allen vier Modulen im CDF-Hellgrau. */
.countdown__tile:nth-child(odd) {
  --tile-line: var(--color-ruby);
}

.countdown__tile:nth-child(even) {
  --tile-line: var(--color-orange);
}

.countdown__tile > .frame {
  top: 0;
  left: calc(-0.5 * var(--tile-h) * var(--slant-tan));
  width: calc(100% + var(--tile-h) * var(--slant-tan));
  height: var(--tile-h);
}

.countdown__tile > .frame .frame__shape {
  stroke: var(--tile-line);
  /* Dieselbe Strichstärke wie der Streckenverlauf darüber: doppelt DIE
     Strichstärke der Seite. Beide Formen gehören sichtbar zum selben
     System. main.js liest die gerechnete Stärke aus und setzt den Pfad
     entsprechend nach innen — der Strich bleibt im Kasten. */
  stroke-width: calc(var(--line-w) * 2);
}

.countdown__value {
  position: relative;
  font-size: var(--tile-value-size);
  font-weight: var(--weight-bold);
  line-height: 1.05;
  letter-spacing: var(--tracking-subhead);
  font-variant-numeric: tabular-nums;
}

.countdown__unit {
  position: relative;
  font-size: var(--tile-unit-size);
  line-height: 1.25;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  text-transform: uppercase;
  opacity: 0.65;
}

/* --- Tabellenstand und Rennergebnisse -------------------------------- */
/* Steht links neben dem Hochformat in Kapitel 2 des Racing-Pop-ups.

   Beide Bloecke — die vier Fuehrungen und die Rennliste — liegen auf
   DEMSELBEN Raster aus drei gleichen Dritteln: Strecke | Austria Formel Cup |
   TopJet F2000. Die Punktangabe der Fuehrungen sitzt rechtsbuendig im
   dritten Drittel und schliesst damit exakt dort ab, wo die TopJet-Spalte der
   Tabelle endet. Links beginnen beide an derselben Kante.

   Alle Trennlinien sind 1px stark und haben denselben Farbwert — dieselbe
   Strichstaerke wie die Umrandung der Countdown-Kacheln und der
   Schaltflaechen. Unterschiedliche Deckkraft wuerde sich als
   unterschiedliche Dicke lesen.

   Durchgehend abgekuerzt, weil die Spalte nur rund 470px breit ist:
   P1 statt „1. Platz", Pts statt „Punkte", FL fuer die schnellste Runde. */

.standings,
.results {
  /* Trennlinien im CDF-Hellgrau, halb so dick wie DIE Strichstärke der
     Seite — in diesem Block tragen sie nur die Gliederung, nicht die Form. */
  --rule: var(--color-light);
  --rule-w: calc(var(--line-w) / 2);
  font-size: var(--results-font);
}

/* Tabellenstand und Rennliste tragen EINEN Schriftgrad und EINE Textfarbe:
   --results-font (Rechnung dort) und das volle CDF-Hellgrau des Fließtextes.
   Ausgezeichnet ist nur, was die Aussage trägt — die orangen Platzangaben.
   Die Quellenangabe behält allein ihre zurückgenommene Deckkraft. */
.standings {
  width: var(--results-w);
  /* Kein Abstand nach unten: darunter steht die Fußnote des Tabellenstandes,
     und die sitzt genauso dicht unter ihrer Tabelle wie „*Technical issue."
     unter der Rennliste — dort trennt die beiden ebenfalls nur der
     Innenabstand der letzten Zellenzeile. Den Abstand zur Rennliste trägt
     jetzt die Fußnote selbst. */
  margin: 0;
  padding: 0;
  list-style: none;
}

.standings__row {
  display: grid;
  /* Drei echte Spalten: Platz | Serienname | Wert. Die erste Spur ist so
     breit wie der Einzug, den „Austria Formel Cup" bisher hatte (2,1em) —
     alle vier Seriennamen beginnen dadurch auf derselben senkrechten Linie.
     Die mittlere nimmt den Rest, die dritte genau ihre Textbreite und
     schliesst rechtsbuendig auf der Tabellenkante ab. */
  grid-template-columns: 2.1em 1fr auto;
  align-items: baseline;
  padding-block: 0.3rem;
  border-top: var(--rule-w) solid var(--rule);
  line-height: var(--lh-ui);
}

.standings__row:last-child {
  border-bottom: var(--rule-w) solid var(--rule);
}

/* Die Fuehrung ist die Aussage des Blocks — sie steht in der Akzentfarbe und
   ist die einzige Stelle im Pop-up, an der Orange auftritt. */
.standings__pos {
  color: var(--color-orange);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-subhead);
}

.standings__name {
  /* Rechts ein Zeichenabstand, damit der laengste Name („Austrian Racing Car
     Championship") nicht an die Wertspalte stoesst. */
  padding-right: 1em;
}

/* Die Punktangabe schliesst rechts ohne Zellabstand ab: ihr letztes Zeichen
   liegt damit genau auf der rechten Kante des Helmbildes — dieselbe
   senkrechte Linie, auf der darunter das „s" von „Pts" endet. */
.standings__pts {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.results {
  width: var(--results-w);
  /* fixed: ohne die Festlegung zieht die Streckenspalte alle Breite an sich
     und die sechs Ergebnisspalten brechen um. */
  table-layout: fixed;
  border-collapse: collapse;
  line-height: var(--lh-ui);
  font-variant-numeric: tabular-nums;
}

/* --- Spaltengeometrie ------------------------------------------------
   Drei Blöcke: Strecke | Austria Formel Cup | TopJet F2000. Die beiden
   Serienblöcke sind genau so breit wie ihr TITEL — nur dann beginnt „R1"
   auf derselben senkrechten Linie, auf der der Titel darüber beginnt, und
   nur dann endet „TopJet F2000 · Gold" auf der rechten Tabellenkante, also
   auf derselben Linie wie „after 4/6" im Tabellenstand darüber. Im Satz
   gemessen, mit Kerning und Tabellenziffern:

     Strecke, längster Eintrag  „16–18 Oct · Season finale"   11,96em
     Austria, Serientitel       „Austria Formel Cup · AF3-T"  12,17em
     TopJet,  Serientitel       „TopJet F2000 · Gold"          9,00em

   Gefordert ist ausserdem: der Abstand von der Streckenspalte zur
   Austria-Spalte ist genauso groß wie der von der Austria-Spalte zur
   TopJet-Spalte. Mit SA = Beginn der Austria-Spalte, L/R = linke/rechte
   Tabellenkante:

     SA − (L + 11,687em) = (R − 9,010em) − (SA + 12,181em)
     2·SA = L + R − 9,504em
     SA − L = --results-w / 2 − 4,752em          <- Breite der Streckenspalte

   Die Lücke selbst misst --results-w / 2 − 16,439em; sie liegt als Überhang
   in der letzten Austria-Zelle.

   Innerhalb der Serien tragen R1 und R2 in BEIDEN Serien dieselbe Breite:
   3,575em. Damit ist der Abstand von R1 zu R2 und von R2 zu Pts überall
   gleich — und enger als zuvor (4,167em), ohne dass „Racing" (3,15em) an
   die Nachbarspalte stösst. Pts nimmt jeweils den Rest seines Blocks; bei
   TopJet sind das 1,860em, das Maß der dreistelligen Zahl.

   Alle sieben Breiten stehen in em, keine in Prozent: bei
   table-layout: fixed löst Chrome eine Prozentbreite gegen die Tabelle auf
   und lässt den em-Teil eines calc() dabei fallen — aus
   calc(50 % − 4,752em) wurde schlicht 50 %, und die beiden Prozentspalten
   teilten den Rest zu gleichen Teilen unter sich auf. Die Tabelle ist
   ohnehin genau 37,5em breit (--results-font ist --results-w / 37,5), die
   sieben Werte summieren sich also auf ihre volle Breite:

     13,998 + 3,575 + 3,575 + 7,342 + 3,575 + 3,575 + 1,860 = 37,5

   Die Zellen tragen KEINEN waagerechten Innenabstand: die Spaltenkanten sind
   die Textkanten, sonst wäre keines der Maße oben mehr exakt. */
.results__col-round {
  width: 13.998em;
}

.results__col-af-r1,
.results__col-af-r2,
.results__col-tj-r1,
.results__col-tj-r2 {
  width: 3.575em;
}

.results__col-af-pts {
  width: 7.342em;
}

.results__col-tj-pts {
  width: 1.860em;
}

.results th,
.results td {
  padding: 0.22rem 0;
  text-align: center;
  vertical-align: baseline;
  font-weight: var(--weight-regular);
}

/* Die Serientitel stehen LINKSBÜNDIG in ihrer Spalte. Damit beginnt jeder
   Titel auf genau derselben senkrechten Linie wie die Werte darunter: „R1"
   trägt denselben Zellabstand, sein Text startet also an derselben Kante wie
   der Titel darüber.

   Die obere Trennlinie: der Tabellenstand darüber beginnt mit einer Linie und
   endet mit einer — die Rennliste tut das jetzt ebenso. Die Linie läuft über
   alle Zellen der Zeile, die leere Streckenzelle eingeschlossen. */
.results__series th,
.results__series td {
  border-top: var(--rule-w) solid var(--rule);
  text-align: left;
  padding-top: 0.24rem;
  padding-bottom: 0.28rem;
  white-space: nowrap;
}

/* Der Titel der zweiten Serie steht rechtsbündig und endet damit auf der
   rechten Kante der Tabelle — dieselbe Linie, auf der im Tabellenstand
   darüber „after 4/6" endet. Weil seine Spalte genau seine Breite hat,
   beginnt er trotzdem an derselben Linie wie „R1" darunter. */
.results__series th:last-child {
  text-align: right;
}

/* Round beginnt auf genau derselben Linie wie „Mugello" darunter — beide
   ohne Zellabstand. Der Selektor führt .results mit, damit er die zwei
   Klassen von „.results th" überbietet. */
.results .results__round-head {
  padding-left: 0;
}

/* Die Linie unter den beiden Serientiteln laeuft ueber die ganze Breite —
   deshalb sitzt sie an der Unterkante ALLER Zellen der Kopfzeile, die
   Streckenspalte eingeschlossen. */
.results__subhead th,
.results__subhead td {
  border-top: var(--rule-w) solid var(--rule);
  border-bottom: var(--rule-w) solid var(--rule);
  padding-block: 0.24rem;
  text-align: left;
}

/* Die Werte stehen linksbündig unter ihrer Spaltenüberschrift — mit
   tabular-nums beginnen sie damit alle auf derselben Linie. Ausnahme ist die
   Punktespalte, siehe .results__pts. */
.results tbody td,
.results tfoot td {
  text-align: left;
}

/* Ohne Zellabstand links: Streckenname und Gesamtzeile beginnen damit auf
   derselben Kante wie „Round" darüber und wie die linke Kante des
   Helmbildes. Vorher standen sie um den Spaltenabstand von 0,15rem
   eingerückt. */
.results tbody th,
.results tfoot th {
  text-align: left;
  padding-left: 0;
}

/* Zeilenabstand der Rennzeilen: halbiert gegenüber den 0,22rem, die die
   Kopf- und Fußzeile weiter tragen. Gefordert war die Halbierung genau
   zwischen den bestrittenen und den bevorstehenden Rennen. */
.results tbody th,
.results tbody td {
  padding-block: 0.11rem;
}

.results__row th {
  padding-right: 0.4rem;
}

.results__place {
  display: block;
}

.results__date {
  display: block;
}

/* Die Punktespalte ist die letzte ihrer Serie und schliesst rechts ohne
   Zellabstand ab. Bei TopJet F2000 ist das die rechte Kante der Tabelle:
   das „s" von „Pts" und die Zahlen darunter liegen damit genau auf der
   rechten Kante des Helmbildes — dieselbe Linie wie die Punktangabe des
   Tabellenstandes darüber. Bei Austria Formel Cup gilt dasselbe an der
   Grenze zum zweiten Drittel. */
/* Die Punktespalte ist die dritte ihrer Serie und liegt im Raster wie die
   beiden davor: gleiche Breite, gleicher Zellabstand, linksbündig. R1, R2
   und Pts beginnen dadurch in beiden Serien auf drei gleichabständigen
   Linien. Ausgezeichnet ist nur die Stärke — die Punkte sind die Aussage
   der Zeile. */
.results .results__pts {
  font-weight: var(--weight-bold);
}

.results .results__subhead .results__pts {
  font-weight: var(--weight-regular);
}

.results__note {
  text-align: left;
}

/* Trennt die bevorstehenden Starts von allem, was gelaufen ist. */
.results__row--last-next td,
.results__row--last-next th {
  border-bottom: var(--rule-w) solid var(--rule);
}

.results__row--next .results__place {
  color: var(--color-orange);
  font-weight: var(--weight-bold);
}

.results__mark {
  vertical-align: super;
  font-size: 0.8em;
}

.results tfoot th,
.results tfoot td {
  border-top: var(--rule-w) solid var(--rule);
  /* Abschlusslinie wie beim Tabellenstand darüber. */
  border-bottom: var(--rule-w) solid var(--rule);
  padding-top: 0.32rem;
}

/* Abstand zur letzten Zeile halbiert, wie die Zeilenabstände darüber.

   Die Breite ist die der Tabelle und nicht die der Textspalte: sonst liefen
   die Fußnoten über die rechte Kante der Tabelle — und damit über die rechte
   Kante des Helmbildes — hinaus. Grad und Farbe wie der Block darüber, nur
   die Deckkraft bleibt zurückgenommen. */
.results__footnote {
  width: var(--results-w);
  font-size: var(--results-font);
  line-height: var(--lh-ui);
  opacity: 0.55;
}

/* Beide Fußnoten stehen gleich weit unter der Abschlusslinie ihrer Tabelle.
   Das Maß ist der Innenabstand, den die Fußzeile der Rennliste bisher schon
   unter „Total Points" trug: 0,22rem. Der Selektor führt das Element mit —
   .panel p setzt weiter oben margin: 0 und wäre sonst spezifischer. */
p.results__footnote {
  margin: 0.22rem 0 0;
}

/* Die Fußnote des Tabellenstandes steht zwischen den beiden Blöcken: oben im
   Fußnotenabstand zum Tabellenstand, unten im Abstand, den vorher der
   Tabellenstand selbst zur Rennliste hielt. */
p.results__footnote--standings {
  margin-bottom: clamp(0.75rem, 1.8vh, 1.1rem);
}

/* Schließen-Button */
.panel__close {
  position: absolute;
  top: clamp(0.5rem, 1.5vw, 0.9rem);
  right: clamp(0.5rem, 1.5vw, 0.9rem);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--color-dark);
  color: var(--color-light);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: opacity 160ms var(--ease-soft);
}

.panel__close:hover,
.panel__close:focus-visible {
  opacity: 0.7;
}

/* 9. Subseiten ------------------------------------------------------------ */

/* Zurück-Link oben, der zweispaltige Block mittig im verbleibenden Raum. */
.content {
  flex: 1;
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 0.25rem 0;
}

/* Zurück-Pfeil oben links, oberhalb der deutschen Fassung. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
  font-size: 15px;
  font-weight: var(--weight-regular);
  text-decoration: none;
  color: inherit;
}

.back-link:hover,
.back-link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.back-link .icon {
  width: 0.95em;
  height: 0.95em;
}

/* Zwei Spalten mit senkrechter Trennlinie mittig auf der Seite:
   deutsche Fassung links, englische rechts. */
.legal-grid {
  display: grid;
  /* Mittelspalte ist die Trennlinie — dieselbe Strichstärke wie überall. */
  grid-template-columns: 1fr var(--line-w) 1fr;
  column-gap: clamp(2rem, 5vw, 4.5rem);
  align-items: stretch;
  margin-block: auto; /* mittig im Raum unter dem Zurück-Link */
}

.legal-grid__rule {
  background-color: currentColor;
}

/* Beide Fassungen rücken zur Trennlinie hin: der Abstand zur Linie ist nur
   noch halb so groß wie bei mittiger Ausrichtung. Der Rest des freien
   Platzes liegt außen.
   (100% − Blockbreite) ist der freie Platz in der Spalte; ein Viertel davon
   innen, per auto der Rest außen. */
.legal {
  --legal-width: 38rem;
  /* Ein Viertel des freien Platzes läge bei mittiger Ausrichtung innen.
     Davon nochmal 33 % abgezogen — die Fassungen rücken damit ein weiteres
     Drittel an die Trennlinie heran. */
  --legal-inner-gap: max(0px, calc((100% - var(--legal-width)) / 4 * 0.67));
  max-width: var(--legal-width);
}

.legal-grid > .legal:first-child {
  margin-left: auto;
  margin-right: var(--legal-inner-gap);
}

.legal-grid > .legal:last-child {
  margin-left: var(--legal-inner-gap);
  margin-right: auto;
}

.legal__title {
  margin: 0 0 0.55rem;
  font-size: clamp(1.375rem, 2.6vw, 1.9rem);
  font-weight: var(--weight-bold);
  line-height: var(--lh-title);
  letter-spacing: var(--tracking-title);
  /* „Datenschutzerklärung" ist lang — darf umbrechen statt zu überlaufen. */
  overflow-wrap: break-word;
  hyphens: auto;
}

/* Abschnittsüberschriften. Die Ebene unterscheidet sich zwischen den
   Spalten (h2 deutsch / h3 englisch, weil die englische Fassung unter einer
   h2 hängt), das Aussehen ist über diese Klasse identisch. */
.legal__heading {
  margin: 0.7rem 0 0.2rem;
  font-size: 1.0625rem;
  font-weight: var(--weight-bold);
  line-height: var(--lh-ui);
  letter-spacing: var(--tracking-subhead);
}

.legal p {
  margin: 0 0 0.5rem;
  font-size: var(--text-body);
  /* Beide Rechtsseiten teilen sich diesen Wert. Etwas enger als der
     Fließtext sonst, damit die Datenschutzerklärung samt Footer ohne
     Scrollen auf eine Bildschirmhöhe passt — mehr geht bei diesem
     Textumfang nicht, ohne dass der Footer unter die Kante rutscht. */
  line-height: var(--lh-ui);
}

.legal p:last-child {
  margin-bottom: 0;
}

/* Gleiches Verhalten wie im Footer und im Contact-Panel: keine statische
   Unterstreichung, sie erscheint erst beim Überfahren.
   font: inherit hält Safari auf iOS davon ab, Telefonnummern selbst
   umzuformatieren. */
.legal a {
  color: inherit;
  font: inherit;
  text-decoration: none;
}

.legal a:hover,
.legal a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Auf den Rechtsseiten rückt der Footer näher an den Text — der gewonnene
   Platz geht in den Zeilenabstand. */
.page--dark .footer {
  margin-top: 1rem;
}

/* Beide Rechtsseiten teilen sich Typografie und Ausrichtung. Der Blocksatz
   auf der Datenschutzseite ist entfallen: in den schmalen Spalten riss er die
   Wortabstände sichtbar auseinander, und die beiden Seiten sahen ohne Grund
   unterschiedlich aus. */

/* 10. Responsive ---------------------------------------------------------- */

/* --- Tablet quer: Overlay nutzt mehr Fläche ---------------------------- */
@media (max-width: 1024px) {
  :root {
    --about-width: min(90vw, 900px);
  }
}

/* --- Ab hier stapeln die nebeneinander gesetzten Kapitel ---------------
   Deckt Tablets im Hochformat (768px) und alle Telefone ab. Nebeneinander
   bliebe die Textspalte zu schmal, um noch angenehm zu lesen. */
@media (max-width: 820px) {
  :root {
    --about-frame: clamp(1.25rem, 5vw, 1.75rem);
    --text-body-compact: 0.875rem;
    /* Gestapelt liegt das Helmbild über dem Text und nicht mehr daneben —
       die Tabelle richtet sich dann an der Textbreite aus. */
    --results-w: var(--content-w);
  }

  /* column-reverse stellt das Bild voran — im Markup steht bei diesen
     Kapiteln der Text zuerst. Nebeneinander trägt der Text die Spalte, aber
     untereinander würde eine Textwand vor dem Bild stehen. */
  .story--split .story__frame,
  .story--media-left .story__frame {
    flex-direction: column-reverse;
    justify-content: flex-end;
  }

  /* Gestapelt liegt die Textspalte nicht mehr neben dem Hochformat, sie hat
     die volle Breite. Ohne diese Rücknahme blieb sie auf halber Breite
     stehen — flex-basis bezieht sich in column-reverse auf die HÖHE, und
     max-width griff weiter waagerecht. Tabellenstand, Rennliste und die
     Kachelfläche des Countdowns liefen dadurch über ihre eigene Spalte
     hinaus. */
  .story--half .story__text {
    flex: 0 0 auto;
    max-width: none;
  }

  /* Und die Countdown-Spalte nimmt hier ihre Inhaltshöhe: sie steht nicht
     mehr neben dem Hochformat, dessen Höhe zu übernehmen ergibt gestapelt
     keinen Sinn — der Satz lief darunter aus dem Block heraus. Die beiden
     Abstände (Streckenverlauf → Kopf → Kacheln) stehen dann als ein fester
     Wert und sind damit weiter exakt gleich groß. */
  .countdown {
    height: auto;
    align-content: start;
    row-gap: clamp(1rem, 4vw, 1.75rem);
  }

  /* Bild und Bildunterschrift bleiben eine Einheit: die Spalte ist genau so
     breit wie der Bildkasten und sitzt mittig im Kapitel.

     Vorher lief die Spalte über die ganze Overlay-Breite, während
     aspect-ratio + max-height nur den Bildkasten schmaler machten. Die
     Bildunterschrift richtete sich dadurch an der Overlay-Kante aus statt an
     der Bildkante — bei Kapitel 3 (rechtsbündig) stand sie sichtbar neben
     dem Bild, bei Kapitel 4 lief sie darüber hinaus. Auf iPad und
     Android-Tablets im Hochformat war das der auffällige Fall.

     Zwei Werte je Eigenschaft: ältere Android-Browser ohne svh (Chrome < 108,
     Samsung Internet < 21, alte WebViews) nehmen die vh-Zeile und ignorieren
     die zweite. Ohne den Rückfall fiele dort die ganze Deckelung aus. */
  .story--split .story__figure {
    flex: 0 0 auto;
    align-self: center;
    width: min(100%, calc(52vh * 4 / 5));
    width: min(100%, calc(52svh * 4 / 5));
  }

  .story--split .story__media {
    width: 100%;
    aspect-ratio: 4 / 5;
    max-height: 52vh;
    max-height: 52svh;
  }

  /* Querformate behalten ihr Seitenverhältnis, bekommen aber eine
     Mindesthöhe, damit sie nicht zum Streifen schrumpfen. Spaltenbreite nach
     derselben Regel wie oben — greift auf niedrigen, breiten Schirmen
     (Android-Tablet quer bei ≤ 820px Breite). */
  .story:not(.story--split) .story__figure {
    flex: 0 0 auto;
    align-self: center;
    width: min(100%, calc(40vh * 16 / 9));
    width: min(100%, calc(40svh * 16 / 9));
  }

  .story:not(.story--split) .story__media {
    flex: 0 0 auto;
    height: auto;
    aspect-ratio: 16 / 9;
    min-height: 0;
    max-height: 40vh;
    max-height: 40svh;
  }

  /* Kleiner als am Desktop kalibriert — dort trägt die Zeilenlänge, hier
     wirkte derselbe Grad zu dominant neben den Bildern. Der Wert steht am
     Token und nicht an der Regel: Fließtext, Tabellenstand, Rennliste und
     die beiden Countdown-Zeilen tragen denselben Grad und dürfen hier nicht
     auseinanderlaufen. */
  .story__body {
    line-height: var(--lh-read);
  }

  .story__title {
    font-size: 1.0625rem;
  }

  .story__caption {
    font-size: 0.6875rem;
  }

  /* Ruhigeres Scrollen: kein Einrasten, schwächere Weichzeichnung.
     Grosse blur()-Flächen kosten auf Mobilgeräten spürbar Bildrate. */
  .about {
    scroll-snap-type: none;
  }

  .story {
    scroll-snap-align: none;
  }

  .story:not(.is-revealed) .story__media {
    filter: blur(8px);
  }

  .story:not(.is-revealed) .story__text,
  .story:not(.is-revealed) .story__caption,
  .story:not(.is-revealed) .countdown {
    filter: blur(3px);
  }
}

/* --- Rechtsseiten nebeneinander: zeilenweise Ausrichtung ----------------
   Jede Spalte trug vorher ihren eigenen Textfluss: eine deutsche Einleitung
   über drei Zeilen und eine englische über vier ließen alle folgenden
   Überschriften auseinanderlaufen.

   Beide Fassungen sind jetzt selbst Raster und übernehmen per
   grid-template-rows: subgrid die Zeilen des Elternrasters. Damit teilen
   sich das n-te Kind links und das n-te Kind rechts dieselbe Zeile — die
   Zeile ist so hoch wie der längere der beiden Blöcke, und jede Überschrift
   startet links wie rechts exakt auf derselben Höhe. Beide Fassungen haben
   dieselbe Blockfolge, deshalb passt die Zuordnung Kind für Kind.

   Wichtig: die Fassungen bleiben dabei EIN Kasten (kein display: contents).
   Nur so behalten sie ihre Shrink-to-fit-Breite und rücken als Block an die
   Trennlinie — sonst stünde der kurze deutsche Impressumstext ganz links am
   Seitenrand statt neben der Linie.

   Unterhalb von 861px stapeln die Spalten (siehe unten); dort greift diese
   Regel nicht. */
@media (min-width: 861px) {
  .legal-grid {
    /* Mehr Zeilen als der längste Text braucht — überzählige Zeilen sind
       leer und damit 0 hoch. Datenschutz nutzt 13, Impressum 3. */
    grid-template-rows: repeat(14, auto);
    align-items: start;
  }

  .legal {
    display: grid;
    grid-row: 1 / -1;
    grid-template-rows: subgrid;
    align-content: start;
  }

  .legal-grid > .legal:first-child {
    grid-column: 1;
  }

  .legal-grid > .legal:last-child {
    grid-column: 3;
  }

  .legal-grid__rule {
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: stretch;
  }

  /* Als eigene Rasterzelle kollabiert der obere Rand der Überschrift nicht
     mehr mit dem unteren des Absatzes davor. 0.5rem (Absatz) + 0.2rem ergibt
     wieder die 0.7rem, die vorher durch das Kollabieren entstanden. */
  .legal__heading {
    margin-top: 0.2rem;
  }
}

/* --- Rechtsseiten: Spalten stapeln ------------------------------------- */
@media (max-width: 860px) {
  .legal-grid {
    grid-template-columns: 1fr;
    row-gap: clamp(2rem, 6vh, 3.5rem);
  }

  .legal-grid__rule {
    height: var(--line-w);
  }

  .legal-grid > .legal:first-child,
  .legal-grid > .legal:last-child {
    display: block;
    margin-left: 0;
    margin-right: 0;
  }

  .legal p {
    line-height: var(--lh-read);
    margin-bottom: 0.7rem;
  }

  .legal__heading {
    margin: 1.25rem 0 0.3rem;
  }
}

/* --- Telefon ----------------------------------------------------------- */
@media (max-width: 700px) {
  :root {
    /* Overlay füllt die Fläche, der zweizeilige Footer bleibt darunter frei. */
    --about-width: calc(100vw - 1.5rem);
    /* Der Footer steht hier zweizeilig — die Fußnotenhöhe für die
       Overlay-Berechnung wächst entsprechend mit. */
    --footer-line: calc(var(--footer-size) * var(--lh-ui) * 2 + 0.35rem);
  }

  /* Auf Telefonen ist der Streifen zwischen Hero-Zeile und Wortmarke deutlich
     kürzer, das Panel aber schmaler und dadurch von Haus aus höher. Schrift
     und Innenrand werden entsprechend kleiner, damit oben und unten Luft
     bleibt; verankert ist es wie am Desktop. */
  /* Auf Telefonen ist das Panel schon an seiner Maximalbreite
     (100vw − Seitenränder), der Text braucht dort also die meisten Zeilen.
     12px statt 13px holt die Höhe zurück, die die Ansprechpartner-Zeile
     kostet — siehe die Anmerkung zum Streifen bei .panel--contact. */
  .panel--contact {
    font-size: 12px;
    padding-block: clamp(0.4rem, calc(11vh - 92px), 2.5rem);
  }

  .contact__lead {
    margin-bottom: 0.55rem;
  }

  /* Footer untereinander, zentriert */
  .footer {
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
  }

}

/* Kleines Telefon im Querformat oder sehr kurzes Fenster: dort ist der
   Streifen zwischen Hero-Zeile und Wortmarke am knappsten. */
@media (max-width: 700px) and (max-height: 700px) {
  .panel--contact {
    font-size: 12px;
  }
}

/* Sehr kleine Schirme (iPhone SE der ersten Generation und schmaler): der
   Streifen misst dort keine 150px mehr. Schrift und Innenrand gehen so weit
   herunter, wie es lesbar bleibt, damit das Panel Claim und Wortmarke nicht
   überdeckt. */
@media (max-width: 360px) and (max-height: 600px) {
  .panel--contact {
    font-size: 11px;
    padding-block: 0.4rem;
    padding-inline: 0.9rem;
  }

  .panel--contact .contact__lead {
    margin-bottom: 0.5rem;
  }
}

/* --- Touch: größere Ziele, kein Hover-Zoom ----------------------------- */
@media (hover: none) {
  .panel__close {
    width: 2.75rem;
    height: 2.75rem;
    font-size: 1.15rem;
  }

  /* Die Social-Icons sind knapp 30px groß — als Tippziel zu klein. Das
     Padding vergrößert nur die Trefferfläche, das Icon bleibt gleich groß;
     der negative Rand hält den gemessenen Abstand zur Fußnote exakt. */
  .social__icons {
    margin: -0.5rem 0;
  }

  .social__link {
    padding: 0.5rem 0.55rem;
    margin: 0 -0.55rem;
  }

  /* Fußnotenlinks bekommen dieselbe Behandlung. */
  .footer a {
    padding: 0.4rem 0;
    margin: -0.4rem 0;
  }

  /* Dasselbe für das LinkedIn-Zeichen: die Marke ist knapp 11px groß und als
     Tippziel zu klein. Die Fläche kommt als ::after RINGSUM gleich weit über
     das Zeichen hinaus — sie bleibt also darauf zentriert und lässt Kasten,
     Zeilenhöhe und Wortabstand unberührt. Ohne Zeiger gibt es keinen Cursor,
     der die größere Fläche verraten könnte. */
  .contact__social::after {
    content: "";
    position: absolute;
    /* 0,35rem = 5,6px: schmaler als der Abstand zum Wort davor (6,76px). Die
       Fläche bleibt damit auf dem Zeichen und greift nicht in den Namen. */
    inset: -0.35rem;
  }

  .about {
    -webkit-overflow-scrolling: touch;
  }
}

/* --- Flache Fenster: Countdown ohne Vergrößerung -----------------------
   Die Countdown-Spalte ist nur so hoch wie das Hochformat neben ihr. Wird
   das Fenster flacher, reicht die Höhe für den um ein Drittel größeren Satz
   nicht mehr, und die untere Kachelreihe liefe unter die Bildkante. Ab hier
   gilt deshalb wieder der ungestreckte Satz; die Abstände oben und unten
   bleiben dabei durch align-content: space-between weiter gleich groß. */
@media (max-height: 640px) {
  .countdown {
    --countdown-scale: 1;
  }
}

/* --- Sehr flache Fenster (Telefon quer) --------------------------------
   Hier reicht der Platz unter der Tagline nicht mehr für das Contact-Panel,
   deshalb rückt es in die Bildschirmmitte. */
@media (max-height: 560px) {
  /* Zwei Stellschrauben statt vier: die Hero-Größe und die Abstandseinheit.
     Über --hero-size zieht die Wortmarke mit (ihre Höhe IST die Höhe des
     Claims), über die Einheit bleiben Tagline, Icon-Reihe, „Follow Lenzi"
     und die Fußnote im Verhältnis 1u : 1u : 1u : 2u zueinander. */
  :root {
    --hero-size: clamp(2rem, 8vh, 3.5rem);
    --space-unit: clamp(0.625rem, 2.2vh, 1.125rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }

  .about {
    scroll-snap-type: none;
  }

  .story:not(.is-revealed) .story__media,
  .story:not(.is-revealed) .story__text,
  .story:not(.is-revealed) .story__caption,
  .story:not(.is-revealed) .countdown {
    filter: none;
    opacity: 1;
  }
}

/* 11. Intro-Overlay -------------------------------------------------------- */
/* Ein vorgeschalteter Opener, kein zweiter Seitenaufruf: Das Overlay liegt
   ueber der bereits geladenen Startseite und nimmt sich nach dem Video selbst
   aus dem DOM. Kein Redirect, kein Reload.

   Alles haengt an html.js — dieselbe Bedingung wie bei Abschnitt 4. Ohne
   JavaScript wird die Klasse nie gesetzt, das Overlay bleibt display: none
   und die Seite ist sofort da. Damit kann ein toter Layer niemanden
   aussperren. */
.intro {
  display: none;

  /* Ausblendzeit des Overlays. Bewusst laenger als --fade-panel: der Schnitt
     vom schnellen Logo auf die Startseite soll entschleunigt wirken. Der
     Ruhepunkt davor steht als HOLD im Skript. */
  --intro-fade-out: 1.4s;
}

.js .intro {
  position: fixed;
  inset: 0;
  /* Ueber allem: Panels liegen auf 20, die Scrollleiste auf 1. */
  z-index: 1000;
  display: block;
  background-color: var(--color-dark);
  transition: opacity var(--intro-fade-out) var(--ease-soft);
}

/* Setzt das Skript nach dem ended-Ereignis; unmittelbar danach faellt das
   Element aus dem Dokument. */
.js .intro.is-done {
  opacity: 0;
  pointer-events: none;
}

/* Der ganze Frame bleibt stehen: contain skaliert nur, es schneidet nie.
   object-position steht per Vorgabe auf 50 % 50 %, das Bild sitzt also in
   beiden Achsen mittig. Bleibt seitlich oder oben und unten Flaeche uebrig,
   zeigt sie den Hintergrund des Overlays — dasselbe CDF-Dunkelblau, das auch
   der Clip selbst traegt. */
.intro__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;

  /* Fade-in: Der Clip beginnt unsichtbar und wird eingeblendet, sobald
     Bilddaten vorliegen — vorher gaebe es nur eine leere Flaeche zu faden.
     Nur Opazitaet, keine Bewegung, kein Massstab. */
  opacity: 0;
  transition: opacity var(--fade-panel) var(--ease-soft);
}

.intro__video.is-visible {
  opacity: 1;
}

/* Abschnitt 4 friert mit body.is-preload * { transition: none !important }
   die ganze Seite ein, bis das Intro freigibt — und trifft damit auch dieses
   Overlay. Ohne Ausnahme spraenge die Deckkraft hart, statt zu blenden:
   gemessen sprang der Fade-in des Clips von 0,000 auf 1,000 innerhalb eines
   Frames. Die beiden Regeln nehmen genau die zwei Elemente des Intros aus.
   !important gegen !important entscheidet die hoehere Spezifitaet — die
   Universalregel wiegt nur (0,1,0), diese hier (0,2,0). */
body.is-preload .intro {
  transition: opacity var(--intro-fade-out) var(--ease-soft) !important;
}

body.is-preload .intro__video {
  transition: opacity var(--fade-panel) var(--ease-soft) !important;
}

/* Solange das Intro laeuft, steht die Seite darunter still. Das Overlay
   faengt Zeiger und Klicks ohnehin ab, hier geht es um Rad und Trackpad. */
.is-intro,
.is-intro body {
  overflow: hidden;
}
