/* ===========================================================================
   friends. — LANDINGPAGE
   ---------------------------------------------------------------------------
   BEWUSST EIGENES BLATT, nicht css/styles.css. Das Blatt der App ist für genau
   eine Sache gebaut: eine Telefon-App in einem 600px-`#app`. Es setzt globale
   Regeln, die eine Landingpage nicht überlebt — `body { overflow: hidden }` im
   Desktop-Rahmen und vor allem
       html.desk body > *:not(#desk-shell) { display: none !important; }
   das auf einem breiten Screen alles ausblendet, was nicht der Telefon-Rahmen
   ist. Eine Landingpage darunter kämpfte in jeder Zeile dagegen.

   TOKENS SIND KOPIERT (Stand 2026-08-06), nicht geteilt. Die Quelle bleibt
   css/styles.css — wer dort eine Farbe ändert, zieht sie hier nach. Der Preis
   ist bewusst: die Alternative wäre, ein `css/tokens.css` aus dem 345-KB-Blatt
   des Prototyps herauszulösen, also ein Eingriff am laufenden Prototyp für
   eine Seite, die daneben steht.

   DASSELBE GILT FÜR DIE ELEMENTE: Kugeln, Symbole und Levelbar sind KOPIEN aus
   dem Prototyp, keine Referenzen. Der Prototyp bleibt unangetastet. Wo eine
   Kopie 1:1 aus css/styles.css stammt, steht die Quelle im Kommentar.

   KEINE FREMD-STYLESHEETS: kein Font Awesome, kein Google Fonts. Der Prototyp
   holt seine Icons von einem CDN — hier stehen dieselben Zeichen als inline
   SVG im Markup. Die Schrift kommt aus dem Prototyp-Ordner; seit dessen Umzug
   nach `friends-prototype/` sind Projektordner und Server gleich gebaut, ein
   relativer Pfad stimmt also an beiden Orten.
   =========================================================================== */

@font-face {
    font-family: 'Geologica';
    src: url('friends-prototype/font/Geologica/Geologica-VariableFont_CRSV,SHRP,slnt,wght.ttf') format('truetype');
    font-weight: 100 900;
    font-display: swap;
}

/* --- Tokens (Auszug aus css/styles.css :root) ---------------------------- */
:root {
    --surface-color: #ffffff;
    --bg-color:      #ffffff;   /* Canvas = Hochweiss, wie in der App */
    --surface-light: #f1eff8;   /* eingelassene Spur */
    --shadow-rgb:    13, 35, 69;          /* Brand-Dunkelblau getönt — nie Schwarz */
    --hairline:      rgba(0, 0, 0, 0.12);

    --text-primary:   #1f1f1f;
    --text-secondary: #5c5c5c;
    --text-muted:     #767676;
    --text-light:     #ffffff;

    --accent-yellow: #FFE600;   /* Updates */
    --accent-pink:   #FF0066;   /* Pins & Boards */
    --accent-turkis: #52cda7;   /* neutral: Friends, Aktionen, Karma */

    --font-main: 'Geologica', -apple-system, BlinkMacSystemFont, sans-serif;

    --radius-full: 9999px;

    /* DIE DREI BEREICHE DER STORY, von unten gerechnet. Feste Höhen statt
       Prozentwerten: mit `56svh` für die Titelzeile sass auf einem hohen
       Desktop-Fenster alles am unteren Rand und darüber klaffte Leere. Die
       Leiste und die Titelzeile brauchen so viel, wie ihr Inhalt braucht —
       was übrig bleibt, gehört der Bühne, und die wird auf Desktop einfach
       grösser. */
    /* Die Ecke oben links: Marke, darunter das Impressum. `--kopf-pad` ist der
       Abstand zur Kante — EIN Wert für oben und für links, weil eine Ecke nur
       dann als Ecke gelesen wird, wenn beide Seiten gleich sind. */
    --kopf-pad: clamp(0.85rem, 2vw, 1.5rem);
    /* Polster + Logo + Polster — keine gegriffene Zahl, sondern die Summe
       dessen, was in der Zeile steht. Das Impressum steht daneben, nicht
       darunter, und ist niedriger als das Logo; es bestimmt die Höhe also nicht.
       Sie geht in `--buehne-h` ein: was der Kopf nimmt, fehlt der Bühne. */
    --kopf-h: 64px;
    --bar-h: 76px;                          /* Pfeile, Punkte, CTA */
    /* Die Titelgrösse steht HIER, weil die Höhe der Titelzeile an ihr hängt
       (siehe `--text-h`) — zwei Orte mit derselben Zahl liefen auseinander. */
    --titel-fs: clamp(1.75rem, 6.2vw, 2.75rem);
    /* Der längste Fall sind die zwei Format-Akte: Akt 4 trägt drei Unterakte,
       Akt 5 zwei, davon einen, der selbst umbricht („Mit Friends, Friends of
       Friends oder öffentlich") — beide kommen auf rund sieben Zeilen.
       ZWEI Masse, und das grössere gewinnt:
       - `clamp(…svh…)` gibt der Zeile ihren Anteil an der Fensterhöhe;
       - `--titel-fs × 7` rechnet, was der Inhalt WIRKLICH braucht: rund sieben
         Zeilen Schrift, deren Grösse an der Fenster BREITE hängt, plus 52 px
         Luft nach unten (der Abstand zur Leiste, nicht bloss kein Anstossen).
       Nur die svh-Rechnung stand hier vorher, und genau dazwischen lag der
       Fehler: auf einem breiten, aber niedrigen Fenster wurde die Schrift
       gross (6.2vw) und die Zeile klein (32svh) — der Titel lief dreizeilig
       um und die letzte Unterakt-Zeile landete in den Punkten. Eine Höhe, die
       nur die eine Achse kennt, kann den Fall nicht sehen. */
    --text-h: max(clamp(220px, 34svh, 300px), calc(var(--titel-fs) * 7 + 52px));
    --spalte: 640px;                        /* so weit stehen die Griffe auseinander */
    /* Was der Bühne bleibt — dieselbe Rechnung, die das Grid macht, nur als
       Zahl: die Höhe der Story minus Titelzeile, Leiste und dem Polster der
       Bühne (2 × 1.25rem). Der Inhalt der Bühne braucht sie, weil er sich sonst
       nur an `svh` binden könnte — und die sagt auf einem breiten, niedrigen
       Fenster das Falsche, wo die Titelzeile den Platz nimmt. */
    --buehne-h: calc(max(480px, 100svh) - var(--kopf-h) - var(--text-h) - var(--bar-h) - 2.5rem);

    /* Kategorien-Zeile (Akt 4/5) und die Kugel darunter. `--kat-h` MISST das JS
       — wie hoch die Zeile samt Rubrik-Reihe ist, hängt an der geladenen
       Schrift; der Wert hier ist der Notnagel. Die Kugel rechnet gegen beide,
       damit die Gruppe auf einer niedrigen Bühne nicht unten hinausläuft:
       zuerst darf die Zeile ihren Platz nehmen, den Rest bekommt die Kugel. */
    --kat-h: 62px;
    --kat-luft: 1.6rem;
    --orb-groesse: min(46vw, 34svh, 200px,
                       calc(var(--buehne-h) - var(--kat-h) - var(--kat-luft)));

    /* --- DIE EINE PAUSE ----------------------------------------------------
       Die ganze Story hat EINEN Abstand, und er ist der zwischen Titel und
       Stempel auf Akt 2 — der Takt, der sich beim Zusehen am besten anfühlte.
       Von Schritt zu Schritt wird START ZU START gerechnet, nicht von Ende zu
       Anfang: die Blenden dauern verschieden lang, und ein Rhythmus, der an
       ihren Enden hängt, ist keiner.

       Hier standen drei gestufte Pausen (kurz/mittel/lang, 0.75/1.05/1.5 s) —
       „was eng zusammengehört, folgt schnell". Die Staffelung war als System
       richtig gedacht, in der Sache aber falsch: die Akte dieser Seite tragen
       je EINEN Gedanken, und ein Schritt darin ist wie der andere. Die Stufung
       machte den Ablauf ungleichmässig, ohne dass die Ungleichheit etwas sagte.

       DIE HALBE PAUSE ist die einzige Ausnahme und hat einen Grund: zwischen
       mehreren Untertiteln UNTEREINANDER (eine Aufzählung ist ein Atemzug, kein
       neuer Gedanke) und zwischen den drei Wörtern von Akt 6 („Posten,
       kommentieren und reagieren" ist EIN Satz, der sich abrollt). */
    --pause:      0.7s;
    --pause-halb: 0.35s;

    /* Die Zeitpunkte sind aus den Pausen GERECHNET, nicht abgeschrieben —
       sonst laufen sie beim nächsten Justieren auseinander.
       `--t-titel` ist der Moment, in dem der Akt einblendet — FAST SOFORT nach
       dem Klick: der erste Auftritt ist die Antwort auf den Griff, und eine
       Antwort, auf die man wartet, fühlt sich an wie ein hängender Knopf. Die
       Pausen danach sind Gestaltung, diese Zahl ist Reaktion. Sie steuert die
       eigene Blende des Akts (siehe `.akt.is-active`) — vorher stand die 150 ms
       dort ein zweites Mal, und die zwei Zahlen konnten auseinanderlaufen. */
    --t-titel: 0.05s;
    --t-sub: calc(var(--t-titel) + var(--pause));
    --t-viz: calc(var(--t-titel) + var(--pause));
    /* Nur Akt 3: der Moment, in dem sein Titel von der Mitte in die Titelzeile
       fährt UND die Bühne einblendet — die beiden sind ein Vorgang, nicht zwei.
       Der Satz räumt die Mitte, und was er freigibt, füllt sich im selben Zug;
       nacheinander waren es zwei Wartezeiten hintereinander und der Akt zog sich.
       Steht hier oben und nicht am Akt, weil auch die Bühne dagegen rechnet und
       sie kein Kind des Akts ist.

       DIE FAHRT IST DER ZWEITE SCHRITT, nicht der dritte: Sie wartete früher,
       bis auch der Untertitel stand (`--t-sub + 520ms`). Der Untertitel ist
       jetzt der LETZTE Schritt dieses Akts — erst der Satz, dann das Zeichen,
       und ganz zuletzt das Kleingedruckte darunter, das die App abgrenzt. Damit
       liegt die Fahrt genau eine Pause nach dem Titel, wie jeder zweite Schritt
       der Seite.
       IHRE DAUER IST DIE PAUSE SELBST (`--fahrt-dauer`), und das ist kein
       Zufall: So landet der Block genau dann, wenn der nächste Schritt fällig
       wäre — der Untertitel setzt auf dem angekommenen Block auf, statt in eine
       laufende Bewegung hineinzublenden. Eine Zahl, zwei Stellen, ein Takt. */
    --fahrt-dauer: var(--pause);
    --t-fahrt: calc(var(--t-titel) + var(--pause));

    --icon-sw: 2.4;             /* Strichstärke der Iconoir-Symbole, wie im Prototyp */

    /* Ebene 1 — Elevation + Bevel, die Tiefe einer Card (DS §3). */
    --elev-1: 0 12px 28px rgba(var(--shadow-rgb), .14),
              inset 0 -3px 6px rgba(var(--shadow-rgb), .07);
    /* Ebene 1 auf FARBIGER Vollfläche: Bevel doppelt, dazu weiches Licht oben
       — auf Gelb und Rosa bleibt der System-Bevel sonst unsichtbar. Genau die
       drei Schatten, die die Chooser-Kugel im Prototyp trägt. */
    --elev-1-solid: 0 12px 28px rgba(var(--shadow-rgb), .14),
                    inset 0 7px 14px rgba(255, 255, 255, .25),
                    inset 0 -10px 20px rgba(var(--shadow-rgb), .15);
    /* Ebene 3 — was frei über allem schwebt: Schlagschatten, kein Bevel. */
    --elev-3: 0 12px 28px rgba(var(--shadow-rgb), .16);
}

/* KEIN DARK MODE — und schon gar keiner aus den Geräte-Einstellungen.
   Hier stand ein `prefers-color-scheme: dark`-Block. Diese Seite ist eine
   Visitenkarte: sie zeigt die App, wie sie gemeint ist, und der helle Modus IST
   die Referenz (dieselbe Regel wie im Prototyp, CLAUDE.md → Top-Bar). Ein
   Umschalter für den Blick ins Dunkle gehört in die App; auf einer Seite, die
   man einmal liest, wäre er nur ein zweiter, ungepflegter Anstrich.
   `color-scheme: light` sagt es auch dem Browser, damit er nicht von sich aus
   Formularfelder, Scrollbalken und die Adressleiste dunkel einfärbt. */
:root { color-scheme: light; }

/* Der Sprung zu „Über friends." gleitet, statt zu schneiden — sonst steht man
   plötzlich in einem Text und weiss nicht, dass man sich bewegt hat. Der
   Anker-Link tut das von sich aus, ohne Skript. Wer weniger Bewegung will,
   bekommt den Schnitt (Block ganz unten stellt es zurück). */
html { scroll-behavior: smooth; }

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

body {
    margin: 0;
    font-family: var(--font-main);
    font-weight: 500;
    color: var(--text-primary);
    background: var(--bg-color);
    -webkit-font-smoothing: antialiased;
    line-height: 1.55;
}

h1, h2, h3 { margin: 0; line-height: 1.15; letter-spacing: -0.5px; font-weight: 800; }
p { margin: 0; }
img { max-width: 100%; display: block; }

/* Fokus sichtbar lassen — die Landing wird auch mit der Tastatur bedient. */
:focus-visible { outline: 3px solid var(--accent-turkis); outline-offset: 3px; }

/* --- Buttons -------------------------------------------------------------
   Farbregel wie im Prototyp: Schwarz auf Gelb und auf Türkis, Weiss auf Rosa. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 700;
    padding: 0.95rem 1.75rem;
    border: none;
    border-radius: var(--radius-full);
    cursor: pointer;
    text-decoration: none;
    box-shadow: var(--elev-1-solid);
}
.btn-pink   { background: var(--accent-pink);   color: var(--text-light); }
/* Der eine Knopf dieser Seite ist TÜRKIS: „Probier's aus!" gehört keiner
   Rubrik — Rosa hätte ihn zur Pin-Aktion gemacht. Türkis ist in der App die
   neutrale Aktion, und darauf steht die Schrift schwarz (DS §2). */
.btn-turkis { background: var(--accent-turkis); color: var(--text-primary); }
/* Der leise Zweit-Griff: gefüllte helle Fläche, kein Rahmen — dieselbe Sprache
   wie das Impressum-Label und wie „Fragen hinzufügen" im Prototyp. Rahmen gibt
   es in dieser Gestaltung global nicht (DS §1), und ein zweiter farbiger Knopf
   neben dem türkisen hätte behauptet, beide seien gleich wichtig. */
.btn-hell { background: var(--surface-light); color: var(--text-primary); }


/* ===========================================================================
   STORY — die Akte
   ---------------------------------------------------------------------------
   ZWEI ZONEN, eine Achse: oben die Bühne, unten die Titelzeile. Auf jeder
   Breite dieselbe Aufteilung — nebeneinander wäre ein zweiter Aufbau nur fürs
   Breite, und die App, die hier vorgestellt wird, ist ein Telefon.

   ALLE AKTE LIEGEN ÜBEREINANDER (`position: absolute; inset: 0`), es ist immer
   genau einer sichtbar. Deshalb springt beim Wechsel nichts: die Titelzeile
   sitzt in jedem Akt an derselben Stelle.

   `svh` statt `vh`: sonst springt die Bühne auf dem Telefon, sobald die
   URL-Leiste kommt und geht.
   =========================================================================== */
.story {
    position: relative;
    height: 100svh;
    min-height: 480px;
    overflow: hidden;           /* nichts schaut seitlich heraus */

    /* DIE VIER BEREICHE ALS GRID-ZEILEN — Kopf · Bühne · Titelzeile · Leiste.
       Vorher waren es drei absolut positionierte Kästen, die ihre Höhe aus
       `calc(100% − …)` zogen. Das hielt nicht: Bereiche, die gegen die Höhe
       ihres Elternteils rechnen, hängen davon ab, dass jeder Browser dieselbe
       Höhe meint — und auf Desktop sass der ganze Inhalt darum 370 px über
       seiner eigenen Box. Grid-Zeilen teilen die Höhe auf, statt sie zu
       berechnen: die zwei unteren nehmen, was sie brauchen, die Bühne bekommt
       den Rest. Das ist zugleich die einfachere Beschreibung dessen, was gemeint
       war. */
    display: grid;
    grid-template-rows: var(--kopf-h) minmax(0, 1fr) var(--text-h) var(--bar-h);
    /* EINE Spalte, und jedes Kind nennt sie (`grid-column: 1`). Ohne das legt
       die Auto-Platzierung jedem Element der Leiste eine eigene Spalte an —
       Pfeil, Pfeil, CTA, Punkte standen dann nebeneinander in vier Spalten
       statt übereinander in einer. Dieselbe Falle wie beim Chat-Composer im
       Prototyp, nur andersherum. */
    grid-template-columns: minmax(0, 1fr);
}
.story > * { grid-column: 1; }

/* --- Kopf ----------------------------------------------------------------
   DIE ECKE OBEN LINKS: die Marke, darunter das Impressum — auf jedem Akt an
   derselben Stelle. Sie ist Rahmen und nicht Inhalt, darum klein und ohne eigene
   Bewegung: was auf jedem Akt gleich bleibt, soll nicht bei jedem Akt auffallen.
   `padding: var(--kopf-pad)` heisst: RINGSUM derselbe Abstand — die Marke sitzt
   damit in der linken Ecke, das Impressum in der rechten, und beide haben oben
   und zur Seite dasselbe Mass. Vorher sass die Marke vertikal mittig in der
   Zeile und links auf einem eigenen Wert; das las sich nicht als Ecke.
   Zwei Ecken, ein Polster: `space-between` verteilt, was dazwischen liegt. */
.story-kopf {
    grid-row: 1;
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: var(--kopf-pad);
}
/* 3:1 (900 × 300 in der Datei) — die Breite bestimmt also auch die Höhe, und
   Polster + Logo + Impressum müssen zusammen in `--kopf-h` passen. */
.story-kopf { --logo-w: min(28vw, 108px); }
.kopf-logo { width: var(--logo-w); height: auto; border-radius: 5px; }
/* Auf Desktop grösser: 144px sind 48px hoch. Die Grenze ist dieselbe wie beim
   Prototyp-Link im JS (`max-width: 600px`) — die Seite kennt diesen einen
   Schnitt. Die Kopfzeile wächst mit, sonst liefe das Impressum unten heraus. */
@media (min-width: 601px) {
    .story-kopf { --logo-w: 144px; }
    :root { --kopf-h: 96px; }   /* 24 + 48 + 24 */
}

/* --- Bühne ---------------------------------------------------------------
   DER OBERE BEREICH: alles, was Titelzeile und Leiste übrig lassen. Damit ist
   sie auf dem Telefon rund 500 px hoch und auf Desktop 600 — und nicht ein
   Prozentsatz, der die Zonen mit dem Fenster auseinanderzieht.
   Sie kommt mit Akt 3 und bleibt; bei Akt 3 mit Verzug, damit sie erst da ist,
   wenn der Titel seinen Platz verlassen hat. */
.stage {
    grid-row: 2;
    min-height: 0;              /* sonst wächst die Zeile am Inhalt statt zu teilen */
    display: grid;
    /* EINE Zeile und EINE Spalte, beide `minmax(0, 1fr)` — also mit BESTIMMTER
       Höhe. Ohne das wuchs die Zeile am höchsten Inhalt: das Profil-Bild löste
       `max-height: 100%` gegen eine unbestimmte Höhe auf, nahm seine natürlichen
       931 px, und weil alle Ebenen gedehnt sind, rutschte damit die Kugel jedes
       anderen Akts mit nach unten. Eine Bühne, die sich vom Inhalt aufblasen
       lässt, ist kein Bereich mehr. */
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    place-items: center;
    padding: 1.25rem;
    opacity: 0;
    transition: opacity 700ms ease;
}
.story.stage-on .stage { opacity: 1; }
/* Bei Akt 3 blendet sie GENAU DANN ein, wenn der Titel losfährt — dieselbe
   Zahl, ein Vorgang: der Satz gibt die Mitte frei und die Bühne nimmt sie ein.
   Sie wartete früher, bis die Fahrt durch war (`+ 0.9s`); das war sauber
   getrennt, aber es waren zwei Wartezeiten hintereinander, und der Akt fühlte
   sich zäh an. Dass der Titel dabei kurz vor der Bühne steht, stört nicht — er
   ist ja gerade dabei, ihr Platz zu machen. */
.story.stage-on[data-akt="3"] .stage {
    transition-delay: var(--t-fahrt);
}

/* Eine Ebene je Akt, alle im selben Grid-Feld. Ihre Teile liegen ebenfalls
   übereinander — die Kugel weicht nach hinten, der Beitrag kommt davor. */
.viz {
    grid-area: 1 / 1;
    /* Gedehnt, nicht auf Inhaltsgrösse: die Post-Card rechnet ihre Höhe gegen
       diese Ebene (`height: 100%`), und eine Ebene, die nur so hoch ist wie ihr
       Inhalt, wäre dafür ein Zirkelschluss. Zentriert wird darin weiter. */
    align-self: stretch;
    justify-self: stretch;
    display: grid;
    /* Auch hier bestimmte Zeile und Spalte, aus demselben Grund wie an der
       Bühne: eine `auto`-Zeile ist für ein `max-height: 100%` darin keine Höhe,
       also nahm das Profil-Bild seine natürlichen 931 px und blies die Ebene
       auf. Die Begrenzung muss auf JEDER Ebene stehen, die eine Höhe weitergibt
       — sonst reisst die Kette an der ersten Stelle, die sie nicht nennt. */
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    place-items: center;
    opacity: 0;
}
.viz > * { grid-area: 1 / 1; }
.viz.is-active { opacity: 1; transition: opacity 500ms ease; }

/* ZUERST DER TEXT, DANN DAS BILD — und dazwischen die längste der drei Pausen
   (`--t-viz`). Die Ebene sagt nichts, was der Text nicht schon gesagt hat: sie
   zeigt es, und Zeigen kommt nach dem Sagen.
   Nur diese drei Ebenen brauchen die Regel: Logo (Akt 3) und Symbolreihe
   (Akt 6) bringen ihre Staffelung selbst mit und stehen ohnehin später da. */
.viz[data-viz="updates"].is-active,
.viz[data-viz="pins"].is-active,
.viz[data-viz="profil"].is-active { transition-delay: var(--t-viz); }

/* --- Icons ---------------------------------------------------------------
   `.ic` ist 1:1 aus css/styles.css: 1em gross, ungefüllt, Strich aus dem
   Token — die Grösse steuert also die Schriftgrösse des Elternteils. `.ic-fill`
   ist der gefüllte Gegenpart für die drei Glyphen, die im Prototyp von Font
   Awesome kommen (Blitz, Pinnadel, Person). */
.ic {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.15em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: var(--icon-sw, 2);
    overflow: visible;
}
.ic-fill {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.15em;
    flex-shrink: 0;
    fill: currentColor;
    stroke: none;
}

/* --- Akt 3: das App-Icon ------------------------------------------------
   Es steht allein — das Wortlogo sitzt oben im Kopf. Die Verzögerung liegt
   HINTER dem Verzug der Bühne (1,25 s in Akt 3), sonst wäre es fertig
   aufgeploppt, bevor man es sehen kann.
   Die Originaldatei ist ein randscharfes Quadrat; die Rundung macht daraus ein
   App-Icon (28 % der Seite — die Proportion eines iOS-Icons wäre 23 %, hier
   liegt sie bewusst darüber: das Zeichen steht gross und allein auf der Bühne
   und soll dasselbe weiche Rund haben wie die Kugel des nächsten Akts).

   ES TRÄGT DIE TIEFE DER KUGEL (`--elev-1-solid`), nicht bloss einen
   Schlagschatten: Auf dieser Seite ist es kein Icon auf einem Homescreen,
   sondern das erste vorgestellte Objekt — und die Objekte dieser Seite (Kugel,
   FAB, Reaktions-Kugeln) tragen alle Ebene 1.
   DER BEVEL BRAUCHT EINE EIGENE SCHICHT: `inset`-Schatten werden über dem
   HINTERGRUND, aber UNTER dem Inhalt gezeichnet — an einem <img> liegen sie
   also unter dem Bild und sind unsichtbar. Darum die Hülle mit `::after`
   darüber. Weil das Pseudo-Element deckungsgleich auf der Hülle liegt
   (`inset: 0`), zeichnet es den Schlagschatten gleich mit: der liegt AUSSERHALB
   der Box und stösst mit dem Bild nicht zusammen. So bleibt es EIN Token für
   beides, dasselbe wie an der Kugel. */
.app-icon-huelle {
    position: relative;
    display: block;
    /* DIESELBE Grösse wie die Kugel im nächsten Akt: beide sind das eine
       Zeichen auf einer sonst leeren Bühne, und ein Wechsel, bei dem das eine
       kleiner ist als das andere, liest sich als Rangordnung. */
    width: min(46vw, 34svh, 200px, var(--buehne-h));
    border-radius: 28%;
    opacity: 0;
}
.app-icon {
    width: 100%;
    height: auto;
    border-radius: inherit;     /* eine Rundung, an einer Stelle notiert */
}
.app-icon-huelle::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: var(--elev-1-solid);
    pointer-events: none;
}
/* MIT der Bühne und mit der Fahrt des Titels, nicht danach: `--t-fahrt` ist für
   alle drei dieselbe Zahl. Die Bühne blendet 700 ms lang ein, das Icon poppt in
   480 ms — zusammen ist das ein Auftritt und nicht eine Kette. */
.viz[data-viz="logo"].is-active .app-icon-huelle {
    animation: sym-rein 480ms cubic-bezier(.34, 1.4, .64, 1) var(--t-fahrt) forwards;
}

/* --- Akt 7: der Account-Tab im Telefon ----------------------------------
   Das Gehäuse ist dasselbe wie das der Demo (`.phone`), nur klein: dieselbe
   dunkle Fläche, dieselbe Idee. Seine Masse hängen an der HÖHE des Geräts,
   nicht an festen Pixeln — bei 390 px Breite sind Polster 12 und Radius 56;
   auf die Höhe umgerechnet sind das die drei Faktoren unten.

   DAS GERÄT IST HÖHER ALS DIE BÜHNE. Es beginnt oben an der Bühne und reicht
   nach unten bis hinter die Schrift; sichtbar sind rund DREI VIERTEL des Wegs
   dorthin, das letzte Viertel ist der Übergang ins Nichts. Darum die Rechnung
   `(Bühne + ihr unteres Polster) × 1.3`: `Bühne + Polster` ist genau der Weg
   von der Bühnenkante bis zur Oberkante der Schrift, und ein Dreiviertel davon
   ist bei einem 1.3-fachen Gerät die Marke 58 % — dort endet die volle Deckung,
   bei 77 % (= der Schrift) ist nichts mehr da. So SCHAUT das Telefon hinter der
   Schrift hervor, statt an einer Kante zu enden; ein harter Schnitt an der
   Bühnenkante wäre ein abgeschnittenes Bild, kein Gerät im Anschnitt. */
.profil-telefon {
    --tel-h: calc((var(--buehne-h) + 1.25rem) * 1.3);
    align-self: start;                              /* der Überhang geht nach unten */
    height: var(--tel-h);
    width: auto;                                    /* die Breite bringt das Bild mit */
    padding: calc(var(--tel-h) * 0.0143);
    border-radius: calc(var(--tel-h) * 0.067);
    background: #10131a;                            /* Gehäuse — kein Token, das ist kein UI der App */
    box-shadow:
        0 18px 40px rgba(var(--shadow-rgb), 0.26),
        0 0 0 1px rgba(255, 255, 255, 0.06) inset;
    /* Der Verlauf schneidet Gehäuse UND Schatten — beides gehört dem Gerät. */
    -webkit-mask-image: linear-gradient(to bottom, #000 58%, transparent 77%);
            mask-image: linear-gradient(to bottom, #000 58%, transparent 77%);
}
.profil-bild {
    height: 100%;
    width: auto;
    border-radius: calc(var(--tel-h) * 0.053);
}
/* Fährt beim Auftritt nach oben herein UND blendet dabei ein — eine Bewegung,
   nicht zwei. Sie startet auf `--t-viz`, derselben Zahl wie die Blende ihrer
   Ebene und wie die zwei Griffe im Akt: das Gerät steigt hinter der Schrift auf,
   und die Griffe erscheinen mit ihm.
   Hier stand eine eigene `0.8s`. Die lag VOR `--t-viz`, also war die Fahrt
   vorbei, bevor die Ebene überhaupt sichtbar wurde — man sah nur noch das
   Ergebnis. Eine zweite Zahl neben der, die den Auftritt steuert, geht beim
   Justieren immer irgendwann verloren. */
.viz[data-viz="profil"].is-active .profil-telefon {
    animation: telefon-hoch 900ms cubic-bezier(.22, 1, .36, 1) var(--t-viz) both;
}

@keyframes viz-rein  { to { opacity: 1; transform: none; } }
@keyframes telefon-hoch {
    from { opacity: 0; transform: translateY(56px); }
    to   { opacity: 1; transform: none; }
}

/* --- Die Kugel ----------------------------------------------------------
   KOPIE aus css/styles.css `.chooser-half::before` / `.chooser-icon` /
   `.chooser-label`: gefüllte Fläche in Rubrikfarbe, Icon und Wort mittig
   darin, Schatten = `--elev-1-solid` (dieselben drei Schatten). Nur der
   Durchmesser ist kleiner — im Chooser hat sie den ganzen Screen, hier 54 svh. */
.orb {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    /* Auch an die BÜHNENHÖHE gebunden, nicht nur an die Breite: auf Desktop ist
       die Zone hoch und breit, dort darf sie näher an ihre 188 px im Chooser.
       Das `100%` ist die Bühne selbst, und es ist das entscheidende Mass: auf
       einem breiten, aber niedrigen Fenster nimmt die Titelzeile viel Höhe (die
       Schrift hängt an der Breite), und was der Bühne bleibt, ist wenig. Eine
       Kugel, die nur `svh` kennt, läge dort über der Schrift.
       Die Bühnenhöhe steht als `--buehne-h` zur Verfügung — sie ist gerechnet,
       nicht `100%`: Prozent meint in der Breite etwas anderes als in der Höhe,
       und die Kugel braucht in beiden dieselbe Zahl, sonst ist sie ein Ei. */
    width:  var(--orb-groesse);
    height: var(--orb-groesse);
    border-radius: 50%;
    box-shadow: var(--elev-1-solid);
    /* NACH UNTEN, unter die Kategorien-Zeile. Die Zeile trägt spiegelbildlich
       `margin-bottom: Kugel + Luft` — beide sind mittig ausgerichtet, und ein
       mittiges Grid-Kind zentriert seine MARGIN-Box. Jede der beiden schiebt
       also die andere weg, und die Gruppe aus Zeile und Kugel steht als Ganzes
       mittig auf der Bühne. Ein fester Versatz an nur einem der beiden hätte
       die Gruppe stattdessen nach unten aus der Mitte gezogen. */
    margin-top: calc(var(--kat-h) + var(--kat-luft));
}
.orb-update { background: var(--accent-yellow); color: #000; }   /* Schwarz auf Gelb */
.orb-pin    { background: var(--accent-pink);   color: #fff; }   /* Weiss auf Rosa */
/* Über die HÖHE bemessen, nicht über die Breite: die zwei Glyphen sind schmal
   und hoch (viewBox 320×512 bzw. 384×512), und `width: 34%` machte den Blitz
   109 px hoch auf einer 200er-Kugel — mehr als die halbe Kugel. Im Chooser sitzt
   er bei 2.9rem auf 188 px, also auf knapp einem Viertel der Höhe. */
.orb-icon   { height: 26%; width: auto; }
.orb-label  { font-size: clamp(1.05rem, 4.2vw, 1.45rem); font-weight: 900; letter-spacing: 0.01em; }

/* --- Akt 4/5: die Zeile über der Kugel ------------------------------------
   Sie liegt im selben Grid-Feld wie die Ebenen, ist aber KEINE Ebene: sie gilt
   für Akt 4 UND 5 und bleibt beim Wechsel zwischen ihnen stehen, statt mit den
   Ebenen umzublenden. Ein- und ausgeblendet wird sie darum vom JS.
   Unter ihr standen einmal die zwei Rubrik-Pillen aus dem Feed samt gleitender
   Farbfläche — die sind wieder raus: auf einer Seite, auf der nichts bedienbar
   ist, sieht ein Bedienelement nach einem Griff aus, den es nicht gibt.
   `margin-bottom` = Kugel + Luft: das Gegenstück zum `margin-top` der Kugel,
   siehe dort. */
.kategorien {
    grid-area: 1 / 1;
    align-self: center;
    justify-self: center;
    margin-bottom: calc(var(--orb-groesse) + var(--kat-luft));
    opacity: 0;
    /* Dieselbe Blende wie ein Akt: auf Akt 4/5 ist sie der erste Auftritt und
       damit die Antwort auf den Klick. */
    transition: opacity 340ms ease;
}
/* SIE KOMMT ZUERST — sie ist der Text, der ankündigt, was die Kugel zeigt.
   Titel und Kugel folgen ihr gemeinsam eine lange Pause später (unten). */
.kategorien.is-active { opacity: 1; transition-delay: var(--t-titel); }

.kategorien-zeile {
    margin: 0;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text-muted);
    text-align: center;
}

/* KUGEL UND TITEL GLEICHZEITIG, beide nach der langen Pause. Auf diesen zwei
   Akten führt die Zeile darüber, nicht der Titel — der Titel gehört hier zum
   Gezeigten und kommt mit ihm.
   Der Untertitel rechnet darum gegen `--t-viz` statt gegen `--t-titel`: das ist
   dieselbe Regel wie überall (Titel + mittlere Pause), nur ist der Titel auf
   diesen Akten eben später da. */
.akt-formate { --t-sub: calc(var(--t-viz) + var(--pause)); }
.akt-formate .akt-titel { opacity: 0; transform: translateY(10px); }
.akt-formate.is-active .akt-titel {
    animation: text-rein 520ms ease var(--t-viz) forwards;
}

/* Leises Schweben, solange sie steht — und sie steht den ganzen Akt. */
.viz.is-active .orb { animation: orb-schweben 6s ease-in-out infinite; }
@keyframes orb-schweben {
    0%, 100% { transform: translateY(-5px); }
    50%      { transform: translateY(5px); }
}

/* --- Akt 6: Symbole und Levelbar ---------------------------------------- */
.viz-aktionen {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.6rem;
}
.symbol-reihe {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.9rem;
}
.sym { opacity: 0; }
/* LANGHAND, nicht die Kurzform: die Kurzform setzt `animation-delay` mit auf 0
   und ist spezifischer als die Verzögerungs-Regeln weiter unten (`:not()` zählt
   wie eine Klasse) — die Symbole erschienen dann alle gleichzeitig. */
.viz.is-active .sym:not(.sym-reaktion) {
    animation-name: sym-rein;
    animation-duration: 420ms;
    animation-timing-function: cubic-bezier(.34, 1.4, .64, 1);
    animation-fill-mode: forwards;
}
@keyframes sym-rein {
    from { opacity: 0; transform: scale(.7); }
    to   { opacity: 1; transform: scale(1); }
}

/* Die zwei FAB-Kugeln — unifarben in der Rubrikfarbe, flach (kein Schatten
   nach DS §7 gilt in der App; hier trägt sie den Solid-Bevel, weil sie auf
   dieser Seite ein vorgestelltes Objekt ist und keine schwebende Aktion). */
.sym-fab {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    box-shadow: var(--elev-1-solid);
}
.sym-fab svg { width: 26px; height: 26px; }
.sym-fab-update { background: var(--accent-yellow); color: #000; }
.sym-fab-pin    { background: var(--accent-pink);   color: #fff; }

/* Das Kommentar-Symbol sitzt auf einer weissen Kugel wie die Reaktionen — und
   ist damit so gross wie die zwei FABs daneben. Vorher stand es nackt in der
   Reihe: drei Objekte und ein blosses Zeichen, und das Zeichen sah aus, als
   hätte man seine Fläche vergessen. Die Reihe zeigt drei Aktionen, also drei
   Griffe. Weiss mit Ebene 1, dieselbe Regel wie an den Reaktions-Kugeln (DS §3). */
.sym-komm {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--surface-color);
    box-shadow: var(--elev-1);
    color: var(--text-primary);
}
.sym-komm svg { width: 30px; height: 30px; }

/* Der Reaktions-Kreis vom Long-Press. Die Kugeln tragen Ebene 1 wie eine Card
   (DS §3) — kein Border, kein Vollschatten. Der Bogen kommt über
   `margin-bottom`, nicht über `transform`: der gehört der Einblend-Animation. */
/* IMMER auf einer eigenen, mittigen Zeile — `flex-basis: 100%` erzwingt in der
   umbrechenden Reihe den Zeilenwechsel. Auf dem Telefon passierte das von
   selbst, weil die Breite nicht reichte; auf Desktop stand der Bogen darum als
   einziger neben den drei Symbolen, und dieselbe Grafik sah auf den zwei
   Screengrössen verschieden aus. Ein Umbruch, der vom Zufall der Breite abhängt,
   ist keine Anordnung. */
.sym-reaktion {
    flex-basis: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    opacity: 1;
}
.reaktion-kugel {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--surface-color);
    box-shadow: var(--elev-1);
    font-size: 1.05rem;
    opacity: 0;
}
.reaktion-kugel:nth-child(2), .reaktion-kugel:nth-child(4) { margin-bottom: 10px; }
.reaktion-kugel:nth-child(3) { margin-bottom: 18px; }

/* Levelbar — eingelassene Spur, türkise Füllung: Karma ist keine Rubrik,
   sondern neutral (DS §2). */
.levelbar {
    display: grid;
    gap: 0.5rem;
    justify-items: center;
    width: min(300px, 74vw);
    opacity: 0;
}
.levelbar-spur {
    display: block;
    width: 100%;
    height: 14px;
    border-radius: var(--radius-full);
    background: var(--surface-light);
    box-shadow: inset 0 2px 5px rgba(var(--shadow-rgb), .14);
    overflow: hidden;
}
.levelbar-fuellung {
    display: block;
    height: 100%;
    width: 12%;
    border-radius: inherit;
    background: var(--accent-turkis);
}
.levelbar-text { font-size: 0.85rem; font-weight: 600; color: var(--text-muted); }

/* --- Akte ----------------------------------------------------------------
   `visibility` mit Verzug, damit der Ausgang sichtbar bleibt: ohne den Verzug
   wäre der Akt sofort weg und die Deckkraft-Blende liefe ins Leere.
   Der Eingang wartet 150 ms, sonst schieben sich zwei Titel durcheinander. */
/* DIE TITELZEILE — die dritte Grid-Zeile. Alle Akte liegen darin
   übereinander (`inset: 0`), es ist immer genau einer sichtbar. */
.akte { grid-row: 3; position: relative; }

/* Der Titel sitzt an der OBERKANTE der Zeile — damit steht er in jedem Akt auf
   derselben Höhe, egal ob darunter drei Unterakte kommen oder keiner.
   Die Spalte ist begrenzt, damit der Stempel rechts NEBEN dem Text landet und
   nicht am Bildschirmrand: „unter dem Text" heisst unter dem Text, nicht in der
   Ecke des Bereichs. */
.akt {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-columns: min(100%, 30rem);
    justify-content: center;
    align-content: start;
    justify-items: center;
    text-align: center;
    padding: 0 1.5rem;
    opacity: 0;
    visibility: hidden;
    /* Der abtretende Akt räumt zügig: er ist beim Klick schon gelesen, und
       solange er verblasst, liegt er über dem neuen. `visibility` muss dieselbe
       Zahl tragen wie die Blende — sonst verschwindet er hart, bevor er
       ausgeblendet ist. */
    transition: opacity 320ms ease, visibility 0s 320ms;
}
.akt.is-active {
    opacity: 1;
    visibility: visible;
    /* Kommt FAST SOFORT (`--t-titel`) und zügig: das ist die Antwort auf den
       Klick, nicht die erste Pause der Choreografie. Die Verzögerung stand hier
       einmal als eigene 150 ms — dieselbe Zahl an zwei Orten, und sie sind beim
       Justieren prompt auseinandergelaufen. */
    transition: opacity 340ms ease var(--t-titel), visibility 0s 0s;
}

/* Mittig im Screen: dieselbe Titelzeile, nur hochgeschoben — ein zweiter Anker
   (mal oben, mal Mitte) liesse sich nicht weich dazwischen bewegen.
   `--shift` MISST das JS, es ist nicht geschätzt: der Weg von der Titelzeile in
   die Mitte hängt an der Höhe des Titels, und ob der zwei oder drei Zeilen
   braucht, weiss nur der Browser. Der Wert hier ist bloss der Notnagel, falls
   kein Skript läuft. */
.akt[data-mode="center"] { transform: translateY(var(--shift, -19svh)); }

/* Akt 3 fährt von der Mitte in die Titelzeile und macht der Bühne Platz.
   `both` statt `forwards`, und die Ruhelage ist die TITELZEILE (kein
   `transform` im Grundzustand): Die Mitte gehört allein der Animation — vorher
   stand sie in der Grundregel, und sobald Akt 3 sein `is-active` verlor, fiel
   er für die Dauer des Ausblendens dorthin zurück. Der Satz hüpfte also beim
   Weiterklicken noch einmal kurz in die Bildschirmmitte. Mit `both` liefert
   der `from`-Zustand die Mitte während der Wartezeit, und danach hält `to` die
   Titelzeile — auch dann noch, wenn der Akt abtritt.
   LOSGEFAHREN WIRD, SOBALD DER TITEL STEHT — und der Untertitel kommt danach,
   auf dem angekommenen Block. Vorher war es umgekehrt: erst der ganze Text,
   dann die Fahrt. Der Untertitel dieses Akts nennt seit dem neuen Text, was die
   App NICHT tut (kein Algorithmus, keine Datensammlerei, keine Werbung) — das
   ist der Nachsatz zum Zeichen und gehört ans Ende, nicht davor.
   Bühne und App-Icon hängen an derselben Zahl — siehe `--t-fahrt` oben; die
   Dauer ist die Pause selbst, damit der Block genau zum nächsten Schritt
   landet. */
.akt[data-mode="center-to-title"].is-active {
    animation: akt-runter var(--fahrt-dauer) cubic-bezier(.22, 1, .36, 1) var(--t-fahrt) both;
}
/* Der Untertitel ist hier der DRITTE Schritt (Titel → Fahrt samt Bühne →
   Untertitel), nicht der zweite: gerechnet ab dem Ende der Fahrt, das
   zugleich eine Pause nach ihrem Start liegt. */
.akt[data-mode="center-to-title"] { --t-sub: calc(var(--t-fahrt) + var(--fahrt-dauer)); }
@keyframes akt-runter {
    from { transform: translateY(var(--shift, -19svh)); }
    to   { transform: translateY(0); }
}
/* Rückwärts steht die Bühne schon — dann startet der Titel gleich unten. */
.story.rueckwaerts .akt[data-mode="center-to-title"].is-active {
    animation: none;
    transform: translateY(0);
}

/* --- Der letzte Akt: die zwei Griffe -------------------------------------
   Sie stehen MITTIG IM LEERRAUM unter dem Titel, nicht in der Leiste: die ist
   für die Bedienung der Story da (zurück, wo bin ich), und wer am Ende
   angekommen ist, verlässt sie — das gehört in die Fläche, nicht an den Rand.
   `grid-template-rows: auto minmax(0, 1fr)` gibt dem Titel seine Höhe und den
   Rest der zweiten Zeile; `align-self: center` setzt die Griffe in deren Mitte.
   Ohne die zweite Zeile stünden sie dank `align-content: start` direkt unter dem
   Titel geklebt — die Mitte des Leerraums gibt es nur, wenn der Leerraum eine
   eigene Zeile ist. */
.akt-final { grid-template-rows: auto minmax(0, 1fr); }
.akt-knoepfe {
    align-self: center;
    display: grid;
    gap: 0.7rem;
    /* Gleich breit, damit sie als Paar lesen und nicht als zwei Zufälle. */
    width: min(100%, 17rem);
    opacity: 0;
}
/* DREI SCHRITTE, KEINE ZWEI: Titel → Telefon → Griffe. Die Griffe kamen früher
   gleichzeitig mit dem Telefon („beides zusammen — das Bild und der Weg
   hinein"); auf dem letzten Akt, an dem die Erzählung endet und die
   Entscheidung anfängt, ist das ein Schritt zu wenig. Erst steht der Satz, dann
   steht die App da, und dann erst wird gefragt.

   UND SIE LASSEN SICH ZEIT — die DOPPELTE Pause davor und eine Blende, die
   dreimal so lang läuft wie die einer Textzeile (520 ms). Das ist die zweite
   Stelle der Seite, die aus dem Takt fällt, und aus demselben Grund wie die
   erste (der Schluss von Akt 6): Hier hört die Seite auf zu erzählen. Ein
   Griff, der im Rhythmus der Untertitel hereinschnippt, wäre bloss das nächste
   Element der Aufzählung statt der Frage, die am Ende steht.
   Reines Einfaden, ohne Aufwärtsbewegung: die gehört auf diesem Akt dem Gerät,
   und zwei Dinge, die gleichzeitig aus derselben Richtung kommen, lesen sich
   als eins. */
.akt-final.is-active .akt-knoepfe {
    animation: viz-rein 1560ms ease calc(var(--t-viz) + 2 * var(--pause)) forwards;
}
/* Ohne Skript liegen die Akte untereinander; dort wäre eine gedehnte zweite
   Zeile bloss ein Loch unter dem letzten Titel — und ohne Animation bliebe das
   Paar auf `opacity: 0` stehen. */
.story:not(.is-live) .akt-final { grid-template-rows: none; }
.story:not(.is-live) .akt-knoepfe { margin-top: 1.5rem; opacity: 1; }

.akt-titel {
    font-size: var(--titel-fs);
    max-width: 22ch;
    /* Zeilen gleich lang statt so lang wie möglich. Ohne das brach der erste
       Titel auf Desktop nach „…mit social," um und liess „bitte!" allein in der
       zweiten Zeile stehen — und die Unterstreichung landete im Niemandsland
       zwischen den beiden. */
    text-wrap: balance;
}

/* --- Akt 1: die Unterstreichung ----------------------------------------
   Unterstrichen ist „mit social" mitsamt der Präposition: die Aussage steckt
   in „mit", nicht in „social". Der Strich zeichnet sich über `stroke-dashoffset`
   — die Länge (210) ist die Pfadlänge im viewBox-Raum (200 breit). */
/* Das „mit" steht kursiv — betont ist die Präposition, nicht das Substantiv.
   ECHTE SCHRÄGE, keine nachgeahmte: Geologica ist eine Variable Font mit einer
   `slnt`-Achse (−12 … 0) und einer `CRSV`-Achse (0 … 1, die kursiven
   Buchstabenformen). `font-style: italic` fände in der Datei keinen kursiven
   Schnitt und liesse den Browser die Schrift selbst schrägstellen — eine
   verzerrte Fassung neben der echten, die daneben steht. Darum `font-style:
   normal` (das <em> bringt `italic` mit) und die Achsen direkt.
   `wght` STEHT MIT DABEI, obwohl `font-weight` es schon setzt: die zwei
   Eigenschaften greifen auf dieselbe Achse, und wo eine Engine sie nicht
   zusammenrechnet, fiele das Wort auf den Achsen-Default (100 = hauchdünn)
   zurück — mitten im 800er-Titel wäre das kein Detail, sondern ein Loch. Die
   800 sind die des Titels (siehe `h1, h2, h3`). */
.kursiv {
    font-style: normal;
    font-variation-settings: 'wght' 800, 'slnt' -12, 'CRSV' 1;
}

.unterstrich { position: relative; white-space: nowrap; }
.unterstrich-linie {
    position: absolute;
    left: -2%;
    /* Etwas hochgezogen: `100%` ist die Unterkante der Zeilenbox, und die reicht
       bei `line-height: 1.15` deutlich unter die Buchstaben. */
    top: calc(100% - 0.2em);
    width: 104%;
    height: 0.45em;
    fill: none;
    stroke: var(--accent-turkis);
    stroke-width: 3.4;
    stroke-linecap: round;
    stroke-dasharray: 210;
    stroke-dashoffset: 210;
}
/* Eine Pause nach dem Titel — dieselbe Rechnung wie jeder zweite Schritt der
   Seite. Hier stand eine gegriffene `0.8s`; die lag daneben, sobald die Pause
   sich änderte. */
.akt.is-active .unterstrich-linie {
    animation: strich 700ms cubic-bezier(.4, 0, .3, 1) calc(var(--t-titel) + var(--pause)) forwards;
}
@keyframes strich { to { stroke-dashoffset: 0; } }

/* --- Akt 2: der Stempel -------------------------------------------------
   Kommt zu gross und schräg herein und wird aufgesetzt — nicht eingeblendet.
   ER WIPPT NICHT MEHR. Vorher federte er über die Zielgrösse hinaus und wieder
   zurück (.93 → 1.06 → 1). Ein Stempel wird gedrückt und bleibt dann liegen;
   was danach noch nachschwingt, ist Gummi. Die Kurve ist darum eine reine
   Verzögerung ohne Überschwingen — dieselbe, mit der auch das Telefon im
   letzten Akt aufsteigt.
   Er steht IM FLUSS unter dem Titel, rechts. Vorher sass er absolut auf einer
   svh-Höhe und lief bei einem dreizeiligen Titel mitten durch die letzte Zeile:
   „unter dem Text" ist eine Aussage über den Text, nicht über den Bildschirm —
   also muss der Text ihn schieben. */
.stempel {
    justify-self: end;
    margin: 0.4rem 0 0;
    font-size: clamp(4rem, 17vw, 7rem);
    line-height: 1;
    opacity: 0;
    transform: rotate(-16deg);
}
/* DIESER ABSTAND IST DAS MASS DER GANZEN SEITE — was hier zwischen Text und
   Stempel liegt, liegt überall zwischen zwei Schritten (siehe `--pause`). */
.akt.is-active .stempel {
    animation: stempeln 520ms cubic-bezier(.22, 1, .36, 1) calc(var(--t-titel) + var(--pause)) forwards;
}
@keyframes stempeln {
    0%   { opacity: 0; transform: rotate(-34deg) scale(2.4); }
    /* Die Deckkraft ist früher fertig als die Bewegung: er wird sichtbar,
       während er noch aufsetzt — nicht erst danach. */
    45%  { opacity: 1; }
    100% { opacity: 1; transform: rotate(-16deg) scale(1); }
}

/* --- Unterakte ---------------------------------------------------------
   Sie stehen von Anfang an im Fluss (nur unsichtbar), damit der Titel nicht
   nachrutscht, wenn sie eintreffen. Das Timing steht hier, nicht im JS. */
.unterakte {
    list-style: none;
    margin: 1.1rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.5rem;
    line-height: 1.4;           /* 1.55 aus dem Body ist für kurze Zeilen zu viel */
}
.unterakte li {
    font-size: clamp(1.05rem, 3.6vw, 1.3rem);
    font-weight: 600;
    color: var(--text-secondary);
    max-width: 30ch;
    opacity: 0;
    transform: translateY(10px);
}
/* Untereinander die HALBE Pause: eine Aufzählung ist ein Atemzug, kein neuer
   Gedanke. Der erste kommt dagegen im vollen Abstand zu dem, was vor ihm
   stand — er fängt die Erklärung an.
   Der Schritt steht als eigener Wert, weil ein Akt ihn dehnen darf (siehe
   Pins unten); die Reihenfolge der Zeilen bleibt dieselbe Rechnung. */
.unterakte { --sub-schritt: var(--pause-halb); }
.akt.is-active .unterakte li { animation: text-rein 520ms ease forwards; }
.akt.is-active .unterakte li:nth-child(1) { animation-delay: var(--t-sub); }
.akt.is-active .unterakte li:nth-child(2) { animation-delay: calc(var(--t-sub) + var(--sub-schritt)); }
.akt.is-active .unterakte li:nth-child(3) { animation-delay: calc(var(--t-sub) + 2 * var(--sub-schritt)); }

/* PINS DEHNT DEN SCHRITT AUFS DOPPELTE — zwei Untertitel, aber dieselbe Dauer
   wie die drei von Updates: der letzte steht auf beiden Akten im selben
   Moment (2.15 s). Updates und Pins sind ein Paar, das man direkt
   hintereinander sieht; liefe der eine kürzer, läse sich das als „hier ist
   weniger", nicht als „hier steht weniger". Gerechnet, nicht abgeschrieben:
   zwei Schritte statt drei, also der doppelte Schritt. */
.akt[data-viz="pins"] .unterakte { --sub-schritt: calc(2 * var(--pause-halb)); }

@keyframes text-rein { to { opacity: 1; transform: none; } }

/* --- Akt 6: Titel Teil für Teil ----------------------------------------
   Die Verzögerungen der Titel-Stücke und der Symbole stehen absichtlich
   direkt untereinander: jedes Stück holt sein Symbol nach. Der Takt dazu steht
   am Akt selbst (siehe unten) und ist bewusst nicht der der Seite. */
.akt-aktionen .akt-titel {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0 0.4rem;
    max-width: 28ch;
}
.teil { opacity: 0; transform: translateY(8px); }
.akt.is-active .teil { animation: text-rein 450ms ease forwards; }

/* DIESER AKT LÄUFT IN DER VOLLEN PAUSE, wie die ganze übrige Seite. Er lief
   kurz in der halben („Posten, kommentieren und reagieren" ist EIN Satz, der
   sich abrollt) — mit drei Schritten in 0,7 s war das aber weniger ein Satz als
   ein Abhaken. Und die Symbole tragen inzwischen dieselben Schritte wie die
   Wörter, also ist ein Schritt hier auch nicht weniger wert als anderswo.

   JEDES SYMBOL KOMMT MIT SEINEM WORT, nicht danach. Vorher lagen zwei
   Intervalle ineinander (Wort → Symbol → nächstes Wort), also sechs Schritte
   für drei Aussagen — und zwischen zwei Wörtern lag dann doch wieder fast eine
   ganze Pause. Wort und Zeichen sind dasselbe gesagt und gezeigt; sie gehören
   in denselben Schritt. Übrig bleiben drei Takte.
   Die Zeitpunkte sind auseinander gerechnet, damit sich beim Justieren alles
   mitverschiebt. */
.akt-aktionen, .viz-aktionen {
    --t-p1: var(--t-titel);
    --t-s1: var(--t-p1);
    --t-p2: calc(var(--t-p1) + var(--pause));
    --t-s2: var(--t-p2);
    --t-p3: calc(var(--t-p2) + var(--pause));
    --t-s3: var(--t-p3);
    /* Der Schluss (Unterakt + Levelbar, die zusammen erscheinen) steht im
       DOPPELTEN Abstand — der einzige Ort der Seite, an dem eine Pause länger
       ist als der Takt. Der Grund: davor lief nicht ein Schritt, sondern der
       ganze Satz in dreien, und was jetzt kommt, ist keine Fortsetzung mehr,
       sondern eine neue Aussage (das Karma). Im einfachen Abstand las sich das
       wie ein vierter Satzteil. */
    --t-schluss: calc(var(--t-s3) + 2 * var(--pause));
}
.akt.is-active .teil-1 { animation-delay: var(--t-p1); }
.viz.is-active .sym-1       { animation-delay: var(--t-s1); }
/* Die zweite Kugel einen Tick später — und der Tick ist derselbe, mit dem auch
   die fünf Reaktions-Kugeln aufploppen: zwei Teile EINES Auftritts, kein
   eigener Schritt. Hier standen 0.15 s; das war knapp die halbe Pause und las
   sich damit wie ein vierter Takt. */
.viz.is-active .sym-fab-pin { animation-delay: calc(var(--t-s1) + 0.09s); }

.akt.is-active .teil-2 { animation-delay: var(--t-p2); }
.viz.is-active .sym-2  { animation-delay: var(--t-s2); }

.akt.is-active .teil-3 { animation-delay: var(--t-p3); }
/* Die fünf Kugeln sind EIN Auftritt, nicht fünf: sie ploppen in 90-ms-Schritten
   auf, wie der Kreis im Prototyp sich öffnet. Das ist keine vierte Pause,
   sondern die Bewegung dieses einen Zeichens. */
.viz.is-active .sym-3 .reaktion-kugel {
    animation: sym-rein 380ms cubic-bezier(.34, 1.4, .64, 1) forwards;
}
.viz.is-active .sym-3 .reaktion-kugel:nth-child(1) { animation-delay: var(--t-s3); }
.viz.is-active .sym-3 .reaktion-kugel:nth-child(2) { animation-delay: calc(var(--t-s3) + 0.09s); }
.viz.is-active .sym-3 .reaktion-kugel:nth-child(3) { animation-delay: calc(var(--t-s3) + 0.18s); }
.viz.is-active .sym-3 .reaktion-kugel:nth-child(4) { animation-delay: calc(var(--t-s3) + 0.27s); }
.viz.is-active .sym-3 .reaktion-kugel:nth-child(5) { animation-delay: calc(var(--t-s3) + 0.36s); }

/* Der Unterakt kommt später als sonst — davor lief der Titel in drei Stücken.
   Er ist der Untertitel dieses Akts, also die mittlere Pause nach dem Letzten. */
.akt.is-active .unterakte-spaet li:nth-child(1) { animation-delay: var(--t-schluss); }
.viz.is-active .levelbar { animation: viz-rein 500ms ease var(--t-schluss) forwards; }
/* DIESELBE ZAHL wie die Blende der Leiste: Der Balken läuft schon los, während
   sie noch einblendet. Er wartete früher 0,4 s — dadurch stand die Leiste einen
   Moment fertig und leer da, und man sah zwei Vorgänge nacheinander statt eines.
   Ein Fortschrittsbalken, der beim Erscheinen bereits läuft, sagt „das füllt
   sich gerade"; einer, der erst dasteht und dann anspringt, sagt „hier wird
   etwas nachgereicht". */
.viz.is-active .levelbar-fuellung {
    animation: level-fuellen 1.6s cubic-bezier(.22, 1, .36, 1) var(--t-schluss) forwards;
}
@keyframes level-fuellen { to { width: 78%; } }

/* --- Steuerung -----------------------------------------------------------
   Links und rechts, aber in der UNTEREN Leiste, nicht vertikal mittig: dort
   stehen in Akt 1–3 die mittigen Titel, und ein Pfeil über der Schrift ist
   ein Griff, der seinen eigenen Inhalt verdeckt. Unten bilden Pfeile, Punkte
   und der CTA zusammen eine Leiste — und sie stossen mit nichts zusammen. */
.story-nav {
    grid-row: 4;
    align-self: center;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: none;
    border-radius: var(--radius-full);
    background: var(--surface-color);
    color: var(--text-primary);
    box-shadow: var(--elev-3);
    cursor: pointer;
    transition: opacity 300ms ease, visibility 0s 300ms;
}
/* Auf schmalen Screens am Rand, auf breiten an den Kanten einer zentrierten
   Spalte: zwei Pfeile, die auf einem 27-Zoll-Monitor 1900 px auseinanderstehen,
   sind kein Paar mehr. Der Abstand steht als `--rand` an EINER Stelle, weil ihn
   drei Elemente brauchen (Pfeile, CTA) und die Punkte gegen ihn rechnen.
   Das `50%` darin meint überall dasselbe: die Breite der Grid-Zelle — bei
   `margin` wie bei `padding` löst sich Prozent gegen die Inline-Grösse der
   Grid-Area auf, und die ist für alle vier die volle Zeile. */
.story { --rand: max(0.75rem, calc(50% - var(--spalte) / 2 + 0.75rem)); }
.story-prev { justify-self: start; margin-left:  var(--rand); }
.story-next { justify-self: end;   margin-right: var(--rand); }
.story-nav[disabled] { opacity: 0.28; cursor: default; }
.story-nav[hidden] { display: none; }

/* AUF BREITEN FENSTERN STEHEN SIE SEITLICH, vertikal in der Bildschirmmitte.
   Der Einwand von oben — „ein Pfeil über der Schrift verdeckt seinen eigenen
   Inhalt" — gilt dort nicht mehr: neben der 640er-Spalte liegt freies Weiss,
   und genau dorthin gehen sie. Mittig zwischen Bildschirmrand und Spaltenkante,
   also auf `--rand / 2`; die 24px sind der halbe Griff, damit seine MITTE dort
   sitzt und nicht seine Kante.
   `grid-row: 1 / -1` statt `position: absolute`: ein absolut gesetztes Kind
   eines Grids mit bestimmter Grid-Position bekommt die GRID-AREA als
   Bezugsrahmen, nicht die Seite — `top: 50%` wäre die Mitte der 76px-Leiste
   geworden. Über alle Zeilen gespannt und `align-self: center` meint dieselbe
   Mitte, ohne die Falle.
   Die Grenze ist 820px und nicht 601px: der freie Rand ist `50% − 320px`, also
   erst ab ~768px so breit wie der Griff selbst. Darunter blieben die Pfeile in
   der Leiste — sonst hingen sie halb ausserhalb des Fensters. */
@media (min-width: 820px) {
    .story-nav {
        grid-row: 1 / -1;
        align-self: center;
        /* SONST SIND SIE NICHT ANKLICKBAR. Über alle Zeilen gespannt liegen sie
           auf der Höhe von `.akte`, und das ist `position: relative` — also ein
           positioniertes Element, das über allem Unpositionierten gemalt wird.
           Die Pfeile lagen damit UNTER der Akt-Fläche und bekamen keinen Klick
           mehr ab; sichtbar waren sie trotzdem, weil der Akt daneben durchsichtig
           ist. In der unteren Leiste stellte sich die Frage nie: dort lag `.akte`
           gar nicht. */
        position: relative;
        z-index: 3;
    }
    .story-prev { margin-left:  calc(var(--rand) / 2 - 24px); }
    .story-next { margin-right: calc(var(--rand) / 2 - 24px); }
}

/* Am Ende geht nur der WEITER-Pfeil (per `hidden` aus dem JS). Zurück bleibt
   stehen: der Rückweg ist auf der letzten Seite so gut wie auf jeder anderen,
   und ein Griff, der ausgerechnet dort verschwindet, wo man ihn zum
   Nochmal-Lesen braucht, ist ein Ausgang zu wenig.
   HIER SASS EINMAL DER CTA, rechts in der Leiste an der Stelle des Pfeils. Er
   steht jetzt als Paar mitten im Akt (`.akt-knoepfe`, unten): aus einem Griff
   sind zwei geworden, und zwei nebeneinander in der Leiste hätten Punkte und
   Pfeile verdrängt. Mit ihm ist auch die Messung seiner Breite gefallen — die
   Punkte mussten ihm ausweichen, solange er neben ihnen stand. */

/* Dieselbe Zeile wie die Pfeile, damit die drei auf einer Linie sitzen.
   Die Punkte stehen NICHT im Screen zentriert, sondern ZWISCHEN den Griffen:
   die Zeile ist über die volle Breite gespannt und links wie rechts um den
   Platz eines Griffs eingerückt. Da links und rechts dasselbe steht (zwei
   Pfeile), ist das dieselbe Mitte — nur ohne dass die Punkte unter einen Griff
   geraten können. */
.story-dots {
    grid-row: 4;
    align-self: center;
    justify-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    margin: 0;
    padding-inline: calc(var(--rand) + 48px + 0.5rem);   /* 48px = ein Griff */
    padding-block: 0;
    list-style: none;
    pointer-events: none;
}
/* Sind die Pfeile seitlich (ab 820px, siehe oben), steht in der Leiste nichts
   mehr, dem die Punkte ausweichen müssten.
   Diese Regel steht bewusst NACH der Basis-Regel der Punkte: sie ist gleich
   spezifisch, und bei Gleichstand gewinnt die spätere. */
@media (min-width: 820px) {
    .story-dots { padding-inline: 0; }
}
.story-dots li {
    width: 7px;
    height: 7px;
    border-radius: var(--radius-full);
    background: var(--hairline);
    transition: background 300ms ease;
}
.story-dots li.is-active { background: var(--accent-turkis); }

/* --- Impressum -----------------------------------------------------------
   Ein kleines Label unter der Marke, auf derselben linken Kante — auf JEDEM Akt.
   Drei frühere Fassungen und warum sie gefallen sind:
   · Es kam einmal erst mit dem letzten Akt, damit es nicht in die Erzählung
     hineinredet. Das Kleingedruckte gehört aber der Seite und nicht ihrem
     Schluss, und ein Griff, der irgendwann von selbst auftaucht, sieht aus wie
     ein Fehler. Leise ist es ohnehin.
   · Es hing einmal hochkant an der linken Kante, unten bei den Pfeilen — auf der
     Achse der Griffe, die die Story bedienen, wo es nichts zu suchen hat.
   · Es hing einmal als angeschnittener Reiter an der oberen Kante und stand
     einmal unter der Marke. Jetzt sitzt es in der RECHTEN oberen Ecke, mit
     demselben Polster wie die Marke in der linken — zwei Ecken, ein Mass.
   `position: relative`, damit die Tafel an ihm hängt; `z-index`, damit sie über
   Bühne und Akt liegt statt darunter. */
.impressum {
    position: relative;
    z-index: 2;
}

/* DER GRIFF IST EIN UMKREISTES „i", kein Wort. Hier stand „Impressum" als
   kleines Label auf der eingelassenen Spur — in der Ecke gegenüber der Marke
   war das ein zweites Wort auf dem Kopf der Seite und las sich wie eine zweite
   Beschriftung. Das Zeichen sagt dasselbe („hier steht das Kleingedruckte"),
   ohne im Kopf mitzureden; der Name lebt im `aria-label` weiter.
   OHNE FLÄCHE: Der Kreis des Zeichens IST seine Form — eine zweite, gefüllte
   Scheibe darunter wären zwei Kreise ineinander. Damit fällt zugleich die
   Tiefe weg, die neben der Marke ohnehin nichts zu suchen hatte.
   Es steht in `--text-secondary` — sichtbar, aber leise.
   Solange das Label an der linken Kante hing, stand hier `writing-mode:
   vertical-rl` + `rotate(180deg)` („von unten nach oben lesen"), und der Radius
   musste spiegelverkehrt notiert werden, weil `rotate` die ganze Box mitsamt
   ihren Ecken dreht. Beides ist mit dem Umzug in die Ecke weggefallen. */
.impressum-griff {
    /* Zeichen und Luft ringsum, aus denen sich alles andere rechnet — das Feld
       ist grösser als das Zeichen, damit der Daumen es auf dem Telefon trifft.
       Drei Werte hingen sonst aneinander (Grösse, Feld, Ausgleich) und liefen
       beim nächsten Justieren auseinander. */
    --zeichen: 20px;
    --luft: 10px;

    display: grid;
    place-items: center;
    width:  calc(var(--zeichen) + 2 * var(--luft));
    height: calc(var(--zeichen) + 2 * var(--luft));
    /* Das Polster des Kopfes zählt schon zum Rand — der Griff soll mit seiner
       ZEICHEN-Kante auf einer Achse mit dem Logo stehen, nicht mit seinem
       Trefferfeld. Darum der negative Ausgleich nach aussen: genau die Luft. */
    margin: calc(-1 * var(--luft)) calc(-1 * var(--luft)) 0 0;
    padding: 0;
    border: none;
    background: none;
    font-size: var(--zeichen);  /* `.ic` ist 1em gross */
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color 250ms ease;
}
/* Offen wird das Zeichen dunkler — mehr Zustand braucht es nicht: die Tafel
   steht dann darunter und sagt es selbst. */
.impressum.is-offen .impressum-griff { color: var(--text-primary); }

/* Die Tafel fährt NACH UNTEN aus dem Label heraus und steht auf derselben
   RECHTEN Kante (`right: 0`) — zwei Dinge, die zusammengehören, stehen auf einer
   Achse. Nach links aufzugehen wäre die Alternative gewesen; nach unten ist die
   Richtung, in der die Seite Platz hat, und sie ist auf dem Telefon dieselbe wie
   auf Desktop.
   Kein Höhen-/Breiten-Aufklappen: die Tafel liegt frei über der Seite, es
   muss nichts Platz machen, und ein Text, der beim Aufgehen umbricht, liest
   sich schlechter als einer, der fertig hereinkommt.
   Die `max-width` ist GERECHNET, nicht gegriffen: das Fenster minus dem
   Eck-Polster auf beiden Seiten — rechts sitzt sie darauf, links soll sie nicht
   dagegenstossen. Sonst liefe sie auf dem Telefon aus dem Bild. */
.impressum-tafel {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 0.5rem;
    width: max-content;
    max-width: min(34ch, calc(100vw - 2 * var(--kopf-pad)));
    padding: 0.75rem 0.95rem 0.8rem;
    background: var(--surface-color);
    border-radius: 16px;        /* kein Token: diese Seite hat sonst keine Cards */
    box-shadow: var(--elev-3);
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--text-secondary);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity 220ms ease,
                transform 340ms cubic-bezier(.22, 1, .36, 1),
                visibility 0s 340ms;
}
.impressum.is-offen .impressum-tafel {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s, 0s, 0s;
}
.impressum-tafel p + p { margin-top: 0.5rem; }
/* Die Mail-Adresse ist ein echter `mailto:`-Link — eine Kontaktadresse, die man
   nicht antippen kann, ist eine halbe Auskunft.
   NICHT türkis: Türkis ist in dieser Gestaltung eine FLÄCHENfarbe (Schwarz
   darauf, DS §2). Als Schrift auf Weiss kommt es auf rund 1,9:1 und wäre bei
   0.75rem nicht zu lesen. Der Link trägt darum die Textfarbe und wird durch die
   Unterstreichung als Link erkennbar — die trägt ohnehin weiter als Farbe. */
.impressum-tafel a {
    color: var(--text-primary);
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

/* --- Ohne Skript --------------------------------------------------------
   Dann stehen die Akte lesbar untereinander. Eine Seite, die ohne Skript
   leer ist, ist auch für Suchmaschinen leer. */
.story:not(.is-live) { display: block; height: auto; min-height: 0; overflow: visible; padding: 3rem 0; }
.story:not(.is-live) .stage,
.story:not(.is-live) .story-nav,
.story:not(.is-live) .story-dots { display: none; }
.story:not(.is-live) .akte { position: static; }
.story:not(.is-live) .akt {
    position: static;
    height: auto;
    padding: 0 1.5rem 2.5rem;
    opacity: 1;
    visibility: visible;
    transform: none;
}
.story:not(.is-live) .unterakte li,
.story:not(.is-live) .teil { opacity: 1; transform: none; }
.story:not(.is-live) .stempel { justify-self: center; margin: 0.5rem 0 0; opacity: 1; }
/* Ohne Skript fährt nichts heraus — also steht der Text gleich da und das
   Wort, das ihn sonst holt, fällt weg. Ein Griff ohne Wirkung wäre schlechter
   als keiner. Die Tafel verliert dabei auch ihre Schwebe-Optik: an einer Seite,
   die einfach untereinander läuft, schwebt sie über nichts. */
/* Als Block, nicht als Flex-Zeile: der Impressum-Text steht ohne Skript offen
   da, und in einer rechten Flex-Zelle würde er auf ein paar Zentimeter
   gequetscht. Untereinander ist er, was er dann ist — ein Absatz unter der
   Marke. */
.story:not(.is-live) .story-kopf { display: block; padding: 1.5rem; }
.story:not(.is-live) .impressum {
    position: static;
    margin: 1rem 0 0;
}
.story:not(.is-live) .impressum-griff { display: none; }
.story:not(.is-live) .impressum-tafel {
    position: static;
    margin: 0;
    padding: 0;
    max-width: none;
    background: none;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
}


/* ===========================================================================
   ÜBER FRIENDS. — der Lesetext unter der Story
   ---------------------------------------------------------------------------
   Die Story ist genau ein Screen hoch und blättert; hier wird gescrollt. Das
   ist bewusst der Bruch: davor eine Erzählung in Akten, die man durchklickt,
   danach ein Text, den man liest. Ein Fliesstext in Akte zerlegt wäre eine
   Diashow aus Absätzen.
   ERREICHBAR über den Griff „Mehr erfahren" im letzten Akt — als ANKER-Link
   (`href="#ueber"`), nicht über ein Skript: so führt er auch dann irgendwohin,
   wenn kein JS läuft, und die Adresse merkt sich die Stelle.
   Die Spalte ist schmaler als die der Story (34rem statt 40): dort stehen
   Schlagzeilen, hier Absätze, und für die ist eine kurze Zeile die lesbarere. */
.ueber {
    /* Das Polster oben ist zugleich die Luft nach dem Sprung — ein eigenes
       `scroll-margin-top` braucht es darum nicht. */
    padding: clamp(3.5rem, 10vw, 7rem) var(--kopf-pad) clamp(4rem, 12vw, 8rem);
}
.ueber-inner {
    max-width: 34rem;
    margin: 0 auto;
}
.ueber-titel {
    font-size: clamp(1.9rem, 6vw, 2.6rem);
    margin-bottom: 1.6rem;
}
/* Die Zwischentitel sind kleiner als der Abschnittstitel und tragen mehr Luft
   ÜBER als unter sich: der Abstand nach oben trennt vom Vorigen, der nach unten
   bindet an das, was zu ihnen gehört. */
.ueber-sub {
    font-size: clamp(1.25rem, 4vw, 1.55rem);
    margin: 2.8rem 0 0.9rem;
}
.ueber p {
    font-size: clamp(1rem, 3.4vw, 1.08rem);
    line-height: 1.7;
    color: var(--text-secondary);
}
.ueber p + p { margin-top: 1.1rem; }

/* Die Animation ist bewusst schlicht: jeder Block steigt einmal leise ein,
   sobald er ins Bild kommt. Dieselbe Bewegung wie `text-rein` in der Story
   (8–10 px von unten, weiches Ausklingen) — die Seite soll nicht auf halber
   Höhe eine zweite Sprache anfangen.
   OHNE `is-live` steht alles gleich da: die Klasse setzt das Skript, und was
   ohne Skript unsichtbar wäre, ist auch für Suchmaschinen unsichtbar. */
.story.is-live ~ .ueber .ueber-teil {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 600ms ease, transform 600ms cubic-bezier(.22, 1, .36, 1);
}
.story.is-live ~ .ueber .ueber-teil.is-sichtbar {
    opacity: 1;
    transform: none;
}


/* ===========================================================================
   DEMO — der Prototyp über der Story (nur Desktop)
   =========================================================================== */
.demo {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    background: var(--bg-color);
}
.demo[hidden] { display: none; }
body.demo-offen { overflow: hidden; }

.demo-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: none;
    border-radius: var(--radius-full);
    background: var(--surface-color);
    color: var(--text-primary);
    box-shadow: var(--elev-3);
    cursor: pointer;
}

/* Gehäuse aus dem Desktop-Rahmen des Prototyps (css/styles.css `.desk-device`)
   übernommen, damit die App hier so sitzt wie dort. */
.phone {
    position: relative;
    width: 390px;
    max-width: 100%;
    height: min(844px, 88svh);
    padding: 12px;
    border-radius: 56px;
    background: #10131a;              /* Gehäuse — kein Token: das ist kein UI der App */
    box-shadow:
        0 32px 60px rgba(var(--shadow-rgb), 0.28),
        0 0 0 1px rgba(255, 255, 255, 0.06) inset;
}
.phone-screen {
    width: 100%;
    height: 100%;
    border-radius: 44px;
    background: var(--surface-color);
    overflow: hidden;
}
.phone-screen iframe {
    width: 100%;
    height: 100%;
    border: none;
    display: block;
}


/* ===========================================================================
   WENIGER BEWEGUNG — alles steht sofort da, nichts läuft nacheinander.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .ueber .ueber-teil { transition-duration: 1ms !important; }
    .story * { transition-duration: 1ms !important; animation-duration: 1ms !important; }
    .story * { animation-delay: 0s !important; transition-delay: 0s !important; }
    .akt[data-mode="center-to-title"].is-active { animation: none; transform: translateY(0); }
    .viz.is-active .orb { animation: none; }
    .viz[data-viz="updates"].is-active .orb,
    .viz[data-viz="pins"].is-active .orb { animation: orb-zurueck 1ms forwards; }
    .unterakte li, .teil, .sym, .reaktion-kugel, .levelbar,
    .akt-knoepfe, .app-icon-huelle { opacity: 1; transform: none; }
    .unterstrich-linie { stroke-dashoffset: 0; }
    .stempel { opacity: 1; }
    .levelbar-fuellung { width: 78%; }
}
