/* ==========================================================================
   WEBEFFEKTE
   Scroll gebundene Sektionseffekte, reines CSS.
   Stand 09.09.2026
   Quelle /var/www/_shared/webeffekte/effekte.css

   GRUNDGESETZE
   1  Endzustand ist Standardzustand. Ohne Animation sieht die Seite
      normal aus, nichts ist unsichtbar, nichts fehlt.
   2  Kein Text wird per Javascript erzeugt. Alles steht im HTML.
   3  Keine Aussenverbindung. Keine Fonts, keine CDN, keine Bibliothek.
   4  prefers-reduced-motion schaltet jeden Effekt ab.
   5  Hoechstens drei verschiedene Bausteine pro Seite.
   6  NIE overflow: hidden auf einem Vorfahren eines Bausteins.
      hidden erzeugt einen Scrollbehaelter, die Effekte messen dann
      gegen diesen statt gegen das Fenster und frieren ein.
      Zum Abschneiden immer overflow: clip verwenden.

   BROWSERLAGE Stand 09.09.2026
   Chrome und Edge ab 115, Opera ab 101, Safari ab 26: voll.
   Firefox: nur hinter Flag, Anteil Deutschland etwa 10 Prozent.
   Dort faellt der Effekt aus, die Seite bleibt vollstaendig nutzbar.
   ========================================================================== */

:root {
  --fx-dauer-kurz: entry 10% cover 40%;
  --fx-dauer-mittel: entry 5% cover 55%;
  --fx-dauer-lang: entry 0% cover 80%;
}

/* Gemeinsamer Schalter. Alles darunter laeuft nur, wenn der Browser
   den Standard kann und der Nutzer Bewegung nicht abgewaehlt hat. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {

    /* ------------------------------------------------------------------
       01 VORHANG
       Sektion bleibt stehen, die naechste schiebt sich darueber.
       HTML: <section class="fx-vorhang">…</section>
             Die FOLGENDE Sektion braucht fx-vorhang-decke.
       ------------------------------------------------------------------ */
    .fx-vorhang {
      position: sticky;
      top: 0;
      z-index: 1;
    }
    .fx-vorhang-decke {
      position: relative;
      z-index: 2;
      background: var(--fx-deckfarbe, #ffffff);
    }

    /* ------------------------------------------------------------------
       02 AUFZUG
       Foto oder Video waechst von klein auf volle Breite.
       HTML: <div class="fx-aufzug"><img …></div>
       ------------------------------------------------------------------ */
    .fx-aufzug {
      animation-name: fx-aufzug-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 0% cover 45%;
      transform-origin: center center;
    }
    @keyframes fx-aufzug-k {
      from { transform: scale(0.62); border-radius: 16px; }
      to   { transform: scale(1);    border-radius: 0; }
    }

    /* ------------------------------------------------------------------
       03 TEXTANFLUG
       Schrift schiebt sich seitlich ein, Kinder zeitversetzt.
       HTML: <div class="fx-textanflug"><h2>…</h2><p>…</p></div>
       Modifier fx-textanflug--rechts kehrt die Richtung um.
       ------------------------------------------------------------------ */
    .fx-textanflug > * {
      animation-name: fx-anflug-links;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: var(--fx-dauer-kurz);
    }
    .fx-textanflug > *:nth-child(2) { animation-range: entry 16% cover 46%; }
    .fx-textanflug > *:nth-child(3) { animation-range: entry 22% cover 52%; }
    .fx-textanflug > *:nth-child(n+4) { animation-range: entry 28% cover 58%; }
    .fx-textanflug--rechts > * { animation-name: fx-anflug-rechts; }
    @keyframes fx-anflug-links {
      from { transform: translateX(-44px); opacity: 0; }
    }
    @keyframes fx-anflug-rechts {
      from { transform: translateX(44px); opacity: 0; }
    }

    /* ------------------------------------------------------------------
       04 ZEILENAUFZUG
       Zeilen fahren nacheinander aus einer Maske hoch.
       HTML: <h2 class="fx-zeilenaufzug"><span>Zeile eins</span>
                                          <span>Zeile zwei</span></h2>
       ------------------------------------------------------------------ */
    .fx-zeilenaufzug > span {
      display: block;
      overflow: clip;
    }
    .fx-zeilenaufzug > span > * ,
    .fx-zeilenaufzug > span {
      will-change: transform;
    }
    .fx-zeilenaufzug > span {
      animation-name: fx-zeile;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 12% cover 42%;
    }
    .fx-zeilenaufzug > span:nth-child(2) { animation-range: entry 20% cover 50%; }
    .fx-zeilenaufzug > span:nth-child(3) { animation-range: entry 28% cover 58%; }
    .fx-zeilenaufzug > span:nth-child(n+4) { animation-range: entry 36% cover 66%; }
    @keyframes fx-zeile {
      from { clip-path: inset(100% 0 0 0); transform: translateY(0.6em); }
      to   { clip-path: inset(0 0 0 0);    transform: translateY(0); }
    }

    /* ------------------------------------------------------------------
       05 WORTGLUT
       Woerter hellen beim Scrollen nacheinander auf.
       HTML: <p class="fx-wortglut"><span>Ein</span> <span>Tag</span> …</p>
       Wichtig: der Satz steht vollstaendig im HTML, nur in Spans zerlegt.
       ------------------------------------------------------------------ */
    .fx-wortglut > span {
      color: var(--fx-glut-aus, #a8a8a2);
      animation-name: fx-glut;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 20% cover 70%;
    }
    @keyframes fx-glut {
      to { color: var(--fx-glut-an, inherit); }
    }
    .fx-wortglut > span:nth-child(1) { animation-delay: calc(0 * -1ms); }

    /* ------------------------------------------------------------------
       06 FOKUS
       Unscharf zu scharf.
       HTML: <div class="fx-fokus">…</div>
       ------------------------------------------------------------------ */
    .fx-fokus {
      animation-name: fx-fokus-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 8% cover 40%;
    }
    @keyframes fx-fokus-k {
      from { filter: blur(14px); opacity: 0.25; }
      to   { filter: blur(0);    opacity: 1; }
    }

    /* ------------------------------------------------------------------
       07 SPERRUNG
       Buchstabenabstand zieht sich zusammen.
       HTML: <h1 class="fx-sperrung">Dirk Dittrich</h1>
       ------------------------------------------------------------------ */
    .fx-sperrung {
      animation-name: fx-sperrung-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 5% cover 50%;
    }
    @keyframes fx-sperrung-k {
      from { letter-spacing: 0.42em; opacity: 0.35; }
      to   { letter-spacing: 0.02em; opacity: 1; }
    }

    /* ------------------------------------------------------------------
       08 SEITWAERTSSTRECKE
       Sektion steht, eine Fotoreihe zieht horizontal durch.
       HTML: <section class="fx-strecke">
               <div class="fx-strecke-buehne">
                 <div class="fx-strecke-reihe"> … Karten … </div>
               </div>
             </section>
       Hoehe der Sektion steuert die Laufzeit.
       ------------------------------------------------------------------ */
    .fx-strecke {
      min-height: 300vh;
      view-timeline: --fx-strecke-t block;
    }
    .fx-strecke-buehne {
      position: sticky;
      top: 0;
      height: 100dvh;
      display: flex;
      align-items: center;
      overflow: clip;
    }
    .fx-strecke-reihe {
      display: flex;
      gap: 1.5rem;
      padding-inline: 5vw;
      animation-name: fx-strecke-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --fx-strecke-t;
      animation-range: contain;
    }
    @keyframes fx-strecke-k {
      to { transform: translateX(calc(-100% + 90vw)); }
    }

    /* ------------------------------------------------------------------
       09 MASKENWUCHS
       Ausschnitt oeffnet sich zum Vollbild.
       HTML: <div class="fx-maskenwuchs">…</div>
       ------------------------------------------------------------------ */
    .fx-maskenwuchs {
      animation-name: fx-maske-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 0% cover 50%;
    }
    @keyframes fx-maske-k {
      from { clip-path: inset(28% 22% 28% 22% round 16px); }
      to   { clip-path: inset(0 0 0 0 round 0); }
    }

    /* ------------------------------------------------------------------
       10 LINIENZUG
       Eine SVG Linie zeichnet sich beim Scrollen.
       HTML: <svg …><path class="fx-linienzug" pathLength="1" …></svg>
       pathLength="1" macht die Laenge unabhaengig von der Geometrie.
       ------------------------------------------------------------------ */
    .fx-linienzug {
      stroke-dasharray: 1;
      animation-name: fx-linie-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 10% cover 70%;
    }
    @keyframes fx-linie-k {
      from { stroke-dashoffset: 1; }
      to   { stroke-dashoffset: 0; }
    }

    /* ------------------------------------------------------------------
       11 ZAEHLER
       Zahlen laufen hoch.
       HTML: <span class="fx-zaehler" style="--fx-ziel:280">280</span>
       PFLICHT: die Zielzahl steht als Text im Element. Ohne sie liest
       Google eine Null. Der Effekt ueberdeckt sie nur.
       ------------------------------------------------------------------ */
    @property --fx-n {
      syntax: "<integer>";
      initial-value: 0;
      inherits: false;
    }
    .fx-zaehler {
      counter-reset: fxn var(--fx-n);
      animation-name: fx-zaehler-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 20% cover 60%;
    }
    .fx-zaehler::after {
      content: counter(fxn);
      position: absolute;
      inset: 0;
      background: inherit;
    }
    @keyframes fx-zaehler-k {
      from { --fx-n: 0; }
      to   { --fx-n: var(--fx-ziel, 100); }
    }

    /* ------------------------------------------------------------------
       12 SPLITFAHRT
       Zwei Spalten laufen gegenlaeufig. Nur Desktop.
       HTML: <section class="fx-split">
               <div class="fx-split-a">…</div>
               <div class="fx-split-b">…</div>
             </section>
       ------------------------------------------------------------------ */
    .fx-split {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 1.5rem;
      overflow: clip;
      view-timeline: --fx-split-t block;
    }
    .fx-split-a,
    .fx-split-b {
      display: grid;
      gap: 1.5rem;
      animation-timeline: --fx-split-t;
      animation-range: cover;
      animation-duration: auto;
      animation-fill-mode: both;
      animation-timing-function: linear;
    }
    .fx-split-a { animation-name: fx-split-hoch; }
    .fx-split-b { animation-name: fx-split-runter; }
    @keyframes fx-split-hoch   { from { transform: translateY(6%);  } to { transform: translateY(-6%); } }
    @keyframes fx-split-runter { from { transform: translateY(-6%); } to { transform: translateY(6%);  } }
    @media (max-width: 900px) {
      .fx-split { grid-template-columns: 1fr; }
      .fx-split-a, .fx-split-b { animation: none; transform: none; }
    }

    /* ------------------------------------------------------------------
       13 FARBDURCHZUG
       Sektionsfarbe wechselt beim Scrollen, Schrift kippt mit.
       HTML: <section class="fx-farbdurchzug">…</section>
       Farben ueber --fx-von, --fx-nach, --fx-text-von, --fx-text-nach.
       ------------------------------------------------------------------ */
    .fx-farbdurchzug {
      animation-name: fx-farbe-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover;
    }
    @keyframes fx-farbe-k {
      from { background-color: var(--fx-von, #ffffff);  color: var(--fx-text-von, #111111); }
      to   { background-color: var(--fx-nach, #16123a); color: var(--fx-text-nach, #f2f0ff); }
    }

    /* ------------------------------------------------------------------
       14 BLENDE
       Zwei Haelften fahren auseinander. Nur als Auftakt ganz oben.
       HTML: <section class="fx-blende">
               <div class="fx-blende-inhalt">…</div>
               <div class="fx-blende-links"></div>
               <div class="fx-blende-rechts"></div>
             </section>
       ------------------------------------------------------------------ */
    .fx-blende { position: relative; overflow: clip; }
    .fx-blende-links,
    .fx-blende-rechts {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 50.5%;
      background: var(--fx-blendfarbe, #ffffff);
      animation-timeline: view();
      animation-range: entry 0% cover 45%;
      animation-duration: auto;
      animation-fill-mode: both;
      animation-timing-function: linear;
      pointer-events: none;
    }
    .fx-blende-links  { left: 0;  animation-name: fx-blende-l; }
    .fx-blende-rechts { right: 0; animation-name: fx-blende-r; }
    @keyframes fx-blende-l { to { transform: translateX(-100%); } }
    @keyframes fx-blende-r { to { transform: translateX(100%); } }

    /* ------------------------------------------------------------------
       15 KARTENSTAPEL
       Karten stapeln sich mit sichtbarem Rand.
       HTML: <div class="fx-stapel">
               <article class="fx-stapel-karte">…</article> x 4 bis 6
             </div>
       ------------------------------------------------------------------ */
    .fx-stapel { display: grid; gap: 2rem; }
    .fx-stapel-karte {
      position: sticky;
      top: calc(6vh + var(--fx-stapel-i, 0) * 2.2rem);
      animation-name: fx-stapel-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: exit-crossing 0% exit 100%;
    }
    .fx-stapel-karte:nth-child(1) { --fx-stapel-i: 0; }
    .fx-stapel-karte:nth-child(2) { --fx-stapel-i: 1; }
    .fx-stapel-karte:nth-child(3) { --fx-stapel-i: 2; }
    .fx-stapel-karte:nth-child(4) { --fx-stapel-i: 3; }
    .fx-stapel-karte:nth-child(5) { --fx-stapel-i: 4; }
    .fx-stapel-karte:nth-child(6) { --fx-stapel-i: 5; }
    @keyframes fx-stapel-k {
      to { transform: scale(0.94); }
    }

    /* ------------------------------------------------------------------
       16 PERSPEKTIVKIPPUNG
       Element kommt gekippt herein und richtet sich auf.
       HTML: <div class="fx-kippbuehne"><div class="fx-kippung">…</div></div>
       ------------------------------------------------------------------ */
    .fx-kippbuehne { perspective: 1200px; }
    .fx-kippung {
      transform-origin: center bottom;
      animation-name: fx-kipp-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 0% cover 45%;
    }
    @keyframes fx-kipp-k {
      from { transform: rotateX(42deg) translateY(30px); opacity: 0.3; }
      to   { transform: rotateX(0)     translateY(0);    opacity: 1; }
    }

    /* ------------------------------------------------------------------
       17 RANDZIEHUNG
       Rahmen oder Unterstreichung zeichnet sich.
       HTML: <svg …><rect class="fx-randziehung" pathLength="1" …></svg>
       ------------------------------------------------------------------ */
    .fx-randziehung {
      stroke-dasharray: 1;
      animation-name: fx-rand-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 15% cover 55%;
    }
    @keyframes fx-rand-k {
      from { stroke-dashoffset: 1; }
      to   { stroke-dashoffset: 0; }
    }

    /* ------------------------------------------------------------------
       18 VORHER NACHHER
       Ein Foto schiebt sich ueber das andere.
       HTML: <div class="fx-vn">
               <img class="fx-vn-vorher"  …>
               <img class="fx-vn-nachher" …>
             </div>
       Beide Fotos brauchen echte alt Texte.
       ------------------------------------------------------------------ */
    .fx-vn { position: relative; }
    .fx-vn-nachher {
      position: absolute;
      inset: 0;
      animation-name: fx-vn-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 10% cover 65%;
    }
    @keyframes fx-vn-k {
      from { clip-path: inset(0 100% 0 0); }
      to   { clip-path: inset(0 0 0 0); }
    }

    /* ------------------------------------------------------------------
       19 LESEBAND
       Fortschrittsbalken oben. Nur auf langen Textseiten.
       HTML: <div class="fx-leseband" aria-hidden="true"></div>
       ------------------------------------------------------------------ */
    .fx-leseband {
      position: fixed;
      top: 0;
      left: 0;
      height: 3px;
      width: 100%;
      background: var(--fx-bandfarbe, #534ab7);
      transform-origin: 0 50%;
      transform: scaleX(0);
      z-index: 999;
      animation-name: fx-band-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: scroll(root block);
    }
    @keyframes fx-band-k {
      to { transform: scaleX(1); }
    }

    /* ------------------------------------------------------------------
       20 FARBENTZUG
       Foto kommt grau herein und wird farbig.
       HTML: <img class="fx-farbentzug" …>
       ------------------------------------------------------------------ */
    .fx-farbentzug {
      animation-name: fx-entzug-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 10% cover 50%;
    }
    @keyframes fx-entzug-k {
      from { filter: grayscale(100%) saturate(40%); }
      to   { filter: grayscale(0)    saturate(100%); }
    }


    /* ------------------------------------------------------------------
       21 PARALLAX EBENEN
       Mehrere Schichten bewegen sich unterschiedlich schnell.
       HTML: <section class="fx-parallax-buehne">
               <div class="fx-parallax-ebene" style="--fx-tempo:20">…</div>
               <div class="fx-parallax-ebene" style="--fx-tempo:55">…</div>
               <div class="fx-parallax-ebene" style="--fx-tempo:95">…</div>
             </section>
       --fx-tempo ist der Gesamtweg in Pixel. Hoeherer Wert heisst
       schneller. Abstand zwischen langsamster und schnellster Ebene
       nie ueber 90.
       ------------------------------------------------------------------ */
    .fx-parallax-buehne { position: relative; overflow: clip; }
    .fx-parallax-ebene {
      animation-name: fx-par-ebene;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover;
      will-change: transform;
    }
    @keyframes fx-par-ebene {
      from { transform: translateY(calc(var(--fx-tempo, 40) * 0.5px)); }
      to   { transform: translateY(calc(var(--fx-tempo, 40) * -0.5px)); }
    }

    /* ------------------------------------------------------------------
       22 PARALLAX BILDFENSTER
       Das Foto bewegt sich langsamer als sein Rahmen.
       HTML: <figure class="fx-parallax-fenster">
               <img src="…" alt="…">
             </figure>
       Der Rahmen braucht eine feste Hoehe, sonst gibt es nichts zu
       verschieben. Ueber aspect-ratio oder height.
       ------------------------------------------------------------------ */
    .fx-parallax-fenster > img,
    .fx-parallax-fenster > picture > img {
      position: absolute;
      inset: -14% 0;
      width: 100%;
      height: 128%;
      object-fit: cover;
      animation-name: fx-par-fenster;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover;
      will-change: transform;
    }
    @keyframes fx-par-fenster {
      from { transform: translateY(-7%); }
      to   { transform: translateY(7%); }
    }


    /* ==================================================================
       RUBRIK TEXT
       ================================================================== */

    /* 23 LICHTSTREIF  Klasse A
       HTML: <h2 class="fx-lichtstreif">Ein Tag, der bleibt</h2>
       Grundfarbe ueber --fx-ls-grund, Reflex ueber --fx-ls-licht. */
    .fx-lichtstreif {
      color: transparent;
      background-clip: text;
      -webkit-background-clip: text;
      background-size: 280% 100%;
      background-image: linear-gradient(100deg,
        var(--fx-ls-grund, currentColor) 0 38%,
        var(--fx-ls-licht, #ffffff) 46%,
        var(--fx-ls-akzent, var(--fx-ls-licht, #ffffff)) 52%,
        var(--fx-ls-grund, currentColor) 60% 100%);
      animation-name: fx-lichtstreif-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 10% cover 70%;
    }
    @keyframes fx-lichtstreif-k {
      from { background-position: 140% 0; }
      to   { background-position: -140% 0; }
    }

    /* 24 MASKENWISCH  Klasse A
       HTML: <h2 class="fx-maskenwisch">Wir sind fuer Sie da</h2> */
    .fx-maskenwisch {
      animation-name: fx-maskenwisch-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 10% cover 45%;
    }
    @keyframes fx-maskenwisch-k {
      from { clip-path: inset(0 100% 0 0); }
      to   { clip-path: inset(0 0 0 0); }
    }

    /* 25 ZEILENKIPPUNG  Klasse B, ein span je Zeile
       HTML: <h2 class="fx-zeilenkippung"><span>Zeile</span><span>Zeile</span></h2> */
    .fx-zeilenkippung { perspective: 800px; }
    .fx-zeilenkippung > span {
      display: block;
      transform-origin: center top;
      animation-name: fx-zeilenkippung-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(10% + (sibling-index() - 1) * 9%) cover calc(42% + (sibling-index() - 1) * 9%);
    }
    @keyframes fx-zeilenkippung-k {
      from { transform: rotateX(84deg) translateY(24px); opacity: 0.1; }
      to   { transform: rotateX(0) translateY(0); opacity: 1; }
    }

    /* 26 ZEILENSCHUB  Klasse B, ein span je Zeile */
    .fx-zeilenschub { overflow-x: clip; }
    .fx-zeilenschub > span {
      display: block;
      animation-name: fx-zeilenschub-l;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(10% + (sibling-index() - 1) * 9%) cover calc(44% + (sibling-index() - 1) * 9%);
    }
    .fx-zeilenschub > span:nth-child(even) { animation-name: fx-zeilenschub-r; }
    @keyframes fx-zeilenschub-l { from { transform: translateX(-70px); opacity: 0; } }
    @keyframes fx-zeilenschub-r { from { transform: translateX(70px);  opacity: 0; } }

    /* 27 UNTERSTRICH  Klasse A
       HTML: <h3 class="fx-unterstrich">Was Sie erwartet</h3> */
    .fx-unterstrich { position: relative; display: inline-block; }
    .fx-unterstrich::after {
      content: "";
      position: absolute;
      left: 0; right: 0; bottom: -0.3em;
      height: 3px;
      border-radius: 2px;
      background: var(--fx-strichfarbe, currentColor);
      transform-origin: 0 50%;
      animation-name: fx-unterstrich-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 15% cover 50%;
    }
    @keyframes fx-unterstrich-k {
      from { transform: scaleX(0); }
      to   { transform: scaleX(1); }
    }

    /* 28 KICKER  Klasse A
       HTML: <div class="fx-kicker">
               <span class="fx-kicker-label">Meine Geschichte, Teil 3</span>
               <h2>Sie sind einfach gekommen.</h2>
             </div> */
    .fx-kicker { overflow: clip; }
    .fx-kicker-label {
      display: block;
      animation-name: fx-kicker-l;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 8% cover 38%;
    }
    .fx-kicker > h1, .fx-kicker > h2, .fx-kicker > h3 {
      animation-name: fx-kicker-h;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 20% cover 50%;
    }
    @keyframes fx-kicker-l { from { transform: translateY(100%); opacity: 0; } }
    @keyframes fx-kicker-h { from { transform: translateY(16px); opacity: 0; } }

    /* 29 ABSATZAUFHELLUNG  Klasse A
       HTML: <p class="fx-aufhellung">Langer Absatz …</p> */
    .fx-aufhellung {
      animation-name: fx-aufhellung-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 20% cover 65%;
    }
    @keyframes fx-aufhellung-k {
      from { color: var(--fx-ah-von, #6d675f); }
      to   { color: var(--fx-ah-nach, inherit); }
    }

    /* 30 LESEACHSE  Klasse A
       HTML: <div class="fx-leseachse"><p>…</p><p>…</p></div> */
    .fx-leseachse > p {
      animation-name: fx-leseachse-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover;
    }
    @keyframes fx-leseachse-k {
      0%   { opacity: 0.34; filter: blur(1.3px); }
      38%  { opacity: 1;    filter: blur(0); }
      62%  { opacity: 1;    filter: blur(0); }
      100% { opacity: 0.34; filter: blur(1.3px); }
    }

    /* ==================================================================
       RUBRIK ELEMENTE
       ================================================================== */

    /* 31 AUFTRITT  Klasse A
       Kurzer Bereich, wirkt dadurch wie ein einmaliges Einblenden.
       HTML: <section class="fx-auftritt">…</section> */
    .fx-auftritt {
      animation-name: fx-auftritt-k;
      animation-duration: auto;
      animation-timing-function: cubic-bezier(.22,.9,.3,1);
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 18% entry 62%;
    }
    @keyframes fx-auftritt-k {
      from { transform: translateY(26px); opacity: 0; }
      to   { transform: translateY(0);    opacity: 1; }
    }

    /* 32 STAFFEL  Klasse A, Zusatz zu Auftritt, Hebung, Klappe …
       HTML: <div class="fx-staffel"><div class="fx-hebung">…</div> … </div>
       Verschiebt den Bereich je Kind. --fx-takt steuert den Abstand. */
    .fx-staffel > * {
      --fx-versatz: calc((sibling-index() - 1) * var(--fx-takt, 7%));
    }
    .fx-staffel > .fx-auftritt {
      animation-range: entry calc(18% + var(--fx-versatz)) entry calc(62% + var(--fx-versatz));
    }
    .fx-staffel > .fx-hebung,
    .fx-staffel > .fx-klappe,
    .fx-staffel > .fx-faecher,
    .fx-staffel > .fx-listenhaken {
      animation-range: entry calc(12% + var(--fx-versatz)) cover calc(45% + var(--fx-versatz));
    }

    /* 33 HEBUNG  Klasse A */
    .fx-hebung {
      animation-name: fx-hebung-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 12% cover 45%;
    }
    @keyframes fx-hebung-k {
      from { transform: translateY(18px) scale(0.985); opacity: 0.25; }
      to   { transform: translateY(0) scale(1);        opacity: 1; }
    }

    /* 34 RASTERWELLE  Klasse A
       HTML: <div class="fx-rasterwelle" style="--fx-spalten:3"> … </div>
       Diagonaler Versatz ueber Zeile plus Spalte. */
    .fx-rasterwelle > * {
      --fx-sp: var(--fx-spalten, 3);
      --fx-i: calc(sibling-index() - 1);
      --fx-reihe: calc(round(down, var(--fx-i) / var(--fx-sp), 1));
      --fx-spalte: calc(var(--fx-i) - var(--fx-reihe) * var(--fx-sp));
      --fx-diag: calc((var(--fx-reihe) + var(--fx-spalte)) * 6%);
      animation-name: fx-hebung-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(8% + var(--fx-diag)) cover calc(40% + var(--fx-diag));
    }

    /* 35 KLAPPE  Klasse A */
    .fx-klappe {
      transform-origin: center top;
      animation-name: fx-klappe-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 12% cover 45%;
    }
    @keyframes fx-klappe-k {
      from { transform: rotateX(84deg); opacity: 0.1; }
      to   { transform: rotateX(0);     opacity: 1; }
    }
    .fx-klappe-buehne { perspective: 1100px; }

    /* 36 FAECHER  Klasse A
       Winkel je Karte ueber --fx-winkel, Standard aus der Position. */
    .fx-faecher {
      transform-origin: center bottom;
      animation-name: fx-faecher-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 12% cover 45%;
    }
    @keyframes fx-faecher-k {
      from { transform: rotate(var(--fx-winkel, -9deg)) translateY(30px) scale(0.9); opacity: 0.15; }
      to   { transform: rotate(0) translateY(0) scale(1); opacity: 1; }
    }

    /* 37 TRENNLINIE  Klasse A
       HTML: <hr class="fx-trennlinie"> */
    .fx-trennlinie {
      border: 0;
      height: 1px;
      background: var(--fx-linienfarbe, currentColor);
      opacity: 0.35;
      transform-origin: 50% 50%;
      animation-name: fx-trennlinie-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 15% cover 50%;
    }
    @keyframes fx-trennlinie-k {
      from { transform: scaleX(0); }
      to   { transform: scaleX(1); }
    }

    /* 38 LISTENHAKEN  Klasse A
       HTML: <ul class="fx-liste"><li class="fx-listenhaken">…</li></ul> */
    .fx-listenhaken {
      animation-name: fx-listenhaken-k;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(10% + (sibling-index() - 1) * 6%) cover calc(40% + (sibling-index() - 1) * 6%);
    }
    @keyframes fx-listenhaken-k {
      from { transform: translateX(-20px); opacity: 0.1; }
      to   { transform: translateX(0);     opacity: 1; }
    }
    .fx-listenhaken > .fx-haken {
      animation-name: fx-haken-k;
      animation-duration: auto;
      animation-timing-function: cubic-bezier(.34,1.4,.64,1);
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(14% + (sibling-index() - 1) * 6%) cover calc(38% + (sibling-index() - 1) * 6%);
    }
    @keyframes fx-haken-k {
      from { transform: scale(0.3); }
      to   { transform: scale(1); }
    }

    /* 42 SANFTES AUFTAUCHEN  Klasse A
       Foto blendet auf, wenn es geladen ist. Braucht reservierten Platz. */
    .fx-auftauchen {
      animation-name: fx-auftauchen-k;
      animation-duration: 0.5s;
      animation-timing-function: ease;
      animation-fill-mode: both;
    }
    @keyframes fx-auftauchen-k { from { opacity: 0; } to { opacity: 1; } }

  } /* @supports */
} /* prefers-reduced-motion */

/* ==========================================================================
   FALLBACK OHNE ANIMATION
   Diese Regeln gelten IMMER. Sie sorgen dafuer, dass die Seite ohne
   Effekt vollstaendig und richtig aussieht. Nichts hier darf etwas
   verstecken.
   ========================================================================== */
.fx-zeilenaufzug > span { display: block; }
.fx-vn { position: relative; }
.fx-leseband { display: none; }
.fx-strecke-reihe { display: flex; gap: 1.5rem; overflow-x: auto; }
.fx-stapel { display: grid; gap: 2rem; }
.fx-zaehler { position: relative; display: inline-block; }
.fx-parallax-fenster { position: relative; overflow: clip; }
.fx-parallax-fenster > img { width: 100%; height: 100%; object-fit: cover; display: block; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .fx-leseband { display: block; }
    .fx-strecke-reihe { overflow-x: visible; }
  }
}


/* ==========================================================================
   RUBRIK UEBERGAENGE UND ZUSTAENDE
   Diese Bausteine haengen NICHT am Scrollrad. Sie reagieren auf einen Klick
   oder auf einen Seitenwechsel. Sie brauchen deshalb kein view() und laufen
   auch dort, wo scrollgebundene Effekte ausfallen.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {

  /* 39 SEITENWECHSEL
     Weicher Uebergang zwischen zwei Unterseiten. In BEIDE Seiten einbauen. */
  @view-transition { navigation: auto; }

  /* 40 FOTOFLUG
     Dasselbe Foto behaelt auf beiden Seiten denselben Namen und wandert.
     HTML Galerie:      <img style="view-transition-name: foto-17" …>
     HTML Detailseite:  <img style="view-transition-name: foto-17" …>
     Der Name muss je Seite EINMALIG sein. */
  ::view-transition-group(*) { animation-duration: 0.42s; }
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 0.3s; }

  /* 41 AUFKLAPPER
     HTML: <div class="fx-aufklapper" data-offen="false">
             <button class="fx-aufklapper-kopf">Frage</button>
             <div class="fx-aufklapper-koerper"><div>Antwort</div></div>
           </div>
     Der Zustand wird ueber data-offen oder eine Klasse gesetzt. */
  .fx-aufklapper-koerper {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.34s cubic-bezier(.4,0,.2,1);
  }
  .fx-aufklapper-koerper > * { overflow: hidden; }
  .fx-aufklapper[data-offen="true"] .fx-aufklapper-koerper { grid-template-rows: 1fr; }
  .fx-aufklapper-pfeil { transition: transform 0.3s ease; }
  .fx-aufklapper[data-offen="true"] .fx-aufklapper-pfeil { transform: rotate(180deg); }

  /* 43 UEBERLAGERUNG und 44 LICHTKASTEN
     Fuer dialog und popover. @starting-style verhindert das Aufblitzen. */
  .fx-overlay {
    opacity: 1;
    transition: opacity 0.24s ease, overlay 0.24s allow-discrete, display 0.24s allow-discrete;
  }
  .fx-overlay:not(:popover-open):not(dialog[open]) { opacity: 0; }
  @starting-style { .fx-overlay:popover-open, dialog.fx-overlay[open] { opacity: 0; } }

  .fx-overlay-kasten {
    transform: translateY(0) scale(1);
    transition: transform 0.3s cubic-bezier(.34,1.3,.64,1);
  }
  @starting-style {
    .fx-overlay:popover-open .fx-overlay-kasten,
    dialog.fx-overlay[open] .fx-overlay-kasten { transform: translateY(16px) scale(0.97); }
  }

  /* 45 MENUEFAHRT
     HTML: <div class="fx-menue" data-offen="false">
             <div class="fx-menue-panel"><div><a class="fx-menue-eintrag">…</a></div></div>
           </div> */
  .fx-menue-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.32s cubic-bezier(.4,0,.2,1);
  }
  .fx-menue-panel > * { overflow: hidden; }
  .fx-menue[data-offen="true"] .fx-menue-panel { grid-template-rows: 1fr; }
  .fx-menue-eintrag {
    opacity: 0;
    transform: translateY(9px);
    transition: opacity 0.3s ease, transform 0.3s ease;
    transition-delay: calc((sibling-index() - 1) * 40ms);
  }
  .fx-menue[data-offen="true"] .fx-menue-eintrag { opacity: 1; transform: translateY(0); }

}

/* Fallback: ohne Animation ist alles offen und sichtbar, nichts fehlt. */
.fx-aufklapper[data-offen="true"] .fx-aufklapper-koerper > * { overflow: visible; }
.fx-menue-eintrag { opacity: 1; }
