/* =============================================================
   Bewegung
   =============================================================
   Alles hier bewegt ausschließlich transform, opacity und clip-path —
   die drei Eigenschaften, die der Browser auf der Grafikkarte erledigt,
   ohne das Layout neu zu rechnen. Jede Reveal-Animation läuft genau
   einmal.

   Drei Regeln stehen über allem:
   · Ohne JavaScript ist alles sofort sichtbar (ohne-js.css und die
     .js-Bedingung unten).
   · Bei prefers-reduced-motion passiert nichts, alles steht im
     Endzustand.
   · Der Hero verzögert nichts. Warum die H1 dort anders behandelt wird
     als die großen H2, steht beim Wort-Reveal.
   ============================================================= */

:root{
  --dur-s:.38s;
  --dur-m:.62s;
  --dur-l:.9s;
  /* Ruhig auslaufend, ohne Überschwingen. Kein Bounce — der Auftrag
     will wertig, nicht verspielt. */
  --ease-out:cubic-bezier(.16,.84,.34,1);
  --ease-inout:cubic-bezier(.65,0,.35,1);
  --stagger:48ms;
}

/* ============================================================
   1 · Wort-für-Wort-Reveal für große Überschriften
   ============================================================
   motion.js zerlegt die Überschrift in Wörter. Jedes Wort steckt in
   einer Maske (overflow:hidden) und fährt von unten herein.

   Barrierefreiheit: Das Skript hängt der Überschrift ein aria-label mit
   dem vollständigen Satz an und versteckt die Wort-Spans vor der
   Vorlesesoftware. Ohne das läse sie Wort für Wort mit Pausen vor.

   Die H1 im Hero läuft mit. Die naheliegende Sorge — der Reveal
   verzögert das LCP — habe ich gemessen statt angenommen: Das
   LCP-Element ist auf allen Seiten das Hero-BILD, nicht die
   Überschrift. Das Foto ist größer als der Text davor. Die Zahlen
   stehen in _docs/qa/motion/lcp.md. */
.wort-maske{display:inline-block;overflow:hidden;vertical-align:bottom}
.wort{display:inline-block;will-change:transform}

.js [data-worte]:not(.ist-da) .wort{transform:translateY(108%)}
.js [data-worte] .wort{
  transform:translateY(0);
  transition:transform var(--dur-l) var(--ease-out);
  transition-delay:calc(var(--i,0) * var(--stagger));
}
/* will-change nur während der Bewegung: Dauerhaft gesetzt belegt es
   Speicher für jede Zeile Text auf der Seite. */
.js [data-worte].ist-fertig .wort{will-change:auto}

/* Das Akzentwort bekommt einen Strich, der sich zieht, sobald die
   Überschrift steht. Höchstens zwei bis drei solcher Momente je Seite —
   deshalb hängt die Regel am em und nicht an jeder Auszeichnung. */
.js [data-worte] em{position:relative}
.js [data-worte] em::after{
  content:"";position:absolute;left:0;right:0;bottom:-.06em;height:.075em;
  background:var(--orange);border-radius:.05em;
  transform:scaleX(0);transform-origin:left center;
}
.js [data-worte].ist-fertig em::after{
  transform:scaleX(1);
  transition:transform var(--dur-m) var(--ease-out);
}

/* ---------- Überzeile: Sperrung zieht sich zusammen ---------- */
.js [data-ueber]{
  opacity:0;letter-spacing:.3em;
  transition:opacity var(--dur-m) var(--ease-out),
             letter-spacing var(--dur-l) var(--ease-out);
}
/* Die Endsperrung kommt aus --ueber-sperrung, damit die schmale
   Variante aus hero.css hier nicht überschrieben wird. */
.js [data-ueber].ist-da{opacity:1;letter-spacing:var(--ueber-sperrung,.14em)}

/* ============================================================
   2 · Sektionen
   ============================================================
   Der alte Zustand hob den Inhalt um 8 bzw. 20 px an. Das war kaum zu
   sehen. 40 px ist die Strecke, bei der die Bewegung spürbar wird, ohne
   dass der Text springt. */
.js [data-reveal]:not(.ist-sichtbar),
.js [data-zeig]:not(.ist-sichtbar){opacity:0;transform:translateY(40px)}
.js [data-reveal],.js [data-zeig]{
  transition:opacity var(--dur-m) var(--ease-out),
             transform var(--dur-m) var(--ease-out);
}

/* Gruppen laufen gestaffelt statt gleichzeitig. Der Index kommt aus
   motion.js, damit die Reihenfolge der Lesefolge entspricht. */
.js [data-staffel] > *{transition-delay:calc(var(--i,0) * var(--stagger))}

/* Abwechslung: Nicht jede Sektion tritt gleich auf. Welche Sektion
   welche Richtung bekommt, steht im HTML über data-auftritt. */
.js [data-auftritt="links"]:not(.ist-sichtbar){transform:translateX(-36px)}
.js [data-auftritt="rechts"]:not(.ist-sichtbar){transform:translateX(36px)}
.js [data-auftritt="ruhig"]:not(.ist-sichtbar){transform:none;opacity:0}

/* ============================================================
   3 · Bildaufbau
   ============================================================
   Das Bild öffnet sich über clip-path, während es innen von 1.08 auf
   1.00 zurückskaliert. Beides zusammen wirkt, als zöge die Kamera auf,
   statt dass ein Rechteck erscheint.

   Drei Richtungen im Wechsel, gesetzt über data-oeffnen. */
.js [data-oeffnen]{overflow:hidden}
.js [data-oeffnen] img,
.js [data-oeffnen] picture{
  transform:scale(1.08);
  transition:transform var(--dur-l) var(--ease-inout);
  will-change:transform;
}
.js [data-oeffnen].ist-sichtbar img,
.js [data-oeffnen].ist-sichtbar picture{transform:scale(1)}
.js [data-oeffnen].ist-fertig img,
.js [data-oeffnen].ist-fertig picture{will-change:auto}

.js [data-oeffnen]:not(.ist-sichtbar){clip-path:inset(0 0 100% 0)}
.js [data-oeffnen="links"]:not(.ist-sichtbar){clip-path:inset(0 100% 0 0)}
.js [data-oeffnen="mitte"]:not(.ist-sichtbar){clip-path:inset(0 50% 0 50%)}
.js [data-oeffnen]{
  clip-path:inset(0 0 0 0);
  transition:clip-path var(--dur-l) var(--ease-inout),
             transform var(--dur-l) var(--ease-inout);
}

/* ---------- Freisteller schweben ein ---------- */
.js [data-schweben]:not(.ist-sichtbar){
  opacity:0;transform:translateY(60px) rotate(-1.2deg);
}
.js [data-schweben]{
  opacity:1;transform:translateY(0) rotate(0);
  transition:opacity var(--dur-l) var(--ease-out),
             transform var(--dur-l) var(--ease-out),
             filter var(--dur-l) var(--ease-out);
}
/* Der Schatten wächst mit, statt von Anfang an dazusein. */
.js .curryflaeche__bild--frei:not(.ist-sichtbar) img{
  filter:drop-shadow(0 4px 8px rgba(20,47,78,.12));
}

/* ---------- Zitatkarten ---------- */
.js .stimme:not(.ist-sichtbar),
.js .stimmen__klein > blockquote:not(.ist-sichtbar){
  opacity:0;transform:translateY(28px) rotate(.8deg);
}
.js .stimme,.js .stimmen__klein > blockquote{
  opacity:1;transform:translateY(0) rotate(0);
  transition:opacity var(--dur-m) var(--ease-out),
             transform var(--dur-m) var(--ease-out);
  transition-delay:calc(var(--i,0) * var(--stagger));
}

/* ---------- Zahlen ---------- */
.js [data-zaehlt]{font-variant-numeric:tabular-nums}

/* ============================================================
   4 · Breaker: sehr langsamer Zoom über die Sektion
   ============================================================
   Bevorzugt über animation-timeline: view(), weil der Browser das ohne
   Scroll-Handler rechnet. Wo es das nicht gibt (Safari bis 26,
   Firefox ohne Flag), bleibt das Bild schlicht stehen — ein Zoom ist
   Zierde, kein Inhalt. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    @keyframes breaker-zoom{from{transform:scale(1)}to{transform:scale(1.06)}}
    .js .appetitband__bild img{
      animation:breaker-zoom linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
  }
}

/* ============================================================
   5 · Ausnahmen
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  .js [data-worte] .wort,
  .js [data-worte]:not(.ist-da) .wort{transform:none;transition:none}
  .js [data-worte] em::after{transform:scaleX(1);transition:none}
  .js [data-ueber],.js [data-ueber].ist-da{opacity:1;letter-spacing:var(--ueber-sperrung,.14em);transition:none}
  .js [data-reveal],.js [data-zeig],
  .js [data-reveal]:not(.ist-sichtbar),.js [data-zeig]:not(.ist-sichtbar),
  .js [data-auftritt]:not(.ist-sichtbar){opacity:1;transform:none;transition:none}
  .js [data-oeffnen],.js [data-oeffnen]:not(.ist-sichtbar){clip-path:none;transition:none}
  .js [data-oeffnen] img,.js [data-oeffnen] picture{transform:none;transition:none}
  .js [data-schweben],.js [data-schweben]:not(.ist-sichtbar){
    opacity:1;transform:none;transition:none;
  }
  .js .stimme,.js .stimme:not(.ist-sichtbar),
  .js .stimmen__klein > blockquote,
  .js .stimmen__klein > blockquote:not(.ist-sichtbar){
    opacity:1;transform:none;transition:none;
  }
}

/* Ohne JavaScript greift keine der Regeln oben — sie hängen alle an .js.
   Diese Zeile ist trotzdem da, falls eine Maske im Quelltext landet. */
html:not(.js) .wort-maske{overflow:visible}
html:not(.js) .wort{transform:none}


/* ============================================================
   6 · Typewriter im Hero
   ============================================================
   Der vollständige Text steht im HTML. motion.js legt je Zeichen ein
   <span class="zeichen"> darum und blendet sie nacheinander ein — die
   Zeichen sind von Anfang an da und belegen ihren Platz, nur unsichtbar.
   Deshalb springt nichts: Kein Zeichen wird nachträglich eingefügt.

   Der Strich daneben ist ein eigenes Element, das mitwandert. Am Ende
   blinkt er dreimal und verschwindet; keine Endlosschleife.

   Ohne JavaScript greift keine dieser Regeln (sie hängen an .js), und
   bei reduzierter Bewegung steht alles sofort — siehe ganz unten. */
.js [data-tippt] .zeichen{opacity:0}
.js [data-tippt] .zeichen.ist-da{opacity:1}
/* Kein transition auf opacity: Bei 40 ms Takt überlagern sich sonst
   dreißig Übergänge und der Satz wirkt verwaschen statt getippt. */

/* Der Strich steht AUSSERHALB des Textflusses.
   Als inline-block mit negativem Außenabstand verschob er die Zeile
   trotzdem: Beim Entfernen am Ende sprang der Satz, gemessen zweimal
   0,023 Layoutverschiebung. Absolut positioniert am rechten Rand des
   gerade getippten Zeichens kostet er null. */
.js [data-tippt] .zeichen{position:relative}
.js .tippstrich{
  position:absolute;left:100%;top:.14em;
  width:.055em;height:.78em;
  background:var(--orange);border-radius:.02em;
  pointer-events:none;
}
@keyframes tippstrich-blinken{0%,49%{opacity:1}50%,100%{opacity:0}}
.js .tippstrich.blinkt{animation:tippstrich-blinken .42s steps(1) 3}
.js .tippstrich.weg{display:none}

/* Nachlauf: Subline, Knöpfe und Vertrauenszeile kommen nach dem Tippen. */
.js [data-nachlauf]:not(.ist-da){opacity:0;transform:translateY(14px)}
.js [data-nachlauf]{
  opacity:1;transform:none;
  transition:opacity var(--dur-m) var(--ease-out),
             transform var(--dur-m) var(--ease-out);
}

@media (prefers-reduced-motion: reduce){
  .js [data-tippt] .zeichen,
  .js [data-tippt] .zeichen.ist-da{opacity:1}
  .js .tippstrich{display:none}
  .js [data-nachlauf],.js [data-nachlauf]:not(.ist-da){
    opacity:1;transform:none;transition:none;
  }
}
