  /* ------------------------------------------------------------------
     FARBEN
     Uebernommen aus holdline/theme/colors.js, der einzigen Quelle der App.
     Beide Paletten sind dort auf Kontrast durchgerechnet (WCAG 4.5:1), die
     Werte werden hier NICHT neu erfunden.

     Drei Zustaende, nicht zwei: wer nichts einstellt, bekommt gar keine
     Markierung am <html>, dann entscheidet allein prefers-color-scheme.
     ------------------------------------------------------------------ */
  :root {
    /* WARUM DIESE ZEILE WICHTIGER IST, ALS SIE AUSSIEHT: sie sagt dem Browser,
       in welcher Helligkeit er die Dinge malt, die NICHT uns gehoeren. Die
       Bildlaufleiste der Galerie, die Auswahlmarkierung, die Innereien der
       Eingabefelder.

       Ohne sie nimmt der Browser "hell" an, auch wenn die Seite dunkel ist.
       Genau das war am 17.08. im Test zu sehen: unter der Galerie stand eine
       weisse Windows-Bildlaufleiste quer ueber der dunklen Seite. */
    color-scheme: light;

    --ground: #f4f5f9;
    --surface: #ffffff;
    --text: #15152b;
    --muted: #5f6076;
    --accent: #007085;
    --line: #dfe2ec;
    --ring-track: #dfe2ec;
    --shadow: 0 1px 2px rgb(21 21 43 / 6%), 0 12px 32px rgb(21 21 43 / 8%);

    /*
     * DIE BREITE DER LESESPALTE, an EINER Stelle.
     *
     * Sie stand vorher dreimal im Blatt: am .wrap, im Raster der Abschnitte
     * und im Innenmass der Galerie. Drei Zahlen, die zusammenpassen muessen,
     * sind zwei Zahlen zu viel: wer eine aendert und die anderen vergisst,
     * bekommt eine Galerie, deren erste Karte schief zur Ueberschrift steht.
     *
     * 88rem sind 1408 Pixel. Absaetze bleiben unabhaengig davon bei 34em
     * (siehe `p`-Regel), es wird also nur die STRUKTUR breiter, nie der
     * Lesetext.
     */
    --spalte: 88rem;
    --pad: clamp(1.25rem, 5vw, 3rem);

    /* Der Bandton liegt bewusst UNTER dem Grund, nicht darueber. So bleiben
       die Karten darin (die auf --surface stehen) weiterhin erhaben. Ein
       hellerer Ton haette sie verschluckt. */
    --band: #eaedf5;
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) {
      color-scheme: dark;

      --ground: #1a1a2e;
      --surface: #232345;
      --text: #e0e0e0;
      --muted: #9494ad;
      --accent: #00b4d8;
      --line: #2e2e52;
      --ring-track: #2e2e52;
      --shadow: 0 1px 2px rgb(0 0 0 / 30%), 0 12px 32px rgb(0 0 0 / 35%);
      --band: #14142a;
    }
  }

  :root[data-theme='dark'] {
    color-scheme: dark;

    --ground: #1a1a2e;
    --surface: #232345;
    --text: #e0e0e0;
    --muted: #9494ad;
    --accent: #00b4d8;
    --line: #2e2e52;
    --ring-track: #2e2e52;
    --shadow: 0 1px 2px rgb(0 0 0 / 30%), 0 12px 32px rgb(0 0 0 / 35%);
    --band: #14142a;
  }

  /* ------------------------------------------------------------------
     SCHRIFT
     System-Schrift, so wie topics/format-rules.md es fuer die App
     festlegt. Der Charakter kommt aus Groesse, Gewicht und Laufweite,
     nicht aus einer fremden Schriftart. Eine geladene Webschrift waere
     ausserdem eine Wette darauf, dass sie ankommt.
     ------------------------------------------------------------------ */
  * { box-sizing: border-box; }

  html { -webkit-text-size-adjust: 100%; }

  /* ------------------------------------------------------------------
     ANKERSPRUENGE UNTER DER GEPINNTEN KOPFLEISTE (17.08.)
     ------------------------------------------------------------------
     Die Kopfleiste klebt seit dem 17.08. oben. Ein Ankersprung scrollt das
     Ziel aber an die OBERKANTE des Fensters, also unter die Leiste: die
     erste Zeile jedes angesprungenen Abschnitts war verdeckt.

     `scroll-padding-top` auf <html> loest das fuer ALLE Anker auf einmal,
     auch fuer solche, die spaeter dazukommen. Die Alternative waere ein
     `scroll-margin-top` an jedem einzelnen Ziel, und genau das vergisst man
     beim naechsten neuen Abschnitt.

     Sanftes Scrollen gehoert hierher und nicht ins Javascript, weil der
     Browser es selbst kann. Wer im System "Bewegung reduzieren" gesetzt hat,
     bekommt den harten Sprung.
     ------------------------------------------------------------------ */
  html {
    scroll-padding-top: 5.5rem;
    scroll-behavior: smooth;
  }
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
  }

  body {
    margin: 0;
    background: var(--ground);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
      'Helvetica Neue', Arial, sans-serif;
    font-size: 17px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
  }

  .wrap {
    /* Von 68rem auf 88rem am 17.08. Auf einem breiten Bildschirm standen
       vorher rund 200 Pixel Leerraum an jeder Seite, und das las sich als
       unfertig statt als Rand. Der Wert steht jetzt als --spalte oben.

       WARUM NICHT GANZ RANDLOS: Fliesstext ueber die volle Breite eines
       1920er Bildschirms ist unlesbar, das Auge verliert beim Ruecksprung
       die Zeile. Deshalb bleibt jeder Absatz bei 34em (`p`-Regel oben) und
       nur die STRUKTUR wird breiter: Karten, Galerie, Diagramme, Figuren.
       Die farbigen Baender weiter unten fuellen den Rest von Kante zu
       Kante, damit gar kein Leerraum mehr entsteht. */
    max-width: var(--spalte);
    margin: 0 auto;
    padding: 0 var(--pad);
  }

  /*
   * DIE ABSCHNITTE LAUFEN VON KANTE ZU KANTE, DER TEXT NICHT.
   *
   * ERSTER VERSUCH UND WARUM ER SCHEITERTE (17.08.): ein Pseudoelement mit
   * `margin-inline: calc(50% - 50vw)` sollte den Hintergrund nach aussen
   * ziehen, und dagegen brauchte es `overflow-x: clip`, weil `50vw` die
   * Bildlaufleiste mitzaehlt. Genau dieses `clip` sass dann am Rahmen, der
   * ausbrechen sollte, und schnitt sein eigenes Band wieder ab. Sichtbar
   * blieb ein Kasten in Spaltenbreite.
   *
   * DIE LOESUNG BRAUCHT KEIN `vw` UND KEIN ABSCHNEIDEN. Jeder Abschnitt ist
   * volle Breite und traegt intern ein Raster aus drei Spalten: aussen zwei
   * flexible, in der Mitte die Lesespalte. Alle Kinder sitzen in der Mitte,
   * der Hintergrund gehoert dem ganzen Abschnitt.
   *
   * Der Nebeneffekt ist der eigentliche Gewinn: wer ausbrechen WILL, sagt
   * `grid-column: 1 / -1`. Genau das braucht die Galerie.
   */
  .sections {
    max-width: none;
    padding: 0;
  }

  .sections > section,
  .sections > footer {
    display: grid;
    grid-template-columns:
      1fr
      min(var(--spalte), 100% - 2 * var(--pad))
      1fr;
  }

  .sections > section > *,
  .sections > footer > * {
    grid-column: 2;
  }

  /* ------------------------------------------------------------------
     DAS ZEICHEN IN DEN FREIEN RANDFLAECHEN (18.08., Balis Wunsch)
     ------------------------------------------------------------------
     Die Abschnitte stehen in einem Raster aus drei Spalten: aussen zwei
     freie, in der Mitte die Lesespalte. Auf einem breiten Bildschirm sind
     die aeusseren zusammen mehrere hundert Pixel breit und komplett leer.
     Genau dort sitzt jetzt das Zeichen.

     WARUM ES NICHT EINFACH EIN HINTERGRUNDBILD AM ABSCHNITT IST: dann
     laege es AUCH unter dem Text, und ein Wasserzeichen unter Fliesstext
     macht ihn schlechter lesbar, egal wie leise es ist. Als eigenes
     Element in der Randspalte kann es nichts verdecken.

     ERST AB 74REM. Darunter gibt es keine freie Randflaeche, dort waere
     es kein Zeichen im Freiraum, sondern eines hinter dem Text.
     ------------------------------------------------------------------ */
  .sections > section > .randzeichen { display: none; }

  @media (min-width: 74rem) {
    .sections > section > .randzeichen {
      display: block;
      grid-row: 1;
      align-self: center;
      justify-self: center;
      width: min(60%, 9rem);
      aspect-ratio: 32 / 22;
      pointer-events: none;
      /* Die Deckkraft sitzt im SVG, nicht hier: `opacity` auf dem Element
         wuerde eine eigene Ebene aufspannen und den Abschnitt darueber
         neu zeichnen lassen. */
      background: no-repeat center / contain
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 22'%3E%3Cg fill='%2315152b' fill-opacity='0.07'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3C/g%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1' fill='%23007085' fill-opacity='0.22'/%3E%3C/svg%3E");
    }
    /* Der Unterstrich ist der Akzent der Marke, deshalb bekommt er im
       Zeichen mehr Deckkraft als die Buchstaben. Sonst verschwindet
       genau das Teil, das HoldLINE von einem H unterscheidet. */
    .sections > section > .randzeichen.links { grid-column: 1; }
    .sections > section > .randzeichen.rechts { grid-column: 3; transform: scaleX(-1); }
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .sections > section > .randzeichen {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 22'%3E%3Cg fill='%23e0e0e0' fill-opacity='0.07'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3C/g%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1' fill='%2300b4d8' fill-opacity='0.28'/%3E%3C/svg%3E");
    }
  }
  :root[data-theme='dark'] .sections > section > .randzeichen {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 22'%3E%3Cg fill='%23e0e0e0' fill-opacity='0.07'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3C/g%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1' fill='%2300b4d8' fill-opacity='0.28'/%3E%3C/svg%3E");
  }

  /* ------------------------------------------------------------------
     DAS ZEICHEN IM HINTERGRUND (17.08., Vorbild trainlogapp.com)
     ------------------------------------------------------------------
     Das HoldLINE-Zeichen liegt gross und sehr leise hinter den farbigen
     Baendern.

     DREI ENTSCHEIDUNGEN, die den Unterschied zwischen Marke und Unruhe
     machen:
     1. NUR IN DEN BAENDERN, nicht auf jedem Abschnitt. Ein Wasserzeichen,
        das ueberall liegt, ist Tapete. So markiert es jeden zweiten
        Abschnitt und bekommt dadurch Bedeutung.
     2. SEHR NIEDRIGER KONTRAST (3 Prozent hell, 4 Prozent dunkel). Es
        soll man es erst merken, wenn man hinsieht. Alles darueber
        kaempft mit dem Text.
     3. ANGESCHNITTEN AM RAND, nicht zentriert. Ein zentriertes Logo
        hinter Text sieht nach Fehler aus, ein angeschnittenes nach
        Absicht.

     Es ist ein inline-SVG in der Hintergrund-Eigenschaft und kostet
     keine Anfrage. `pointer-events` braucht es nicht, weil es kein
     Element ist.
     ------------------------------------------------------------------ */
  .band {
    background-color: var(--band);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='22' viewBox='0 0 32 22'%3E%3Cg fill='%2315152b' fill-opacity='0.045'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right -6vw top 50%;
    background-size: auto min(58%, 26rem);
    opacity: 1;
  }

  /* Die Deckkraft steckt nicht in `opacity` (das wuerde den Text mit
     verblassen), sondern in der Farbe des Zeichens selbst. Deshalb zwei
     Fassungen, eine je Palette. */
  .band::before { content: none; }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .band {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='22' viewBox='0 0 32 22'%3E%3Cg fill='%23e0e0e0' fill-opacity='0.05'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1'/%3E%3C/g%3E%3C/svg%3E");
    }
  }
  :root[data-theme='dark'] .band {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='22' viewBox='0 0 32 22'%3E%3Cg fill='%23e0e0e0' fill-opacity='0.05'%3E%3Crect width='4.3' height='22'/%3E%3Crect x='12.2' width='4.3' height='22'/%3E%3Crect x='4.3' y='8.9' width='7.9' height='4.3'/%3E%3Crect x='20.1' y='17.8' width='11.8' height='4.2' rx='2.1'/%3E%3C/g%3E%3C/svg%3E");
  }

  /* Die Trennlinie oben ist im Band ueberfluessig: der Farbwechsel ist die
     Trennung. Zwei Trennungen uebereinander sehen nach Versehen aus. */
  .band, .band + section { border-top-color: transparent; }

  h1, h2, h3 { text-wrap: balance; margin: 0; }

  h1 {
    font-size: clamp(2.6rem, 8vw, 5rem);
    font-weight: 800;
    letter-spacing: -0.035em;
    line-height: 1.02;
  }

  h2 {
    font-size: clamp(1.7rem, 3.6vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1.1;
  }

  h3 {
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
  }

  p { margin: 0; max-width: 34em; }

  .eyebrow {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent);
  }

  a { color: var(--accent); }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 4px;
  }

  /* ------------------------------------------------------------------
     KOPF
     Bleibt beim Scrollen oben stehen, weil die Seite lang ist und der
     Weg zur Anmeldung sonst ein Zurückscrollen kostet.
     ------------------------------------------------------------------ */
  /* ------------------------------------------------------------------
     DER LAUFSTREIFEN ALS ABSCHNITTS-TRENNER (17.08.)
     ------------------------------------------------------------------
     Er stand zuerst ganz oben ueber dem Kopf und sah dort nach
     Werbebanner aus, weil er neben dem Knopf in der Leiste dieselbe
     Sache zweimal sagte. Jetzt trennt er zwei Abschnitte in der
     Seitenmitte, und dort ist er kein Banner, sondern eine Einladung an
     der Stelle, an der der Besucher genug gesehen hat, um sich zu
     entscheiden.

     ES LAEUFT NUR EIN SATZ, wiederholt. Balis Vorgabe, und sie ist
     richtig: ein Fliessband mit wechselndem Inhalt liest man, ein
     Fliessband mit einem Satz wirkt.

     Hier ist eine CSS-Animation richtig, anders als bei der Galerie: es
     laeuft Text, kein base64-Bild, also darf der Inhalt doppelt im
     Markup stehen.

     KEINE LEEREN STELLEN: die Bahn traegt genug Wiederholungen, um auch
     einen breiten Bildschirm zweimal zu fuellen, und der Versatz betraegt
     genau die Haelfte. Beide Haelften sind gleich, also gibt es keinen
     sichtbaren Sprung und keine Luecke.
     ------------------------------------------------------------------ */
  /* ------------------------------------------------------------------
     DIE BAENDER, ueber die ganze Breite (18.08., Balis Entscheidung)
     ------------------------------------------------------------------
     Am 17.08. waren sie kurz runde Pillen in der Lesespalte. Bali will
     sie ueber den ganzen Bildschirm, und das ist fuer ein Fliessband auch
     das Richtige: eine Pille schneidet die Bewegung an beiden Enden ab,
     ein durchlaufendes Band hat kein Ende, an dem es aufhoert.

     Der Glanz bleibt, die Rundung faellt weg. Was bleibt, ist der
     Schatten nach unten, damit das Band auf der Seite liegt und nicht
     in sie eingelassen wirkt.
     ------------------------------------------------------------------ */
  .beltcta {
    grid-column: 1 / -1;
    margin: 0;
    max-width: none;
    width: 100%;
    background: var(--accent);
    overflow: hidden;
    text-decoration: none;
    display: block;
    position: relative;
    box-shadow: 0 6px 18px rgb(0 112 133 / 18%);
  }

  .beltcta-track {
    display: flex;
    width: max-content;
    animation: ticker-lauf 30s linear infinite;
  }

  /* DIE LUECKE IM UMSCHLAG, gemeldet am 17.08.
     Ein Fliessband aus zwei gleichen Haelften laeuft nur dann ohne Naht,
     wenn EINE Haelfte breiter ist als das Fenster. Sonst ist beim Sprung
     von -50 Prozent zurueck auf 0 der rechte Teil noch leer, und genau
     das war zu sehen. `min-width: 100vw` je Durchgang erzwingt das
     unabhaengig davon, wie breit der Bildschirm ist. */
  .beltcta .run { min-width: 100vw; justify-content: space-around; }

  @keyframes ticker-lauf {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
  }

  .beltcta .run {
    display: flex;
    align-items: center;
    white-space: nowrap;
    padding: 0.8rem 0;
  }

  /* Das zweite Band traegt das Angebot und nicht die Aufforderung, deshalb
     steht es als Umriss statt gefuellt: zwei identisch laute Balken auf
     einer Seite heben sich gegenseitig auf. */
  .beltcta.outline {
    background: transparent;
    border: 2px solid var(--accent);
    box-shadow: none;
  }
  .beltcta.outline .run span { color: var(--accent); }
  .beltcta.outline .run i { color: var(--accent); opacity: 0.4; }
  .beltcta .run span {
    /* Der Abstand steht hier und nicht als gap, damit die Wiederholung
       gleichmaessig ist: gap wuerde zwischen den beiden Haelften der Bahn
       fehlen und genau dort eine Luecke lassen. */
    padding: 0 1.6rem;
    font-size: 0.9375rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ground);
  }
  .beltcta .run i { color: var(--ground); opacity: 0.45; font-style: normal; }

  /* Anhalten bei Beruehrung, dieselbe Regel wie bei der Galerie, damit
     die Seite sich einheitlich verhaelt. Wer klicken will, soll nicht auf
     ein bewegtes Ziel klicken muessen. */
  .beltcta:hover .beltcta-track,
  .beltcta:focus-visible .beltcta-track { animation-play-state: paused; }
  .beltcta:focus-visible { outline: 3px solid var(--text); outline-offset: -3px; }

  /* ------------------------------------------------------------------
     DIE GEPINNTE LEISTE
     ------------------------------------------------------------------
     Sie ist ein direktes Kind von <body>, und nur deshalb klebt sie
     ueber die ganze Seite. Der Kopf darin ist wieder statisch: zwei
     ineinander verschachtelte sticky-Elemente streiten sich um dieselbe
     Kante.
     ------------------------------------------------------------------ */
  /* Jedes Anker-Ziel haelt Abstand zur gepinnten Leiste. Ohne diese Regel
     springt ein Menuepunkt genau so weit, dass seine eigene Ueberschrift
     unter der Leiste liegt, und der Besucher landet mitten im Text. */
  section[id], form[id] { scroll-margin-top: 5.5rem; }

  .topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    background: var(--ground);
    border-bottom: 1px solid transparent;
    transition: border-color 0.2s, box-shadow 0.2s;
  }
  .topbar.stuck {
    border-bottom-color: var(--line);
    box-shadow: 0 4px 16px rgb(21 21 43 / 6%);
  }

  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 0;
  }

  /* ------------------------------------------------------------------
     DIE NAVIGATION (18.08. neu gebaut)
     ------------------------------------------------------------------
     Bis zum 18.08. war sie unter 52rem auf `display: none` gesetzt, also
     auf jedem Telefon komplett unsichtbar. Bei einer langen Einzelseite
     war das noch verschmerzbar, bei drei echten Seiten kaeme man an zwei
     davon gar nicht mehr heran.

     AM HANDY KLAPPT SIE JETZT AUF, statt zu verschwinden. Kein Overlay
     ueber die ganze Seite: drei Punkte brauchen keinen Vollbildschirm,
     und ein Overlay muss man wieder schliessen.
     ------------------------------------------------------------------ */
  header nav {
    display: flex;
    gap: 1.5rem;
    font-size: 0.875rem;
  }

  header nav a {
    color: var(--muted);
    text-decoration: none;
    font-weight: 600;
    white-space: nowrap;
  }

  header nav a:hover { color: var(--text); }

  /* Die Seite, auf der man gerade ist. Nicht nur ueber die Farbe, sondern
     mit einer Linie darunter: Farbe allein unterscheidet sich zu wenig
     vom Hover-Zustand. */
  header nav a[aria-current='page'] {
    color: var(--text);
    border-bottom: 2px solid var(--accent);
    padding-bottom: 0.15rem;
  }

  /* ---- Der Knopf, nur am Handy ---- */
  .menuknopf {
    display: grid;
    gap: 4px;
    width: 42px;
    height: 42px;
    padding: 0;
    place-content: center;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 12px;
    cursor: pointer;
  }
  .menuknopf span {
    display: block;
    width: 18px;
    height: 2px;
    border-radius: 2px;
    background: var(--text);
    transition: transform 0.2s, opacity 0.2s;
  }
  /* Aus drei Strichen wird ein Kreuz. Das ist kein Zierrat: der Knopf
     muss zeigen, dass er jetzt schliesst und nicht noch einmal oeffnet. */
  .menuknopf[aria-expanded='true'] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .menuknopf[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
  .menuknopf[aria-expanded='true'] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  @media (max-width: 51.99rem) {
    /* Reihenfolge am Handy: Logo, Knopf, Menue-Knopf. Das Menue selbst
       klappt darunter auf und schiebt den Inhalt nach unten, statt ihn zu
       ueberdecken. */
    header { flex-wrap: wrap; gap: 0.6rem; }

    header nav {
      display: none;
      order: 3;
      flex-basis: 100%;
      flex-direction: column;
      gap: 0;
      border-top: 1px solid var(--line);
      margin-top: 0.4rem;
      padding-top: 0.2rem;
    }
    header nav.offen { display: flex; }

    header nav a {
      padding: 0.85rem 0.2rem;
      font-size: 1rem;
    }
    header nav a + a { border-top: 1px solid var(--line); }
    header nav a[aria-current='page'] {
      border-bottom: 0;
      padding-bottom: 0.85rem;
      color: var(--accent);
    }

    .navcta { margin-left: auto; }
  }

  @media (min-width: 52rem) {
    .menuknopf { display: none; }
  }

  .navcta {
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--ground);
    background: var(--accent);
    border-radius: 999px;
    padding: 0.5rem 1rem;
    text-decoration: none;
    white-space: nowrap;
  }

  /* ------------------------------------------------------------------
     DER GLANZ (17.08., Balis Wunsch: "so shiny wie wenns Licht auf
     Diamonds scheint")
     ------------------------------------------------------------------
     Ein schmaler, schraeger weisser Streifen laeuft von links nach
     rechts ueber den Knopf.

     DREI ENTSCHEIDUNGEN, die den Unterschied zwischen Glanz und Blinken
     machen:
     1. SCHRAEG, nicht senkrecht. Licht faellt nie im rechten Winkel auf
        eine gewoelbte Flaeche, und 20 Grad reichen, damit das Auge es
        als Reflexion liest statt als Balken.
     2. LANGE PAUSE. Der Lauf dauert gut eine Sekunde, die Pause danach
        rund vier. Ohne Pause ist es ein Lauflicht wie an einer
        Spielhalle, mit Pause ist es ein Aufblitzen.
     3. NICHT DECKEND. Reines Weiss auf 55 Prozent, sonst verschwindet
        die Schrift darunter fuer den Moment des Durchlaufs.
     ------------------------------------------------------------------ */
  .shine { position: relative; overflow: hidden; isolation: isolate; }
  .shine::after {
    content: '';
    position: absolute;
    top: -60%;
    bottom: -60%;
    width: 45%;
    left: -60%;
    background: linear-gradient(
      100deg,
      transparent 0%,
      rgb(255 255 255 / 0%) 25%,
      rgb(255 255 255 / 55%) 50%,
      rgb(255 255 255 / 0%) 75%,
      transparent 100%
    );
    transform: skewX(-20deg);
    animation: glanz 5s ease-in-out infinite;
    pointer-events: none;
  }
  @keyframes glanz {
    /* Der Lauf belegt nur die ersten 22 Prozent der Zeit, der Rest ist
       Pause am rechten Rand, ausserhalb des Knopfes. */
    0%   { left: -60%; }
    22%  { left: 115%; }
    100% { left: 115%; }
  }

  .mark {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: var(--text);
    /* Der Anker darf nicht hoeher werden als die Kopfzeile, sonst
       verschiebt sich die Navigation daneben. */
    line-height: 1;
  }

  /* Das Plaettchen. Kantenlaenge und Rundung im Verhaeltnis wie beim
     iOS-Icon: dort sind es 1024 Punkt mit einer Rundung von rund 22
     Prozent. Bei 34 Punkt sind das 7,5. */
  .mark .glyph {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 7.5px;
    background: #0d1017;
    /* Auf der hellen Fassung braucht das dunkle Plaettchen eine Kante,
       sonst schwebt es als schwarzer Fleck auf Weiss. */
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 8%);
    flex: none;
  }

  .mark .glyph svg { display: block; }

  .mark:hover .wordmark { color: var(--accent); }

  .mark:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 10px;
  }

  /* Das Zeichen als Rechtecke statt als Schrift: eine Schrift im SVG ist
     eine Wette darauf, dass sie ueberall installiert ist, wo gerendert wird.

     KORRIGIERT AM 17.08. Die erste Fassung war frei erfunden: ein H mit
     CYANFARBENEM QUERBALKEN. Das App-Icon und die Wortmarke in
     assets/holdline-logo.svg zeigen aber beide dasselbe andere Motiv, naemlich
     ein helles Zeichen mit einem cyanfarbenen UNTERSTRICH daneben. Aus
     HOLDLINE_ wird im Icon H_, der Balken ist der Cursor und gehoert nicht
     in den Buchstaben hinein. */
  /* Grossbuchstaben und enge Laufweite wie in der Wortmarke. Dort steht
     letter-spacing -4.5 bei font-size 100, also -0.045em.

     Etwas groesser als vorher (1,25rem statt 1,125rem), damit das Wort
     neben dem 34 Punkt hohen Plaettchen nicht zu klein wirkt. */
  .mark .wordmark {
    font-size: 1.25rem;
    font-weight: 900;
    letter-spacing: -0.045em;
    transition: color 0.15s ease;
  }

  .tag {
    font-size: 0.8125rem;
    color: var(--muted);
    letter-spacing: 0.01em;
  }

  /* ------------------------------------------------------------------
     HERO mit der 15-Sekunden-Probe
     Der Kern des Produkts ist keine Funktionsliste, sondern eine Regel.
     Also steht die Regel im Hero und laesst sich anfassen.
     ------------------------------------------------------------------ */
  .hero {
    display: grid;
    grid-template-columns: 1fr;
    gap: clamp(2.5rem, 6vw, 4.5rem);
    align-items: center;
    padding: clamp(2rem, 6vw, 4.5rem) 0 clamp(3rem, 7vw, 5.5rem);
  }

  @media (min-width: 62rem) {
    .hero { grid-template-columns: 1.15fr 0.85fr; }
  }

  .hero .lede {
    margin-top: 1.25rem;
    font-size: clamp(1.0625rem, 1.6vw, 1.1875rem);
    color: var(--muted);
  }

  .hero .lede strong { color: var(--text); font-weight: 700; }

  /* Die Herkunftszeile. Steht direkt ueber der Anmeldung, weil sie die
     Frage beantwortet, die genau dort aufkommt: warum ausgerechnet die. */
  .credit {
    margin-top: 1.5rem;
    padding-left: 0.9rem;
    border-left: 2px solid var(--accent);
    font-size: 0.9375rem;
    color: var(--muted);
  }

  /* Anmeldung */
  .signup { margin-top: 2rem; max-width: 27rem; }

  .row { display: flex; gap: 0.5rem; flex-wrap: wrap; }

  /* AUF DEM HANDY GEHT DER KNOPF UEBER DIE VOLLE BREITE.
     Das Feld bricht bei 390 Pixeln ohnehin in eine eigene Zeile um, und der
     Knopf stand danach als 162 Pixel breiter Rest links daneben. Ein
     Ziel, das man mit dem Daumen treffen soll, sollte nicht das schmalste
     Element der Zeile sein. Ab 34rem ist Platz fuer beides nebeneinander,
     dort darf er wieder auf seine Textbreite schrumpfen. */
  .row > button { flex: 1 1 100%; }

  @media (min-width: 34rem) {
    .row > button { flex: 0 0 auto; }
  }

  input[type='email'] {
    flex: 1 1 13rem;
    min-width: 0;
    padding: 0.85rem 1rem;
    font: inherit;
    font-size: 1rem;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 12px;
  }

  input[type='email']::placeholder { color: var(--muted); }

  button {
    padding: 0.85rem 1.35rem;
    font: inherit;
    font-size: 1rem;
    font-weight: 700;
    color: var(--ground);
    background: var(--accent);
    border: 0;
    border-radius: 12px;
    cursor: pointer;
  }

  button:hover { filter: brightness(1.08); }
  button:active { transform: translateY(1px); }

  .note {
    margin-top: 0.75rem;
    font-size: 0.875rem;
    color: var(--muted);
  }

  .note[data-state='said'] { color: var(--accent); font-weight: 600; }

  /* Die Probe: Ring, Zaehler, Leiter */
  .probe {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 20px;
    padding: clamp(1.5rem, 3vw, 2rem);
    box-shadow: var(--shadow);
    text-align: center;
  }

  /* Das kurze Aufleuchten, wenn ein "Get early access" hierher geschickt
     hat. Ohne es landet der Fokus lautlos und der Besucher sucht, was
     gerade passiert ist. */
  .flash {
    animation: feldblitz 0.9s ease-out;
  }
  @keyframes feldblitz {
    0%   { box-shadow: 0 0 0 0 rgb(0 180 216 / 55%); }
    100% { box-shadow: 0 0 0 14px rgb(0 180 216 / 0%); }
  }

  /* ------------------------------------------------------------------
     DAS BAND AM SEITENENDE (17.08.)
     ------------------------------------------------------------------
     Gleiche Farbe wie der Knopf, aber ueber die GANZE Breite und mit
     dauerhaftem Glanz. Es ist der letzte Aufruf der Seite, also darf es
     hier lauter sein als in der Mitte: wer bis hierher gelesen hat, ist
     entweder ueberzeugt oder weg.

     Der Glanz laeuft hier OHNE lange Pause, anders als am Knopf oben.
     Begruendung: der Knopf sitzt neben Text, den man lesen will, dieses
     Band steht allein am Ende und konkurriert mit nichts.
     ------------------------------------------------------------------ */
  .beltcta.wide { box-shadow: none; }
  .beltcta.wide .run { padding: 1.05rem 0; }
  .beltcta.wide .run span { font-size: 1.0625rem; }

  /* Der Glanz laeuft ueber ALLE Baender, nicht nur das untere (18.08.).
     Sie sind jetzt gleich gebaut, also sollen sie sich auch gleich
     verhalten: drei Baender, von denen eines glaenzt, sehen nach Versehen
     aus. */
  .beltcta::after {
    content: '';
    position: absolute;
    top: -60%;
    bottom: -60%;
    width: 30%;
    left: -40%;
    background: linear-gradient(
      100deg,
      transparent 0%,
      rgb(255 255 255 / 0%) 25%,
      rgb(255 255 255 / 42%) 50%,
      rgb(255 255 255 / 0%) 75%,
      transparent 100%
    );
    transform: skewX(-20deg);
    animation: dauerglanz 3.2s linear infinite;
    pointer-events: none;
  }
  @keyframes dauerglanz {
    from { left: -40%; }
    to { left: 130%; }
  }

  /* Der Store-Hinweis unter dem Formular. Als Pille mit Rand, damit er
     wie ein Knopf AUSSIEHT, ohne einer zu sein: er verspricht nichts,
     was man heute anklicken koennte. */
  .storesoon {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 1.1rem 0 0;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--muted);
  }
  .storesoon svg { flex: none; color: var(--text); }

  /* DIE EINLADUNG ZUM KNOPF (17.08.). Gross, fett, im Akzentton.
     Sie ersetzt eine kleine Ueberzeile plus einen grauen Hinweis, die
     beide zu leise waren fuer das, was sie ausloesen sollen.
     Die Groesse skaliert mit dem Fenster, bleibt aber unter der H1, denn
     die Ueberschrift muss die Seite anfuehren, nicht die Einladung. */
  .invite {
    margin: 0;
    font-size: clamp(1.25rem, 2.6vw, 1.6rem);
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--accent);
    text-wrap: balance;
  }

  .dial { position: relative; width: 13.5rem; height: 13.5rem; margin: 1.25rem auto 0; }

  .dial svg { width: 100%; height: 100%; transform: rotate(-90deg); }

  .dial circle { fill: none; stroke-width: 10; stroke-linecap: round; }

  .track { stroke: var(--ring-track); }

  .fill {
    stroke: var(--accent);
    stroke-dasharray: 565.49;
    stroke-dashoffset: 565.49;
  }

  .readout {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    gap: 0.15rem;
  }

  .secs {
    font-size: 3rem;
    font-weight: 800;
    letter-spacing: -0.04em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
  }

  .secs small { font-size: 1.125rem; font-weight: 700; color: var(--muted); }

  .hint { font-size: 0.8125rem; color: var(--muted); }

  .hold {
    margin-top: 1.25rem;
    width: 100%;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
  }

  .verdict {
    margin: 1rem auto 0;
    min-height: 3.25rem;
    max-width: 22rem;
    font-size: 0.9375rem;
    color: var(--muted);
  }

  .verdict strong { color: var(--text); }
  .verdict[data-state='earned'] strong { color: var(--accent); }

  /* ------------------------------------------------------------------
     ABSCHNITTE
     ------------------------------------------------------------------ */
  section { padding: clamp(3rem, 7vw, 5rem) 0; border-top: 1px solid var(--line); }

  .head { max-width: 42rem; }
  .head p { margin-top: 0.9rem; color: var(--muted); }

  .cards {
    margin-top: 2.5rem;
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  }

  .card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 1.35rem;
  }

  .card p { margin-top: 0.5rem; font-size: 0.9375rem; color: var(--muted); }

  /* Gegenueberstellung: die Konkurrenz rechnet anders */
  .versus {
    margin-top: 2.5rem;
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  }

  .col {
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 1.35rem;
    background: var(--surface);
  }

  .col.ours { border-color: var(--accent); }

  .col h3 { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .col h3 span { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
  .col.ours h3 span { color: var(--accent); }

  .col ul { margin: 1rem 0 0; padding: 0; list-style: none; display: grid; gap: 0.9rem; }
  .col li { font-size: 0.9375rem; color: var(--muted); padding-left: 1.4rem; position: relative; }
  .col li::before { content: '—'; position: absolute; left: 0; color: var(--line); }
  .col.ours li::before { content: '↑'; color: var(--accent); }

  /* ------------------------------------------------------------------
     ROT UND GRUEN IM VERGLEICH (17.08., Balis Wunsch)
     ------------------------------------------------------------------
     Beide Toene sind gegen --surface auf mindestens 4.5:1 gerechnet, also
     dieselbe Schwelle, die `npm run kontrast` in der App prueft. Ein
     knalliges #ff0000 haette auf Weiss nur 4.0:1 und faellt durch.

     DIE FARBE IST NIE DIE EINZIGE AUSSAGE. Rund acht Prozent der Maenner
     haben eine Rot-Gruen-Schwaeche und die Zielgruppe ist ueberwiegend
     maennlich, deshalb traegt jede Zeile zusaetzlich ein Zeichen (✕ / ✓)
     und einen Text. Ohne das waere der ganze Abschnitt fuer diese Leser
     eine Liste ohne Aussage.
     ------------------------------------------------------------------ */
  .col { --mark: var(--line); --tone: var(--muted); }
  .col.bad { --mark: #b3261e; --tone: #b3261e; border-color: #f3d6d3; }
  .col.ours { --mark: var(--accent); --tone: #1b6b3a; }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .col.bad { --mark: #f2857c; --tone: #f2857c; border-color: #5a2e2b; }
    :root:not([data-theme='light']) .col.ours { --tone: #6ddba0; }
  }
  :root[data-theme='dark'] .col.bad { --mark: #f2857c; --tone: #f2857c; border-color: #5a2e2b; }
  :root[data-theme='dark'] .col.ours { --tone: #6ddba0; }

  .col.bad h3, .col.bad h3 span { color: var(--tone); }
  .col.bad li::before { content: '✕'; color: var(--mark); font-weight: 800; }
  .col.ours li::before { content: '✓'; color: var(--mark); font-weight: 800; }

  /* Die Zahl steht zuerst und gross, der Satz darunter erklaert sie.
     Eine nackte 0 ohne Erklaerung waere eine Behauptung. */
  .col li b {
    display: block;
    color: var(--tone);
    font-size: 1.0625rem; font-weight: 800; letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
    margin-bottom: 0.28rem;
  }

  /* Die eine Zeile, in der beide gleich gut sind. Sieben rote Zeilen gegen
     sieben gruene liest der Besucher als Werbung und glaubt keine davon.
     Ein zugegebener Gleichstand macht die anderen sechs glaubwuerdig. */
  .col li.tie::before { content: '='; color: var(--muted); font-weight: 800; }
  .col li.tie b { color: var(--muted); }

  /* Der Abschluss unter dem Vergleich. Er ist die Zusammenfassung des
     ganzen Abschnitts in vier Woertern, deshalb steht er allein. */
  .closer-line {
    margin: 2rem 0 0;
    text-align: center;
    font-size: clamp(1.25rem, 3vw, 1.6rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    max-width: none;
  }

  /* FAQ. <details> statt selbstgebautem Aufklappen: das kann der Browser
     seit Jahren, es funktioniert ohne JavaScript, und Vorlesegeräte
     kennen es. Selbstgebaut waere hier nur mehr Code mit weniger
     Bedienbarkeit. */
  /* ------------------------------------------------------------------
     DIE FRAGEN, ab 68rem in zwei Spalten (17.08.)
     ------------------------------------------------------------------
     Vorher standen sie in einer Spalte von 44rem, und auf einem breiten
     Bildschirm blieb rechts daneben die halbe Seite leer. Bei jetzt
     dreizehn Fragen war die Liste ausserdem so lang, dass der Abschnitt
     unter ihr nicht mehr in Sichtweite war.

     WARUM `columns` UND NICHT GRID: eine Fragenliste wird auf- und
     zugeklappt, also aendert sich ihre Hoehe dauernd. `columns` verteilt
     nach jedem Klick neu und haelt beide Spalten gleich lang. Ein Grid
     mit festen Haelften wuerde beim Aufklappen einer langen Antwort
     schief werden.

     `break-inside: avoid` ist dabei Pflicht: ohne die Regel reisst eine
     Antwort mitten im Satz in die naechste Spalte.
     ------------------------------------------------------------------ */
  .faq {
    margin-top: 2.5rem;
    max-width: 44rem;
    border-top: 1px solid var(--line);
  }

  @media (min-width: 68rem) {
    .faq {
      max-width: none;
      columns: 2;
      column-gap: 3rem;
      border-top: 0;
    }
    /* Jede Spalte braucht ihre eigene Oberkante, sonst haengt die zweite
       Spalte oben in der Luft. */
    .faq details:first-child,
    .faq details { border-top: 1px solid var(--line); }
    .faq details { border-bottom: 0; }
  }

  .faq details {
    border-bottom: 1px solid var(--line);
    break-inside: avoid;
  }

  .faq summary {
    padding: 1.1rem 2rem 1.1rem 0;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    cursor: pointer;
    list-style: none;
    position: relative;
  }

  .faq summary::-webkit-details-marker { display: none; }

  .faq summary::after {
    content: '+';
    position: absolute;
    right: 0.25rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 1.4rem;
    font-weight: 400;
    color: var(--accent);
    line-height: 1;
  }

  .faq details[open] summary::after { content: '−'; }

  .faq summary:hover { color: var(--accent); }

  .faq details p {
    margin: 0;
    padding: 0 2rem 1.25rem 0;
    font-size: 0.9375rem;
    color: var(--muted);
    max-width: 40em;
  }

  /* Die Leiter */
  .ladder { margin-top: 2.5rem; display: grid; gap: 0.5rem; max-width: 36rem; }

  .rung {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.1rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 12px;
  }

  .rung b { font-size: 0.9375rem; }
  .rung .n {
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    min-width: 1.5rem;
  }
  .rung .s { margin-left: auto; font-size: 0.8125rem; color: var(--muted); }
  .rung[data-done='yes'] { border-color: var(--accent); }
  .rung[data-done='yes'] .s { color: var(--accent); font-weight: 700; }

  /* Datenschutz-Aussage */
  .privacy {
    margin-top: 2.5rem;
    padding: 1.5rem;
    border: 1px solid var(--accent);
    border-radius: 16px;
    background: var(--surface);
  }

  .privacy p { margin-top: 0.6rem; color: var(--muted); font-size: 0.9375rem; }

  /* ------------------------------------------------------------------
     FUSS
     ------------------------------------------------------------------ */
  footer {
    padding: 2.5rem 0 3.5rem;
    border-top: 1px solid var(--line);
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 2rem;
    align-items: baseline;
    justify-content: space-between;
    font-size: 0.875rem;
    color: var(--muted);
  }

  footer nav { display: flex; gap: 1.25rem; flex-wrap: wrap; }

  /* ------------------------------------------------------------------
     DAS TELEFON
     Kein Foto, sondern die Oberflaeche nachgebaut. Grund: ein Screenshot
     altert bei jedem Umbau der App, dieser Bildschirm nicht. Er benutzt
     dieselben Farben wie die App im Dunkelmodus, deshalb stehen hier
     feste Werte statt der Seiten-Variablen: das Telefon ist dunkel, auch
     wenn der Besucher hell eingestellt hat.
     ------------------------------------------------------------------ */
  /* Die Galerie. Waagerecht schiebbar, mit Rastung, damit ein Bildschirm
     immer mittig stehen bleibt statt halb angeschnitten. */
  .gallery {
    margin-top: 2.25rem;

    /* ----------------------------------------------------------------
       RASTER STATT FLEX (22.08.), und der Grund ist die HOEHE.

       Bali: "die phones sind nicht auf der gleichen Hoehe, weil der Text
       oberhalb unterschiedlich viele Zeilen hat."

       Genau so war es. Eine Unterschrift wie "Four movements, seven
       stages, one ladder." braucht eine Zeile, "The timer runs while you
       hold. No guessing afterwards." braucht zwei. Bei `flex` ist jede
       Karte fuer sich, also faengt ihr Telefon dort an, wo ihre eigene
       Unterschrift aufhoert. Vierzehn Pixel Versatz, gemessen.

       DIE ALTE ANTWORT WAR `min-height` AUF DER UNTERSCHRIFT, und sie
       war eine Schaetzung: 2.6em reichen fuer eine kurze Zeile und nicht
       fuer zwei. Sie muesste bei jeder Textaenderung nachgerechnet werden
       und bricht auf schmalen Bildschirmen erneut, weil dort auch der
       Titel umbricht.

       `subgrid` loest es ohne Zahl: die Karten teilen sich die ZWEI
       Zeilen des Elternrasters, und eine Rasterzeile ist immer so hoch
       wie ihr hoechster Inhalt. Alle Telefone beginnen damit auf
       derselben Linie, egal wie lang der Text darueber ist.
       ---------------------------------------------------------------- */
    display: grid;
    grid-auto-flow: column;
    /* Dieselbe Breite, die vorher als `width` auf der Karte stand. */
    grid-auto-columns: clamp(17rem, 82vw, 22rem);
    grid-template-rows: auto auto;
    /* Zeilenabstand zuerst, Spaltenabstand danach: 1.25rem trennt
       Unterschrift und Telefon INNERHALB der Karte (das war vorher der
       `gap` der Karte), 1rem trennt die Karten voneinander. */
    gap: 1.25rem 1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding: 0 1rem;
    padding-bottom: 1rem;
    /* Damit die Karten am Rand nicht kleben, wenn ganz durchgescrollt wird */
    scrollbar-width: thin;

    /* MUSS DABEISTEHEN, sobald scrollbar-width gesetzt ist: Chrome schaltet
       damit auf die genormten Eigenschaften um und IGNORIERT die
       ::-webkit-scrollbar-Regeln darunter. Die standen hier und wirkten nicht.
       Sie bleiben trotzdem stehen, aelteres Safari kennt nur sie. */
    scrollbar-color: var(--line) transparent;
  }

  /*
   * DIE GALERIE BLEIBT IN DER LESESPALTE (22.08., Balis Anweisung mit
   * Zeichnung: "dass es in der gleichen Breite ist wie die Sachen
   * unterhalb, und dann kann man einfach in diesem Bereich scrollen").
   *
   * ------------------------------------------------------------------
   * DAS DREHT EINE FRUEHERE ENTSCHEIDUNG UM, deshalb steht die alte
   * Begruendung hier weiter und wird nicht stillschweigend geloescht:
   * die Galerie lief bis an die Fensterkanten, weil ein am Bildrand
   * angeschnittenes Telefon der Hinweis ist, dass es seitlich
   * weitergeht. Ein zentrierter Kasten verschweigt das.
   *
   * WARUM DER EINWAND HIER NICHT MEHR TRAEGT: der Hinweis geht nicht
   * verloren, er wandert nur nach innen. Acht Karten passen auch in die
   * Lesespalte nicht nebeneinander, also wird weiterhin eine
   * angeschnitten, nur eben an der Spaltenkante statt an der
   * Fensterkante. Dazu stehen die Scrollleiste und die Zeile
   * "Eight screens from the app itself. Drag them sideways."
   *
   * WAS DER TAUSCH GEWINNT, und das war Balis Punkt: auf einem breiten
   * Bildschirm stand die Galerie als einziger Block ausserhalb der
   * Flucht. Die Kacheln darunter, die Ueberschrift darueber und der
   * ganze Rest der Seite enden an derselben Kante, die Telefone liefen
   * links und rechts darueber hinaus.
   * ------------------------------------------------------------------
   *
   * Technisch ist es eine Streichung, kein Zusatz: ohne
   * `grid-column: 1 / -1` greift die allgemeine Regel weiter oben, die
   * jedes Kind eines Abschnitts in Spalte 2 setzt. Das Innenmass, das die
   * erste Karte auf die Textkante zurueckgeholt hat, wird damit
   * ueberfluessig, denn die Spalte hat den Seitenabstand schon.
   */

  .gallery::-webkit-scrollbar { height: 8px; }
  .gallery::-webkit-scrollbar-thumb { background: var(--line); border-radius: 99px; }
  .gallery::-webkit-scrollbar-track { background: transparent; }

  /* ------------------------------------------------------------------
     DIE GALERIE ALS KARUSSELL (17.08., ersetzt das Fliessband)
     ------------------------------------------------------------------
     Balis Entscheidung: die Screenshots sollen korrekt sitzen statt zu
     laufen, und man wechselt mit Ziehen oder mit den Pfeilen.

     WAS DAMIT AUCH ENTFAELLT: das Verdoppeln der Karten. Ein Karussell
     braucht keine lueckenlose Schleife, also stehen wieder genau die
     sechs echten Karten im Dokument.

     `scroll-snap-type: x mandatory` ist jetzt richtig, waehrend es beim
     Band im Weg war: eine Karte, die von selbst laeuft, darf nicht
     einrasten, eine Karte, die man schiebt, muss es.
     ------------------------------------------------------------------ */
  .gallery.deck { cursor: grab; }
  .gallery.deck:active { cursor: grabbing; }
  /* Waehrend gezogen wird, darf kein Bild als Bild aufgegriffen werden,
     sonst startet der Browser sein eigenes Ziehen und das Karussell
     bleibt stehen. */
  .gallery.deck.is-dragging { cursor: grabbing; scroll-snap-type: none; }
  .gallery.deck.is-dragging img { pointer-events: none; }

  /* ==================================================================
     PFEILE UND VERLAUFSKANTEN (22.08., Balis Wunsch)
     ==================================================================
     Woertlich: "baue visuelle Pfeile ein, dass man checkt, dass man
     scrollen kann, und nicht nur diese visuellen Pfeile wie bei der App,
     sondern auch klickbare Tasten, mit denen man scrollen kann."

     ⚠️ DAS DREHT EINE ENTSCHEIDUNG VOM 17.08. UM, an der Tag Pfeile
     ausdruecklich ENTFERNT wurden ("es bleibt das Ziehen"). Der Grund,
     warum es jetzt trotzdem richtig ist: damals waren es sechs Karten,
     die auf einem breiten Bildschirm fast alle sichtbar waren. Es sind
     acht, die Galerie sitzt seit heute in der Lesespalte statt ueber die
     volle Fensterbreite, und damit ist mehr als die Haelfte des Inhalts
     nicht zu sehen. Ein Ziehen, von dem niemand weiss, ist kein Ziehen.

     ZWEI ANZEIGEN, WEIL SIE ZWEI VERSCHIEDENE DINGE SAGEN:
     · Die VERLAUFSKANTE sagt "hier hoert es nicht auf". Sie ist passiv,
       nimmt keinen Platz und kann nicht geklickt werden.
     · Die TASTE sagt "du kannst weiter". Sie ist fuer alle, die nicht
       ziehen: Maus ohne Rad, Tastatur, Vorlesegeraet.

     BEIDE VERSCHWINDEN, WENN ES NICHTS ZU SCROLLEN GIBT. Ein Pfeil, der
     nichts tut, laesst den Besucher an sich zweifeln statt an der Seite,
     und genau dieser Satz steht seit dem 17.08. beim Hinweistext
     darunter. Er gilt hier genauso.
     ================================================================== */
  .galwrap { position: relative; }

  /* Die Kanten liegen UEBER den Karten, aber unter den Tasten, und sie
     lassen jeden Klick durch. Sie enden 1.5rem ueber dem Boden, damit
     die Bildlaufleiste frei bleibt. */
  .galwrap::before,
  .galwrap::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 1.5rem;
    width: 3.5rem;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s;
    z-index: 1;
  }
  .galwrap::before {
    left: 0;
    background: linear-gradient(to right, var(--band), transparent);
  }
  .galwrap::after {
    right: 0;
    background: linear-gradient(to left, var(--band), transparent);
  }
  /* Die Kante erscheint nur auf der Seite, auf der wirklich noch etwas
     liegt. Beide gleichzeitig heisst: man steht in der Mitte. */
  .galwrap[data-links='ja']::before { opacity: 1; }
  .galwrap[data-rechts='ja']::after { opacity: 1; }

  .galpfeil {
    position: absolute;

    /* ----------------------------------------------------------------
       SENKRECHT MITTIG OHNE `transform`, und das ist ein Fehlerfix.

       Bali am 22.08.: "wenn ich auf diese Pfeiltaste druecke, dann
       springen beide Tasten nach unten."

       DIE URSACHE STAND SEIT MONATEN IM BLATT, ganz oben bei den
       allgemeinen Knopfregeln:

           button:active { transform: translateY(1px); }

       Das ist die Druck-Rueckmeldung fuer JEDEN Knopf der Seite und
       voellig richtig. Nur: der Pfeil hat seine Mitte ueber
       `transform: translateY(-50%)` geholt, und ein Element hat genau
       EINEN `transform`. Beim Druecken gewinnt der spaetere, die
       Zentrierung faellt weg, und der Knopf faellt um seine halbe Hoehe
       nach unten. **22 Pixel, nicht 1.**

       DIE LOESUNG NIMMT DEM KNOPF DEN `transform` WEG, statt die
       Druckregel zu entschaerfen: `top` und `bottom` gesetzt, feste
       Hoehe, und `margin-block: auto` verteilt den Rest gleichmaessig
       nach oben und unten. Das ist die aeltere und robustere Art, etwas
       absolut Positioniertes zu zentrieren.

       ZWEI DINGE WERDEN DAMIT BESSER, nicht nur eines:
       1. Der Pfeil bekommt dieselbe Druck-Rueckmeldung wie jeder andere
          Knopf der Seite, weil `transform` jetzt frei ist.
       2. Er zentriert sich zwischen Oberkante und BILDLAUFLEISTE statt
          ueber die ganze Hoehe. Vorher sass er einen halben Balken zu
          tief.
       ---------------------------------------------------------------- */
    top: 0;
    bottom: 1.5rem;
    margin-block: auto;

    /* 44 Punkt. Dieselbe Mindestgroesse fuer ein Ziel unter dem Finger,
       die auch in der App gilt. Die Hoehe MUSS hier stehen: ohne sie
       wuerde das Element die ganze Strecke zwischen top und bottom
       fuellen und `margin: auto` haette nichts zu verteilen. */
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;

    /* FARBEN: der Kreis ist DUNKLER als die Karte, nicht gleich hell.
       ----------------------------------------------------------------
       Erster Versuch war `--surface`, also genau der Ton der Karte
       darunter. Im Bild nachgesehen: der Knopf verschwand fast, weil er
       auf demselben Ton lag und nur eine haarfeine Linie ihn trennte.
       Ein Bedienelement, das man suchen muss, ist keines.

       `--ground` ist der Seitengrund und damit dunkler als jede Karte.
       Zusammen mit dem tuerkisen Rand und Pfeil hebt er sich auf beiden
       Untergruenden ab, auf der Karte wie auf dem Band dazwischen.

       WARUM NICHT GANZ TUERKIS GEFUELLT: dann konkurriert er mit
       "Get early access" in der Kopfleiste. Auf einer Seite mit genau
       einer Aufgabe darf nur ein Element so laut sein. */
    border: 1px solid var(--accent);
    border-radius: 999px;
    background: var(--ground);
    color: var(--accent);
    cursor: pointer;
    padding: 0;
    z-index: 2;
    box-shadow: var(--shadow);
    transition: border-color 0.15s, color 0.15s, background 0.15s, opacity 0.15s;
  }
  /* Beim Ueberfahren dreht sich das Verhaeltnis um: der Knopf wird zur
     Flaeche, der Pfeil zum Loch darin. Das ist die deutlichste
     Rueckmeldung, die ohne Bewegung auskommt. */
  .galpfeil:hover { background: var(--accent); color: var(--ground); border-color: var(--accent); }
  .galpfeil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .galpfeil svg { width: 20px; height: 20px; display: block; }
  .galpfeil.links { left: 0.5rem; }
  .galpfeil.rechts { right: 0.5rem; }

  /* Am Ende der Reihe ausgegraut statt versteckt: ein Knopf, der
     verschwindet, laesst den anderen springen. */
  .galpfeil[disabled] {
    opacity: 0.35;
    cursor: default;
    background: var(--ground);
    border-color: var(--line);
    color: var(--muted);
    box-shadow: none;
  }
  /* Ohne diese Zeile faerbt sich auch der tote Knopf beim Ueberfahren
     ein und behauptet, er wuerde etwas tun. */
  .galpfeil[disabled]:hover { background: var(--ground); color: var(--muted); border-color: var(--line); }

  /* Passt alles nebeneinander, sind Tasten UND Kanten falsch. Das Skript
     setzt das Merkmal und prueft es bei jeder Groessenaenderung neu. */
  .galwrap[data-scrollbar='nein'] .galpfeil { display: none; }

  /* Ohne JavaScript gibt es niemanden, der die Tasten bedient. Sie sind
     deshalb im Markup versteckt und werden erst vom Skript sichtbar
     gemacht, genau wie es der Kommentar bei der Ziehen-Logik vorsieht. */
  .galpfeil[hidden] { display: none; }

  @media (prefers-reduced-motion: reduce) {
    .galwrap::before,
    .galwrap::after { transition: none; }
  }

  .galnav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-top: 0.25rem;
  }
  .galnav .scrollhint { margin: 0; }

  .galbtns { display: flex; gap: 0.5rem; }

  .galbtn {
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    font: inherit;
    transition: border-color 0.15s, color 0.15s, background 0.15s, opacity 0.15s;
  }
  .galbtn:hover { border-color: var(--accent); color: var(--accent); }
  .galbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Am Ende der Reihe ausgegraut statt versteckt: ein Knopf, der
     verschwindet, laesst den anderen springen. */
  .galbtn[disabled] { opacity: 0.35; cursor: default; border-color: var(--line); color: var(--muted); }

  .shot {
    /* Die Breite kommt seit dem 22.08. aus `grid-auto-columns` am
       Elternraster. Der Wert selbst ist unveraendert: von 17,5 auf 22rem
       am 17.08., weil die Screenshots der Beweis sind, dass die App
       existiert, und kleiner waren als die Textkarten daneben. Auf dem
       Handy bleiben 82vw: eine Karte fuellt den Blick, die naechste
       schaut am Rand hervor und zeigt, dass es weitergeht. */
    scroll-snap-align: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 24px;
    padding: 1.5rem 1.25rem 1.75rem;

    /* DIE KARTE UEBERNIMMT DIE ZEILEN DES ELTERNRASTERS statt eigene zu
       bilden. Genau das macht `subgrid`, und nur deshalb stehen die
       Telefone aller acht Karten auf derselben Linie. Ohne diese zwei
       Zeilen faellt alles auf das alte Verhalten zurueck. */
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 2;
  }

  .cap {
    font-size: 1.0625rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.25;
    text-align: center;
    text-wrap: balance;
  }

  /* RUECKFALL FUER BROWSER OHNE `subgrid`.
     ------------------------------------------------------------------
     Rund vier Prozent, im Wesentlichen aeltere Safari-Staende. Dort
     bleibt die geratene Mindesthoehe, aber gross genug fuer ZWEI Zeilen
     Unterschrift statt der bisherigen 2.6em, die nur fuer eine reichten.
     Gerechnet: Titelzeile 1.25em, Abstand 0.28em, zwei kleine Zeilen zu
     je 0.96em, macht 3.45em. Aufgerundet auf 3.6em.

     Es ist bewusst KEINE Umkehrung: mit subgrid steht hier gar keine
     Mindesthoehe mehr, weil eine Rasterzeile ihre Hoehe vom Inhalt
     bekommt und keine Schaetzung braucht. */
  @supports not (grid-template-rows: subgrid) {
    .cap { min-height: 3.6em; }
  }

  .cap small {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0;
    color: var(--muted);
  }

  .scrollhint {
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    color: var(--muted);
  }

  .phone {
    --p-ground: #1a1a2e;
    --p-surface: #232345;
    --p-text: #e0e0e0;
    --p-muted: #9494ad;
    --p-accent: #00b4d8;
    --p-line: #2e2e52;

    width: 100%;
    aspect-ratio: 9 / 19.5;
    background: var(--p-ground);
    border: 8px solid #0b0b16;
    border-radius: 2.4rem;
    box-shadow: 0 18px 40px rgb(0 0 0 / 22%), 0 3px 8px rgb(0 0 0 / 14%);
    overflow: hidden;
    position: relative;
    color: var(--p-text);
    display: flex;
    flex-direction: column;
  }

  /* Seit dem 17.08. steht im Rahmen ein ECHTER Screenshot statt einer
     nachgebauten Oberflaeche. Der Nachbau war gut, aber er alterte bei jedem
     Umbau der App, und er behauptete etwas ueber einen Bildschirm, den es so
     vielleicht gar nicht mehr gab. Das Seitenverhaeltnis der Aufnahme
     (1290:2796) passt auf ein Tausendstel genau in den Rahmen, deshalb
     braucht es keinen Beschnitt. */
  .phone img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* ==================================================================
     INFO-PUNKTE AUF DEN SCREENSHOTS (17.08., Balis Wunsch)
     ==================================================================
     Ein Screenshot zeigt, DASS etwas da ist. Er erklaert nicht, WARUM. Der
     Punkt sitzt genau auf dem Bedienelement, ueber das man sonst hinwegliest.

     WARUM <button> UND KEIN :hover: auf einem Telefon gibt es kein Hover.
     Eine Erklaerung, die nur der Mauszeiger findet, existiert fuer die
     Haelfte der Besucher nicht. Ein Knopf laesst sich antippen, mit der
     Tastatur erreichen und von Vorlesegeraeten ansagen.

     WARUM DIE POSITION IN STYLE STEHT: sie gehoert zum Bild und nicht zum
     Bauplan. Jeder Punkt liegt woanders, eine CSS-Klasse je Punkt waeren
     sieben tote Klassen.
     ================================================================== */
  .ipunkt {
    position: absolute;
    left: var(--x);
    top: var(--y);
    /* Mit dem MITTELPUNKT auf der Stelle sitzen, nicht mit der Ecke daneben. */
    transform: translate(-50%, -50%);
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 2px solid rgb(255 255 255 / 85%);
    background: var(--p-accent);
    color: #04212b;
    font: 700 15px/1 system-ui, sans-serif;
    display: grid;
    place-items: center;
    cursor: pointer;
    padding: 0;
    z-index: 2;
    box-shadow: 0 0 0 4px rgb(0 180 216 / 25%), 0 2px 8px rgb(0 0 0 / 35%);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }

  .ipunkt:hover,
  .ipunkt:focus-visible {
    transform: translate(-50%, -50%) scale(1.15);
    box-shadow: 0 0 0 7px rgb(0 180 216 / 22%), 0 2px 10px rgb(0 0 0 / 40%);
  }

  /* Der Ring pulst, sonst uebersieht man ihn auf einem dunklen Screenshot.
     Zwei Sekunden Takt liest das Auge als Hinweis, ein schnelles Blinken
     als Fehler. */
  @media (prefers-reduced-motion: no-preference) {
    .ipunkt::after {
      content: '';
      position: absolute;
      inset: -4px;
      border-radius: 50%;
      border: 2px solid var(--p-accent);
      animation: ipuls 2.4s ease-out infinite;
    }
    .ipunkt[aria-expanded='true']::after { animation: none; opacity: 0; }
  }

  @keyframes ipuls {
    0%   { transform: scale(1);   opacity: 0.75; }
    70%  { transform: scale(1.9); opacity: 0; }
    100% { transform: scale(1.9); opacity: 0; }
  }

  /* Die Sprechblase liegt IM Telefon und darf es nicht verlassen. Feste
     Breite statt Prozent: bei schmalen Karten waeren 90% zu wenig fuer
     zwei Woerter pro Zeile. */
  .ibubble {
    position: absolute;
    left: 50%;
    /* Standard unten: die Punkte sitzen meist oben, so verdeckt die Blase
       nie den Punkt, der sie geoeffnet hat. Sitzt der Punkt selbst unten,
       bekommt die Blase die Klasse "oben". */
    bottom: 0.9rem;
    transform: translateX(-50%);
    width: min(15rem, 88%);
    background: rgb(10 12 22 / 96%);
    border: 1px solid rgb(0 180 216 / 45%);
    border-radius: 14px;
    padding: 0.7rem 0.85rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: #e6ecf6;
    z-index: 3;
    box-shadow: 0 10px 30px rgb(0 0 0 / 45%);

    /* Geschlossen ist sie unsichtbar UND nicht anklickbar. Nur opacity: 0
       liesse eine unsichtbare Flaeche ueber dem Bild liegen. */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.18s ease;
  }

  .ibubble[data-offen='ja'] {
    opacity: 1;
    visibility: visible;
  }

  .ibubble.oben {
    bottom: auto;
    top: 0.9rem;
  }

  .ibubble b {
    display: block;
    color: var(--p-accent);
    margin-bottom: 0.15rem;
  }

  /* Ueberschrift ueber den Fragen. Sie traegt den Anker #faq, deshalb oben
     Abstand: sonst verschwindet die Zeile beim Anspringen unter der Leiste. */
  .faqhead {
    margin-top: 3.5rem;
    scroll-margin-top: 5.5rem;
  }
  .faqhead h3 {
    font-size: 1.35rem;
    letter-spacing: -0.01em;
    margin: 0;
  }

  /* Die Stile der nachgebauten Bildschirme standen hier: .island, .statusbar,
     .screen, .panel, .big, .week, .set, .badge, .pring, .prung, .pdots und
     .pbody. Rund 190 Zeilen, entfernt am 17.08. zusammen mit dem Nachbau
     selbst. Wer sie sucht: sie liegen in der Git-Historie, nicht hier. */

  /* ------------------------------------------------------------------
     WER DAHINTERSTECKT: Foto links, Geschichte rechts (18.08.)
     ------------------------------------------------------------------
     DAS FOTO IST ANGESCHNITTEN, nicht vollstaendig gezeigt. Ein
     Hochformat in voller Hoehe neben vier Absaetzen zwingt entweder das
     Bild auf Briefmarkengroesse oder den Text auf halbe Zeilenlaenge.
     `aspect-ratio` plus `object-fit: cover` schneidet stattdessen einen
     ruhigen Ausschnitt und laesst beide Spalten gleich stark.

     `object-position: 50% 30%` haelt dabei den Kopf im Bild: bei einem
     zentrierten Schnitt landet bei Hochformat-Portraits regelmaessig das
     Gesicht ueber der Kante.
     ------------------------------------------------------------------ */
  .storywrap {
    display: grid;
    gap: 2rem;
    grid-template-columns: 1fr;
    align-items: center;
  }
  @media (min-width: 58rem) {
    .storywrap { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 3rem; }
  }

  .storyfoto { margin: 0; }
  .storyfoto img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    object-position: 50% 30%;
    border-radius: 20px;
    border: 1px solid var(--line);
    box-shadow:
      0 2px 4px rgb(21 21 43 / 10%),
      0 22px 48px rgb(21 21 43 / 16%);
  }

  .storytext > p { margin: 0 0 1rem; }
  .storytext h2 { margin-bottom: 1rem; }
  .storysign { color: var(--text); }
  .storysign strong { color: var(--accent); }

  /* ------------------------------------------------------------------
     DIE LEITER MIT TELEFON DANEBEN (17.08.)
     ------------------------------------------------------------------ */
  /* Kopf UND Leiter links, das Telefon rechts daneben, oben ausgerichtet.
     Vorher stand das Telefon nur neben der Leiter, also erst unterhalb der
     Ueberschrift, und rechts oben blieb eine grosse leere Flaeche.
     `align-items: start` ist dabei entscheidend: ohne sie streckt sich die
     Figur ueber die ganze Zeilenhoehe und das Telefon rutscht in die Mitte. */
  .ladderwrap {
    margin-top: 0;
    display: grid;
    gap: 2rem;
    grid-template-columns: 1fr;
    align-items: start;
  }
  @media (min-width: 62rem) {
    .ladderwrap { grid-template-columns: minmax(0, 1fr) 17rem; gap: 3rem; }
  }
  .ladderwrap .ladder { margin-top: 2.25rem; }
  .colstack { min-width: 0; }
  /* Das Telefon steht in der zweiten Spalte und beginnt oben, also auf
     Hoehe der Ueberschrift und nicht erst neben der Leiter. */
  .laddershot { align-self: start; }

  /* ------------------------------------------------------------------
     DAS GEKIPPTE TELEFON (17.08., Vorbild trainlogapp.com)
     ------------------------------------------------------------------
     Bei TrainLog sind das gerenderte 3D-Bilder. Hier entsteht dasselbe
     mit CSS auf dem vorhandenen Screenshot, und das ist kein Notbehelf:
     ein Render waere ein weiteres Bild im Dokument (das schon 565 KB
     hat), waehrend die Kippung hier null Bytes kostet und beim naechsten
     neuen Screenshot automatisch mitgeht.

     WAS EINE KIPPUNG GLAUBWUERDIG MACHT, und es sind drei Dinge:
     1. `perspective` VOR der Drehung. Ohne sie ist ein rotateY nur ein
        Zusammenquetschen, mit ihr wird die nahe Kante groesser als die
        ferne. Das ist der ganze Unterschied.
     2. DER SCHATTEN MUSS MITKIPPEN. Er sitzt versetzt nach links unten,
        also entgegen der Drehung, weil das Licht von rechts oben kommt.
        Ein zentrierter Schatten unter einem gekippten Objekt liest sich
        sofort als falsch.
     3. EIN GLANZSTREIFEN AUF DEM GLAS. Ein Bildschirm ohne Reflexion ist
        eine Flaeche, mit Reflexion ist er Glas. Er laeuft schraeg und
        bleibt sehr leise.
     ------------------------------------------------------------------ */
  .laddershot {
    margin: 0;
    /* Der Raum, in dem die Kippung stattfindet. Am Element selbst statt
       am Elternteil, damit die Bildunterschrift flach bleibt. */
    perspective: 1400px;
  }

  .laddershot .phone {
    transform: rotateY(-16deg) rotateX(4deg) rotate(-1.5deg);
    transform-origin: 60% 50%;
    box-shadow:
      -18px 26px 40px -12px rgb(21 21 43 / 32%),
      -4px 8px 12px -6px rgb(21 21 43 / 24%);
    transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
    position: relative;
  }

  /* Beim Zeigen richtet es sich auf. Kein Wackeln, nur ein Zuwenden. */
  .laddershot:hover .phone { transform: rotateY(-6deg) rotateX(1deg) rotate(0deg); }

  /* Das Glas. `mix-blend-mode` statt einer weissen Flaeche, damit die
     Reflexion die Farben darunter aufhellt statt sie zu ueberdecken. */
  .laddershot .phone::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
      104deg,
      rgb(255 255 255 / 0%) 34%,
      rgb(255 255 255 / 13%) 46%,
      rgb(255 255 255 / 0%) 58%
    );
    pointer-events: none;
    border-radius: inherit;
  }

  .laddershot figcaption {
    margin-top: 1.4rem;
    font-size: 0.8125rem;
    color: var(--muted);
  }

  @media (prefers-reduced-motion: reduce) {
    .laddershot .phone { transform: none; }
  }

  /* ------------------------------------------------------------------
     DER READINESS-CHECK ZUM ANFASSEN (17.08.)
     ------------------------------------------------------------------
     Links der nachgebaute Bildschirm, rechts die Karten, die vorher die
     ganze Breite belegt haben und dabei rechts eine leere Flaeche
     liessen. Unter 62rem stapelt es, dann steht der Bildschirm oben.

     DER RAHMEN IST DUNKEL, auch wenn die Seite hell ist. Genau wie bei
     den Screenshots: die App IST dunkel, und ein hell nachgebauter
     App-Bildschirm waere eine Luege ueber das Produkt.
     ------------------------------------------------------------------ */
  .checkwrap {
    margin-top: 2.5rem;
    display: grid;
    gap: 1.5rem;
    grid-template-columns: 1fr;
    align-items: start;
  }
  /* DIE BEIDEN SPALTEN STEHEN AUF EINER MITTELACHSE (22.08.).
     ------------------------------------------------------------------
     Vorher: `align-items: start` von oben und vier Karten in EINER
     Reihe. Gemessen auf der Live-Seite ergab das einen Bildschirm von
     678 px Hoehe neben einem Kartenblock von 296 px, also 382 px leere
     Flaeche unter den Karten. Bali hat es als "der Screen liegt nicht
     in der Mitte" gemeldet, und genau das war es: der Kasten haengt
     oben links, rechts daneben schwebt eine flache Reihe, darunter
     ein Loch.

     ZWEI AENDERUNGEN, die zusammengehoeren:
     1. `align-items: center` legt beide Spalten auf dieselbe
        Mittelachse. Der Rest der Hoehe verteilt sich dann gleichmaessig
        oben und unten statt in einem Block darunter.
     2. Die Karten stehen zu zweit statt zu viert (siehe .checkcards).
        Das halbiert die Hoehendifferenz und macht die Karten von 252
        auf rund 500 px breit, also von drei auf fuenf Woerter je Zeile.

     Der Bildschirm waechst von 20 auf 22 rem. Der Nachbau soll sich
     anfuehlen wie die App, und 320 px sind schmaler als jedes Handy,
     auf dem sie laeuft. */
  @media (min-width: 62rem) {
    .checkwrap {
      grid-template-columns: 22rem 1fr;
      gap: 2rem;
      align-items: center;
    }
  }

  .checkphone {
    /* Die Farben der App, fest und nicht als Variable: dieser Kasten ist
       immer dunkel, weil das Produkt es ist. */
    background: #1a1a2e;
    border: 1px solid #2e2e52;
    border-radius: 24px;
    padding: 1.4rem 1.25rem 1.25rem;
    color: #e0e0e0;
    box-shadow: var(--shadow);
  }

  .cscreen-title { margin: 0; font-size: 1.0625rem; font-weight: 800; letter-spacing: -0.01em; }
  .cscreen-sub { margin: 0.15rem 0 1.25rem; font-size: 0.8125rem; color: #9494ad; }

  .crow + .crow { margin-top: 1.1rem; }

  .clabel { margin: 0 0 0.5rem; font-size: 0.875rem; font-weight: 700; }
  .clabel span { display: block; font-weight: 400; font-size: 0.75rem; color: #9494ad; margin-top: 0.1rem; }

  .cscale { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.4rem; }

  .cscale button {
    /* 44 Punkt Mindesthoehe. Das ist die Groesse, die Apple fuer ein
       Ziel unter dem Finger verlangt, und sie gilt hier genauso: der
       Nachbau soll sich anfuehlen wie die App, nicht wie eine Webseite,
       die eine App zeigt. */
    min-height: 44px;
    border: 1px solid #2e2e52;
    background: #232345;
    color: #e0e0e0;
    border-radius: 12px;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
  }
  .cscale button:hover { border-color: #00b4d8; }
  .cscale button[aria-pressed='true'] {
    background: #00b4d8;
    border-color: #00b4d8;
    color: #10101f;
  }
  .cscale button:focus-visible { outline: 2px solid #00b4d8; outline-offset: 2px; }

  .cout {
    margin: 1.25rem 0 0;
    padding-top: 0.9rem;
    border-top: 1px solid #2e2e52;
    font-size: 0.875rem;
    font-weight: 700;
    color: #00b4d8;
    font-variant-numeric: tabular-nums;
  }
  /* Der Start-Knopf, wie in der App. Er ist die Belohnung fuer die drei
     Eingaben: ohne ihn endet der Nachbau in einer Textzeile und wirkt wie
     ein Formular ohne Absenden. */
  .cstart {
    display: block;
    width: 100%;
    margin-top: 0.9rem;
    min-height: 48px;
    border: 0;
    border-radius: 14px;
    background: #00b4d8;
    color: #10101f;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 800;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: filter 0.15s;
  }
  .cstart:hover { filter: brightness(1.08); }
  .cstart:focus-visible { outline: 2px solid #e0e0e0; outline-offset: 2px; }
  .cstart[data-started='yes'] { background: #232345; color: #9494ad; }

  .cnote { margin: 0.7rem 0 0; font-size: 0.75rem; color: #9494ad; }
  /* Die Rueckmeldung nach dem Start. Sie spiegelt die Zahl zurueck, die der
     Besucher selbst eingegeben hat, und bewertet sie NICHT. */
  .cnote[data-kind='joints'] { color: #f2b45c; }

  /* ZWEI SPALTEN, FEST, statt `auto-fit` (22.08.).
     ------------------------------------------------------------------
     `auto-fit` mit `minmax(15rem, 1fr)` legte auf einem breiten
     Bildschirm alle vier Karten in eine Reihe. Vier Karten nebeneinander
     sind 252 px breit, das sind drei bis vier Woerter je Zeile, und der
     Block wird so flach, dass er neben dem Bildschirm verloren aussieht.

     Zwei feste Spalten geben rund 500 px je Karte und zwei Reihen. Fest
     und nicht `auto-fit`, weil die Anzahl hier bekannt ist: es sind
     vier Karten, und vier gehen in zwei Reihen zu zweit sauber auf. */
  @media (min-width: 62rem) {
    .checkcards { grid-template-columns: repeat(2, 1fr); }
  }

  /* ------------------------------------------------------------------
     BEISPIEL-STATISTIK neben dem Telefon
     ------------------------------------------------------------------ */
  .stats { display: grid; gap: 1rem; }

  .figures {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  }

  .figure {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 0.9rem 1rem;
  }

  .figure .n {
    font-size: 1.9rem;
    font-weight: 800;
    letter-spacing: -0.035em;
    font-variant-numeric: tabular-nums;
    line-height: 1.05;
  }

  .figure .n em { font-style: normal; font-size: 0.9rem; color: var(--muted); }
  .figure .k { font-size: 0.75rem; color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase; margin-top: 0.15rem; }

  /* Verlauf mit der 15-Sekunden-Linie. Der ganze Sinn des Diagramms ist
     die gestrichelte Linie: ohne sie waeren es nur steigende Balken. */
  .chart {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 1.1rem 1.2rem 0.9rem;
  }

  .bars {
    position: relative;
    display: flex;
    align-items: flex-end;
    gap: 0.55rem;
    height: 9rem;
    margin-top: 0.9rem;
  }

  .bars i {
    flex: 1;
    background: var(--line);
    border-radius: 5px 5px 0 0;
    display: block;
    transform-origin: bottom;
    transform: scaleY(0);
  }

  .bars i[data-earned='yes'] { background: var(--accent); }
  .bars.in i { transform: scaleY(1); transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.3, 1); }

  .line15 {
    position: absolute;
    left: 0;
    right: 0;
    border-top: 2px dashed var(--accent);
    opacity: 0.55;
  }

  .line15 span {
    position: absolute;
    right: 0;
    top: -1.35rem;
    font-size: 0.6875rem;
    font-weight: 700;
    color: var(--accent);
    letter-spacing: 0.04em;
  }

  .weeks { display: flex; gap: 0.55rem; margin-top: 0.4rem; }
  .weeks span { flex: 1; text-align: center; font-size: 0.6875rem; color: var(--muted); font-variant-numeric: tabular-nums; }

  .caption { margin-top: 0.9rem; font-size: 0.8125rem; color: var(--muted); }

  /* ------------------------------------------------------------------
     KOERPERKARTE
     ------------------------------------------------------------------ */
  .chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 2rem; }

  .chip {
    font: inherit;
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.5rem 1rem;
    cursor: pointer;
  }

  .chip[aria-pressed='true'] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--ground);
  }

  /* Umschalter zwischen den beiden Betriebsarten der Karte */
  .tabs {
    display: inline-flex;
    gap: 0.25rem;
    margin-top: 2rem;
    padding: 0.25rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    /* `inline-flex` allein reicht hier NICHT (17.08.). Die Leiste ist Kind
       eines Grids, und ein Grid-Item wird auf die Spaltenbreite gestreckt,
       egal welches `display` es traegt. Sie lief dadurch ueber die ganze
       Seite und hatte rechts von "Across a week" eine leere Flaeche.
       `justify-self: start` gibt ihr ihre eigene Breite zurueck. */
    justify-self: start;
    width: max-content;
    max-width: 100%;
  }

  .tabs button {
    font: inherit;
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--muted);
    background: transparent;
    border-radius: 999px;
    padding: 0.45rem 1.1rem;
  }

  .tabs button[aria-selected='true'] { background: var(--accent); color: var(--ground); }

  .mapwrap {
    margin-top: 1.5rem;
    display: grid;
    gap: 1.25rem;
    align-items: start;
    grid-template-columns: 1fr;
  }

  @media (min-width: 58rem) {
    .mapwrap { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
  }

  /* ------------------------------------------------------------------
     DER RAHMEN UM DIE FIGUREN (17.08. ueberarbeitet)
     ------------------------------------------------------------------
     Vorher ein flacher Kasten mit einem Punkt Rand. Jetzt mit Tiefe:

     · EINE TRENNLINIE zwischen Front und Back. Es sind zwei Ansichten
       derselben Person, ohne Linie liest man sie als ein breites Bild.
       Sie steht als Rahmen der Spalte und nicht als eigenes Element,
       damit sie beim Schiebe-Effekt nicht mitwandert.
     · GESTUFTER SCHATTEN. Zwei Lagen: eine harte, kurze fuer die Kante
       und eine weite, weiche fuer den Abstand zum Grund. Ein einzelner
       grosser Schatten sieht nach Aufkleber aus, zwei nach Objekt.
     · EIN HELLER INNENRAND OBEN. Das ist der Trick, der Tiefe macht:
       Licht kommt von oben, also ist die Oberkante heller als der Rest.
     ------------------------------------------------------------------ */
  .bodies {
    display: grid;
    gap: 0;
    grid-template-columns: 1fr 1fr;
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--surface) 92%, var(--accent)) 0%,
      var(--surface) 38%);
    border: 1px solid var(--line);
    border-radius: 20px;
    padding: 1rem 0.5rem 0.75rem;
    box-shadow:
      inset 0 1px 0 rgb(255 255 255 / 10%),
      0 2px 4px rgb(21 21 43 / 8%),
      0 18px 40px rgb(21 21 43 / 12%);
    /* Der Schiebe-Effekt beim Wechsel spielt sich hier drin ab. */
    overflow: hidden;
  }

  /* Die Trennlinie. Sie laeuft nicht ganz bis an die Kanten, sonst
     schneidet sie den Rahmen an und wirkt wie ein Bruch. */
  .bodies > .slide:first-child {
    border-right: 1px solid var(--line);
    padding-right: 0.5rem;
  }
  .bodies > .slide:last-child { padding-left: 0.5rem; }

  /* Die Muskelnamen als Marken. Gefuellt = Hauptmuskel, umrandet =
     Mitarbeiter, dieselbe Unterscheidung wie in der Figur daneben. */
  .mline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.6rem;
  }
  .mline b {
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    margin-right: 0.25rem;
  }
  .mtag {
    font-size: 0.8125rem;
    font-weight: 700;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    white-space: nowrap;
  }
  .mtag.is-prime { background: var(--accent); color: var(--ground); }
  .mtag.is-support {
    border: 1px solid var(--accent);
    color: var(--accent);
    background: transparent;
    font-weight: 600;
  }

  .slide { transition: transform 0.32s cubic-bezier(0.3, 0, 0.2, 1), opacity 0.32s ease; }
  .slide[data-move='out-left'] { transform: translateX(-9%); opacity: 0; }
  .slide[data-move='out-right'] { transform: translateX(9%); opacity: 0; }

  .body { text-align: center; }
  .body svg { width: 100%; height: auto; max-height: 30rem; display: block; }
  .body .who {
    font-size: 0.6875rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 0.35rem;
  }

  /* Jede Flaeche bekommt eine haarfeine Trennlinie in der Grundfarbe.
     Ohne sie verschmelzen benachbarte Gruppen zu einem Fleck, sobald
     beide gefaerbt sind, und man sieht nicht mehr, wo der Lat aufhoert
     und der Trapez anfaengt. */
  .body path[data-group] {
    stroke: var(--surface);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    transition: fill 0.5s ease, fill-opacity 0.5s ease;
    cursor: pointer;
  }

  .body path[data-group]:hover,
  .body path[data-group][data-picked='yes'] {
    stroke: var(--text);
    stroke-width: 2.5;
  }

  /* Seitenspalte: Legende, Erklaerung, Regler */
  .stage { margin-top: 2.75rem; }

  .side { display: grid; gap: 1rem; }

  .mdetail {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 1.1rem 1.2rem;
    min-height: 9.5rem;
  }

  .mdetail h3 { display: flex; align-items: center; gap: 0.6rem; }

  .mdetail .lvl {
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    background: var(--line);
    color: var(--text);
  }

  .mdetail p { margin-top: 0.6rem; font-size: 0.9375rem; color: var(--muted); }
  .mdetail .ask { color: var(--muted); font-size: 0.9375rem; }

  .legend { display: flex; flex-wrap: wrap; gap: 0.9rem 1.25rem; font-size: 0.8125rem; color: var(--muted); }
  .legend b { display: inline-flex; align-items: center; gap: 0.45rem; font-weight: 600; }
  .legend i { width: 0.85rem; height: 0.85rem; border-radius: 3px; display: inline-block; }

  .worked { margin: 0; font-size: 0.9375rem; color: var(--muted); max-width: 34em; }
  .worked strong { color: var(--text); }

  /* Der Wochenregler */
  .scrub {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 1.1rem 1.2rem 1.3rem;
  }

  .scrub .dayname { font-size: 1.05rem; font-weight: 800; letter-spacing: -0.02em; }
  .scrub .dsub { font-size: 0.8125rem; color: var(--muted); margin-top: 0.1rem; }

  /* ------------------------------------------------------------------
     DER WOCHEN-REGLER, und warum er vorher nicht passte (17.08.)

     Die Buchstaben standen in sieben gleich breiten Spalten mit
     `flex: 1` und zentriertem Text. Ihre Mitten lagen damit bei 7,1 %,
     21,4 %, 35,7 % und so weiter.

     Der Griff eines `input[type=range]` laeuft aber NICHT von Kante zu
     Kante: seine Mitte geht von halber Griffbreite bis Breite minus
     halber Griffbreite. Am Montag war er dadurch rund drei Prozentpunkte
     neben dem M, und das war sichtbar.

     ZWEI SYSTEME, DIE NUR AEHNLICH AUSSEHEN. Deshalb wird der Griff hier
     selbst gestaltet (dann ist seine Breite bekannt und steht als
     --thumb) und die Buchstaben werden an genau diese Positionen
     gerechnet. Ohne eigenen Griff waere die Breite eine Vermutung, denn
     jeder Browser waehlt eine andere.
     ------------------------------------------------------------------ */
  .scrub { --thumb: 20px; --track: 6px; }

  input[type='range'] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    margin-top: 1rem;
    height: var(--thumb);
    background: transparent;
    cursor: pointer;
  }
  input[type='range']:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 999px; }

  input[type='range']::-webkit-slider-runnable-track {
    height: var(--track);
    border-radius: 999px;
    background: var(--ring-track);
  }
  input[type='range']::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: var(--thumb);
    height: var(--thumb);
    border-radius: 50%;
    background: var(--accent);
    border: 0;
    /* Zentriert den Griff auf der Schiene, statt ihn oben aufzusetzen. */
    margin-top: calc((var(--track) - var(--thumb)) / 2);
  }
  input[type='range']::-moz-range-track {
    height: var(--track);
    border-radius: 999px;
    background: var(--ring-track);
  }
  input[type='range']::-moz-range-thumb {
    width: var(--thumb);
    height: var(--thumb);
    border-radius: 50%;
    background: var(--accent);
    border: 0;
  }

  /* Die Buchstaben sitzen jetzt auf den echten Griffpositionen:
     halbe Griffbreite plus i Siebtel der nutzbaren Strecke. */
  .ticks { position: relative; height: 2.4rem; margin-top: 0.3rem; }
  .ticks span {
    position: absolute;
    top: 0;
    left: calc(var(--thumb) / 2 + var(--i) * (100% - var(--thumb)) / 6);
    transform: translateX(-50%);
    font-size: 0.6875rem;
    color: var(--muted);
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
  }
  .ticks span:nth-child(1) { --i: 0; }
  .ticks span:nth-child(2) { --i: 1; }
  .ticks span:nth-child(3) { --i: 2; }
  .ticks span:nth-child(4) { --i: 3; }
  .ticks span:nth-child(5) { --i: 4; }
  .ticks span:nth-child(6) { --i: 5; }
  .ticks span:nth-child(7) { --i: 6; }

  /* TRAININGSTAG deutlich machen, und nicht nur ueber die Farbe.
     Der Punkt darunter sagt es auch dann, wenn Blau und Grau gleich
     aussehen (Rot-Gruen-Schwaeche, Sonnenlicht, gedimmter Bildschirm). */
  .ticks span[data-trained='yes'] { color: var(--accent); }
  .ticks span[data-trained='yes']::after {
    content: '';
    display: block;
    width: 5px; height: 5px;
    margin: 0.3rem auto 0;
    border-radius: 50%;
    background: var(--accent);
  }
  /* Der Tag, auf dem der Regler gerade steht. */
  .ticks span[data-now='yes'] {
    color: var(--text);
    font-size: 0.75rem;
  }

  .scrub .tickhint {
    font-size: 0.75rem;
    color: var(--muted);
    margin-top: 0.1rem;
  }
  .scrub .tickhint i {
    display: inline-block;
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent);
    margin-right: 0.3rem;
    vertical-align: 0.05em;
  }

  .daylog { margin-top: 0.9rem; display: grid; gap: 0.35rem; }
  .daylog div {
    display: flex;
    gap: 0.6rem;
    font-size: 0.8125rem;
    color: var(--muted);
    border-top: 1px solid var(--line);
    padding-top: 0.35rem;
  }
  .daylog b { color: var(--text); font-weight: 600; }
  .daylog span { margin-left: auto; font-variant-numeric: tabular-nums; }

  [hidden] { display: none !important; }

  /* ------------------------------------------------------------------
     SCROLL-BEWEGUNG
     Zurueckhaltend: alles kommt aus derselben Richtung und nur einmal.
     Elemente, die erst beim Scrollen erscheinen, muessen ohne JavaScript
     trotzdem sichtbar sein, deshalb setzt erst das Skript die Startlage.
     ------------------------------------------------------------------ */
  .js .reveal { opacity: 0; transform: translateY(18px); }

  .js .reveal.in {
    opacity: 1;
    transform: none;
    transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.7, 0.3, 1);
  }

  @media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
    .js .reveal { opacity: 1; transform: none; }
    .bars i { transform: scaleY(1); }
  }

  /* ------------------------------------------------------------------
     DIE KOPFZEILE DER UNTERSEITEN (18.08.)
     ------------------------------------------------------------------
     "The App" und "FAQ" haben keinen Hero mit Halte-Probe, sie brauchen
     aber trotzdem eine Ueberschrift, die sagt wo man ist. Sie ist
     bewusst kleiner als die H1 auf der Startseite: eine Unterseite
     eroeffnet nicht, sie fuehrt fort.
     ------------------------------------------------------------------ */
  .seitenkopf { padding-top: 3.5rem; }

  h1.seitentitel {
    font-size: clamp(2rem, 5.5vw, 3.2rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.06;
    margin: 0 0 0.9rem;
  }

  /* ------------------------------------------------------------------
     DER BLICK IN DIE APP AUF DER STARTSEITE (18.08.)
     ------------------------------------------------------------------
     Spiegelbildlich zum Story-Abschnitt weiter unten: dort steht das
     Foto links und der Text rechts, hier das Telefon rechts und der Text
     links. Zwei gleich gebaute Bloecke untereinander waeren eine
     Wiederholung, so wechselt der Blick die Seite.
     ------------------------------------------------------------------ */
  .peekwrap {
    display: grid;
    gap: 2rem;
    grid-template-columns: 1fr;
    align-items: center;
  }
  @media (min-width: 58rem) {
    .peekwrap { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: 3.5rem; }
    /* Text zuerst im Quelltext, Telefon rechts im Bild. Das ist kein
       Selbstzweck: ein Screenreader und ein Handy lesen die Ueberschrift
       zuerst, und die traegt die Aussage. */
    .peekwrap .peektext { order: 1; }
    .peekwrap .laddershot { order: 2; }
  }

  .peektext > p { margin: 0 0 1rem; }
  .peektext h2 { margin-bottom: 1rem; }

  .peeklink { margin-top: 1.5rem; }
  .peeklink a {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 700;
    color: var(--accent);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    padding-bottom: 0.15rem;
    transition: border-color 0.15s;
  }
  .peeklink a::after { content: '\2192'; transition: transform 0.15s; }
  .peeklink a:hover { border-bottom-color: var(--accent); }
  .peeklink a:hover::after { transform: translateX(3px); }

  /* Das Telefon hier kippt zur anderen Seite als das bei der Leiter,
     weil es auf der anderen Bildhaelfte steht. Licht und Blickrichtung
     muessen zusammenpassen, sonst sieht eines von beiden falsch aus. */
  #peekshot .phone { transform: rotateY(14deg) rotateX(4deg) rotate(1.5deg); }
  #peekshot:hover .phone { transform: rotateY(5deg) rotateX(1deg) rotate(0deg); }
  #peekshot .phone {
    box-shadow:
      18px 26px 40px -12px rgb(21 21 43 / 32%),
      4px 8px 12px -6px rgb(21 21 43 / 24%);
  }
  #peekshot img { display: block; width: 100%; height: auto; }

  /* Das Telefon auf der Startseite hatte ohne Begrenzung die volle
     Spaltenbreite, also 567 Punkt, und wurde dadurch 1281 Punkt hoch:
     eineinhalb Bildschirmhoehen fuer ein einziges Bild. Ein Mockup soll
     zeigen, wie die App aussieht, nicht die Seite sprengen. */
  #peekshot {
    max-width: 19rem;
    justify-self: center;
    width: 100%;
  }
