/* leXpro Motion — Scroll-Choreografie, überwiegend nativ.
 *
 * Native CSS-Scroll-Animationen laufen auf dem Compositor-Thread und kosten 0 KB JavaScript.
 * Sie sind aber noch NICHT Baseline: Firefox unterstützt sie bis 154 nicht. Deshalb steht der
 * versteckte Ausgangszustand IMMER innerhalb des @supports-Blocks — ohne Unterstützung wird
 * der Inhalt statisch und vollständig ausgeliefert, niemals leer.
 *
 * GSAP kommt nur noch für Zeilen-Splitting zum Einsatz und wird nachgeladen (siehe JS).
 */

/* ==========================================================================
   Reveal
   ========================================================================== */

@keyframes lx-reveal { to { opacity: 1; translate: 0 0; } }
@keyframes lx-rise   { to { translate: 0 0; } }
@keyframes lx-word   { to { opacity: 1; } }

/* Muss außerhalb von @supports stehen: sonst stünden die Wörter ohne Scroll-Timeline in der
   Zeile, aber ohne die Zeilenumbruch-Fähigkeit eines inline-block. */
[data-lx-words-ready] .lx-word { display: inline-block; }

/* Drei Zustände statt zwei:
     data-motion fehlt     -> Systemeinstellung entscheidet
     data-motion="reduced" -> Nutzer hat Bewegung hier abgeschaltet
     data-motion="full"    -> Nutzer will Bewegung, auch wenn das System "reduzieren" meldet

   Deshalb werden die Animationen NICHT in `@media not (prefers-reduced-motion)` definiert:
   sonst existierten sie unter einer System-Reduzierung gar nicht und kein Schalter der Welt
   könnte sie einblenden. Stattdessen unten gezielt zurückgenommen. */
@supports (animation-timeline: view()) {
	html:not([data-motion="reduced"]) {

		/* Der Bereich endete früher bei `cover 30%`. Rechnerisch war die Bewegung damit fertig,
		   während das Element noch im unteren Drittel stand — bis der Blick dort ankam, war
		   längst alles vorbei und die Seite wirkte statisch. `cover 45%` schiebt das Ende in
		   die Bildmitte, und 2,5rem Weg statt 1,5 macht sie überhaupt erst wahrnehmbar. */
		[data-lx-reveal] {
			opacity: 0;
			translate: 0 2.5rem;
			animation: lx-reveal linear both;
			animation-timeline: view();
			animation-range: entry 15% cover 45%;
		}

		/* Variante ohne Opacity: ein Element mit opacity:0 ist KEIN LCP-Kandidat, ein
		   eingeblendeter Titel verschiebt die LCP-Messung um 800–1500 ms. Über dem Fold
		   und auf jeder Headline, die das LCP-Element sein könnte, diese Variante nutzen. */
		[data-lx-reveal="rise"] {
			opacity: 1;
			animation-name: lx-rise;
		}

		[data-lx-stagger] > * {
			opacity: 0;
			translate: 0 1.5rem;
			animation: lx-reveal linear both;
			animation-timeline: view();
		}

		/* Nativ gibt es keine zeitliche Verzögerung — gestaffelt wird über die Scroll-Distanz. */
		[data-lx-stagger] > *:nth-child(1) { animation-range: entry 15% cover 42%; }
		[data-lx-stagger] > *:nth-child(2) { animation-range: entry 18% cover 46%; }
		[data-lx-stagger] > *:nth-child(3) { animation-range: entry 21% cover 50%; }
		[data-lx-stagger] > *:nth-child(4) { animation-range: entry 24% cover 54%; }
		[data-lx-stagger] > *:nth-child(5) { animation-range: entry 27% cover 58%; }
		[data-lx-stagger] > *:nth-child(6) { animation-range: entry 30% cover 62%; }
		[data-lx-stagger] > *:nth-child(n+7) { animation-range: entry 33% cover 66%; }

		/* ---------- Wort-Reveal ----------
		   Die Signatur: die Überschrift füllt sich beim Scrollen Wort für Wort auf. Dasselbe
		   Prinzip, das um.marketing mit GSAP SplitType und ScrollTrigger fährt — hier ohne
		   Bibliothek, weil das Splitten 30 Zeilen JS sind und die Staffelung über die
		   Scroll-Distanz läuft statt über eine Zeitachse.

		   Der Startwert ist 0.14 und nicht 0: ein Element mit `opacity: 0` scheidet als
		   LCP-Kandidat aus, und die Überschrift soll auch ungescrollt lesbar bleiben. */
		[data-lx-words-ready] .lx-word {
			opacity: 0.14;
			animation: lx-word linear both;
			animation-timeline: view();
			/* Der Versatz kommt aus --i, das beim Splitten pro Wort gesetzt wird. Eine
			   nth-child-Staffel würde hier nicht reichen: die Wörter liegen je nach
			   Überschrift unterschiedlich tief verschachtelt. */
			animation-range: cover calc(16% + var(--i, 0) * 2.4%) cover calc(46% + var(--i, 0) * 2.4%);
		}

		/* ---------- Parallax ---------- */

		[data-lx-parallax] {
			animation: lx-parallax linear both;
			animation-timeline: view();
			animation-range: cover 0% cover 100%;
		}

		/* ---------- SVG-Datenkurve zeichnen ----------
		   Der Pfad braucht pathLength="1"; dann ist die Länge unabhängig von der Geometrie
		   und es braucht kein JavaScript zum Ausmessen. */
		[data-lx-draw] path {
			stroke-dasharray: 1;
			stroke-dashoffset: 1;
			animation: lx-draw linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 55%;
		}

		/* ---------- Gold-Sheen ----------
		   Scroll-gekoppelt statt `infinite`: die Bewegung ist damit nutzerinitiiert, die
		   WCAG-2.2.2-Pflicht auf eine Pause-Steuerung entfällt, und im Leerlauf entsteht
		   keine Compositor-Last. */
		/* `overflow: clip` ist hier nicht kosmetisch, sondern verhindert horizontales Scrollen:
		   der Schimmer liegt mit `inset: -10% -40%` absichtlich außerhalb seines Elements, damit
		   er von außen hereinfahren kann. Ungeclippt schiebt er das Dokument auf — auf einem
		   375-px-Display waren das 126 px pro Seite, und die ganze Seite ließ sich seitwärts
		   wegschieben. `clip` statt `hidden`, weil es keinen Scrollcontainer erzeugt. */
		[data-lx-sheen] { position: relative; isolation: isolate; overflow: clip; }

		[data-lx-sheen]::after {
			content: "";
			position: absolute;
			inset: -10% -40%;
			pointer-events: none;
			background: linear-gradient(105deg, transparent 42%,
				rgb(255 255 255 / 0.5) 50%, transparent 58%);
			mix-blend-mode: screen;
			animation: lx-sheen linear both;
			animation-timeline: view();
			animation-range: entry 20% cover 65%;
		}
	}
}

@keyframes lx-parallax {
	from { translate: 0 calc(var(--lx-parallax, 6%) * -1); }
	to   { translate: 0 var(--lx-parallax, 6%); }
}

/* Eine Linie, die beim Scrollen gezogen wird. Ohne Scroll-Timeline liegt sie fertig da —
   deshalb steht der Ausgangszustand scaleX(0) ausschließlich in der Animation, nie im
   Grundzustand. */
[data-lx-line] { position: relative; }

[data-lx-line]::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 1px;
	background-image: var(--grad-gold);
	transform-origin: 0 50%;
	pointer-events: none;
}

@supports (animation-timeline: view()) {
	html:not([data-motion="reduced"]) [data-lx-line]::before {
		animation: lx-line linear both;
		animation-timeline: view();
		animation-range: entry 30% cover 48%;
	}
}

@keyframes lx-line  { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* --- Ablauf: gestapelt braucht jede Phase ihre eigene Linie -----------------
   `data-lx-line` sitzt am Raster und zeichnet **eine** Linie an dessen Kopf. Nebeneinander
   deckt die alle vier Phasen ab; sobald sie untereinander stehen, liegt sie nur noch über der
   ersten — die übrigen behielten ihre ruhende Haarlinie und es sah aus, als liefe der Balken
   nur bei Woche 01.

   Unter 1024 px übernimmt deshalb jede Phase selbst. Die ruhende Haarlinie ist der
   `border-top` der Phase; darüber legt sich die gezogene Goldlinie. */
@media (max-width: 1023px) {
	.lx-process[data-lx-line]::before { display: none; }

	@supports (animation-timeline: view()) {
		html:not([data-motion="reduced"]) .lx-process .lx-phase::after {
			content: "";
			position: absolute;
			top: -1px;
			left: 0;
			right: 0;
			height: 1px;
			background-image: var(--grad-gold);
			transform-origin: 0 50%;
			pointer-events: none;
			animation: lx-line linear both;
			animation-timeline: view();
			animation-range: entry 30% cover 48%;
		}
	}
}
@keyframes lx-draw  { to { stroke-dashoffset: 0; } }
@keyframes lx-sheen { from { transform: translate3d(-60%, 0, 0); } to { transform: translate3d(60%, 0, 0); } }

/* ==========================================================================
   Mitlaufende Überschriften-Spalte
   Reines position:sticky. GSAP-Pinning wäre teurer, würde mit Elementors Layout kämpfen —
   und laut NN/g verlieren Nutzer genau bei gepinntem Lesetext die Orientierung. Hier bleibt
   nur die Überschrift stehen, der Text scrollt normal daran vorbei.
   ========================================================================== */

@media (min-width: 1024px) {
	[data-lx-pin-heading] {
		position: sticky;
		top: var(--lx-pin-top, 7rem);
		align-self: start;
	}
}

/* Ein Sticky-Header darf das Fokus-Ziel nicht verdecken (WCAG 2.4.11). */
html { scroll-padding-top: var(--lx-header-h, 5rem); }

/* ==========================================================================
   Marquee — CSS-Keyframes auf transform, also compositor-getrieben.
   Der Track enthält zwei identische Hälften, deshalb ist -50 % ein nahtloser Umlauf.
   ========================================================================== */

/* ==========================================================================
   Rotierende Leistungszeile im Hero

   Vier Wörter im selben Rasterfeld, nacheinander eingeblendet. Das Stapeln steht hier und
   nicht im Marken-CSS: ohne laufende Animation lägen vier Wörter übereinander und wären
   unlesbar. Der Grundzustand dort ist deshalb eine Liste, gestapelt wird nur, wo auch
   animiert wird.
   ========================================================================== */

/* Vier Wörter, 2,5 s Abstand, 10 s Zyklus. Damit nie eine leere Zeile entsteht, muss das
   Ausblenden des einen Wortes exakt über dem Einblenden des nächsten liegen:

     Wort n blendet aus von 25 % bis 30 % seines Zyklus
     Wort n+1 startet bei 25 % (= seine 0 %) und blendet ein bis zu seinen 5 %

   Beide Rampen decken damit dasselbe halbe Sekundenfenster ab und die Summe der Deckkraft
   bleibt 1. Deshalb auch `linear` und keine Easing-Kurve: mit `--ease-out` war die Ausblendung
   nach 60 % des Intervalls schon bei 2 % Deckkraft angekommen, und genau dort riss das Bild —
   gemessen viermal pro Umlauf eine fast leere Zeile. Eine Überblendung braucht keine Kurve. */
@keyframes lx-rotate {
	0%         { opacity: 0; transform: translateY(0.28em); }
	5%, 25%    { opacity: 1; transform: none; }
	30%, 100%  { opacity: 0; transform: translateY(-0.28em); }
}

html:not([data-motion="reduced"]) .lx-rotator__slot { display: grid; }

html:not([data-motion="reduced"]) .lx-rotator__word {
	grid-area: 1 / 1;
	opacity: 0;
	animation: lx-rotate 10s linear infinite;
}

/* Negativer Versatz für das erste Wort: ohne ihn stünde es beim Seitenaufruf bei 0 % seines
   Zyklus, also unsichtbar, und der Hero begänne mit einer halben Sekunde leerer Zeile.
   Mit -0.5s startet die Seite mittendrin — bei voller Deckkraft. Die übrigen Verzögerungen
   sind um denselben Betrag mitgezogen, der Abstand von 2,5 s bleibt. */
html:not([data-motion="reduced"]) .lx-rotator__word:nth-child(1) { animation-delay: -0.5s; }

/* Sicherung: liefe die Animation aus irgendeinem Grund nicht, stünden vier Wörter auf
   `opacity: 0` und die Zeile wäre leer. Das erste bleibt deshalb auch ohne Animation sichtbar —
   die laufende Animation überschreibt es ohnehin in jedem Frame. */
html:not([data-motion="reduced"]) .lx-rotator__word:first-child { opacity: 1; }

html:not([data-motion="reduced"]) .lx-rotator__word:nth-child(2) { animation-delay: 2s; }
html:not([data-motion="reduced"]) .lx-rotator__word:nth-child(3) { animation-delay: 4.5s; }
html:not([data-motion="reduced"]) .lx-rotator__word:nth-child(4) { animation-delay: 7s; }

/* WCAG 2.2.2 — die eigentliche Pause-Steuerung ist der Schalter im Footer; Hover und
   Tastaturfokus halten sie zusätzlich an. */
.lx-rotator:hover .lx-rotator__word,
.lx-rotator:focus-within .lx-rotator__word { animation-play-state: paused; }

@keyframes lx-marquee { to { transform: translate3d(-50%, 0, 0); } }

.lx-marquee__track { animation: lx-marquee var(--lx-marquee-dur, 48s) linear infinite; }
.lx-marquee__track--reverse { animation-direction: reverse; }

/* Pause bei Hover, bei Tastaturfokus und über den Button (WCAG 2.2.2). */
.lx-marquee:hover .lx-marquee__track,
.lx-marquee:focus-within .lx-marquee__track,
.lx-marquee[data-paused] .lx-marquee__track { animation-play-state: paused; }

/* Außerhalb des Blickfelds: keine Animation, keine Compositor-Last. */
.lx-marquee[data-offscreen] .lx-marquee__track { animation-play-state: paused; }

/* ==========================================================================
   Zeilen-Reveal (GSAP SplitText, nachgeladen)
   Bis GSAP geladen ist, bleibt der Text normal sichtbar — erst wenn das JS gesplittet hat,
   setzt es data-lx-split-ready und übernimmt die Darstellung. Kein leerer Screen.
   ========================================================================== */

/* Nicht an [data-lx-split] koppeln: Elementor setzt das Attribut auf den Widget-Wrapper,
   gesplittet wird die Ueberschrift darin. Das Ready-Flag bleibt am Wrapper. */
[data-lx-split-ready] .lx-line-mask {
	display: block;
	overflow: clip;
	/* Versalien mit Umlaut ragen bis 0,967em über die Grundlinie und würden von der Maske
	   beschnitten. Sentence-Case-Headlines brauchen das nicht, aber der Puffer kostet nichts
	   und rettet den Fall, dass doch einmal Versalien gesetzt werden. Siehe BUILD-NOTES. */
	padding-top: 0.18em;
	margin-top: -0.18em;
}

/* ==========================================================================
   Arbeiten — die Startseite spielt sich im Rahmen selbst ab.

   Der Weg, den das Bild zurücklegen muss, ist (Bildhöhe − Rahmenhöhe). Als Prozentwert
   eines translateY, der sich auf die eigene Höhe des Bildes bezieht:

       Weg = −(1 − Rahmenhöhe / Bildhöhe) · 100 %
           = −(1 − Bildbreite / (Seitenverhältnis · Bildhöhe)) · 100 %

   Die Pixelmaße kommen als --lx-iw/--lx-ih aus dem Markup, das Seitenverhältnis aus dem
   CSS — so stimmt der Weg auch, wenn der Rahmen im Mobil-Layout flacher wird.

   @property ist hier kein Schmuck: sollte die Division einmal ungültig sein, fiele ohne
   Registrierung die gesamte transform-Deklaration aus. Mit Registrierung greift stattdessen
   der initial-value und das Bild wandert immer noch plausibel weit.
   ========================================================================== */

@property --lx-travel {
	syntax: "<percentage>";
	inherits: true;
	initial-value: -72%;
}

@keyframes lx-shot { to { transform: translate3d(0, var(--lx-travel), 0); } }

@keyframes lx-work-active {
	0%       { opacity: 0.42; }
	18%, 82% { opacity: 1; }
	100%     { opacity: 0.42; }
}

@keyframes lx-work-bar {
	0%       { transform: scaleY(0); }
	18%, 82% { transform: scaleY(1); }
	100%     { transform: scaleY(0); }
}

@supports (animation-timeline: view()) {
	html:not([data-motion="reduced"]) {

		/* Die Rahmen melden ihre Timeline an, die Sektion macht sie für das Register sichtbar.
		   Beides paarweise über nth-child statt über Inline-Styles: die Reihenfolge im Markup
		   ist ohnehin die Reihenfolge im Register. */
		.lx-work { timeline-scope: --lxw1, --lxw2, --lxw3, --lxw4; }

		.lx-work__item:nth-child(1) { view-timeline-name: --lxw1; }
		.lx-work__item:nth-child(2) { view-timeline-name: --lxw2; }
		.lx-work__item:nth-child(3) { view-timeline-name: --lxw3; }
		.lx-work__item:nth-child(4) { view-timeline-name: --lxw4; }

		.lx-work__index li:nth-child(1) { --lx-tl: --lxw1; }
		.lx-work__index li:nth-child(2) { --lx-tl: --lxw2; }
		.lx-work__index li:nth-child(3) { --lx-tl: --lxw3; }
		.lx-work__index li:nth-child(4) { --lx-tl: --lxw4; }

		.lx-work__item {
			--lx-travel: calc(-100% * (1 - var(--lx-iw) / (var(--lx-ar, 1.6) * var(--lx-ih))));
		}

		.lx-work__item .lx-frame__view img {
			animation: lx-shot linear both;
			animation-timeline: view();
			/* `cover` statt `entry 100% exit 0%`: letzteres beschreibt die Zeit, in der der
			   Rahmen vollständig sichtbar ist — und die schrumpft auf null, sobald der Rahmen
			   so hoch wird wie das Fenster. Auf kurzen Viewports stand die Seite deshalb still.
			   `cover` misst den gesamten Durchlauf durchs Bild und kann nicht kollabieren; die
			   Ränder bleiben ruhig, damit die Bewegung nicht am Bildrand beginnt. */
			animation-range: cover 12% cover 88%;
		}

		/* Das Register folgt den Rahmen über benannte Timelines: jeder Rahmen meldet seine
		   eigene an (`view-timeline-name` im Markup), `timeline-scope` an der Sektion macht
		   sie für die linke Spalte sichtbar. Ohne timeline-scope bleibt das Register
		   einfach durchgehend hell — lesbar, nur ohne Hervorhebung. */
		.lx-work__index li {
			animation: lx-work-active linear both;
			animation-timeline: var(--lx-tl);
			animation-range: cover 0% cover 100%;
		}

		.lx-work__index li::before {
			animation: lx-work-bar linear both;
			animation-timeline: var(--lx-tl);
			animation-range: cover 0% cover 100%;
		}
	}
}

@media (max-width: 767px) {
	.lx-work__stack { --lx-ar: 1.3333; }
}

/* ==========================================================================
   Bewegung zurücknehmen — reduzieren, nicht entfernen.
   Alles Räumliche geht weg; Inhalte bleiben in jedem Fall vollständig sichtbar.

   Zwei Auslöser, ein Regelsatz:
     1. System meldet „reduzieren" UND der Nutzer hat nicht ausdrücklich widersprochen
     2. Nutzer hat auf dieser Seite abgeschaltet
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
	html:not([data-motion="full"]) [data-lx-reveal],
	html:not([data-motion="full"]) [data-lx-stagger] > * {
		opacity: 1 !important;
		translate: none !important;
		animation: none !important;
	}
	html:not([data-motion="full"]) .lx-marquee__track,
	html:not([data-motion="full"]) [data-lx-parallax],
	html:not([data-motion="full"]) [data-lx-draw] path { animation: none; translate: none; }
	html:not([data-motion="full"]) [data-lx-line]::before { animation: none; transform: none; }
	html:not([data-motion="full"]) .lx-process .lx-phase::after { animation: none; transform: none; }
	html:not([data-motion="full"]) [data-lx-words-ready] .lx-word { animation: none; opacity: 1; }
	html:not([data-motion="full"]) .lx-rotator__slot { display: flex; }
	html:not([data-motion="full"]) .lx-rotator__word { animation: none; opacity: 1; transform: none; }
	html:not([data-motion="full"]) [data-lx-sheen]::after { animation: none; opacity: 0.2; }
	html:not([data-motion="full"]) [data-lx-pin-heading] { position: static; }

	/* Das Bild bleibt stehen — dann muss der Rahmen aber die ganze Seite zeigen dürfen,
	   sonst bliebe von vier Arbeiten nur viermal ein Kopfbereich übrig. Dafür muss das Bild
	   zurück in den Fluss: absolut positioniert würde ein Rahmen ohne Seitenverhältnis
	   in sich zusammenfallen. */
	html:not([data-motion="full"]) .lx-frame__view img { position: static; animation: none; transform: none; }
	html:not([data-motion="full"]) .lx-frame__view { aspect-ratio: auto; }
	html:not([data-motion="full"]) .lx-work__index li,
	html:not([data-motion="full"]) .lx-work__index li::before { animation: none; opacity: 1; transform: none; }
}

html[data-motion="reduced"] [data-lx-reveal],
html[data-motion="reduced"] [data-lx-stagger] > * {
	opacity: 1 !important;
	translate: none !important;
	animation: none !important;
}
html[data-motion="reduced"] .lx-marquee__track,
html[data-motion="reduced"] [data-lx-parallax],
html[data-motion="reduced"] [data-lx-draw] path { animation: none; translate: none; }
html[data-motion="reduced"] [data-lx-line]::before { animation: none; transform: none; }
html[data-motion="reduced"] .lx-process .lx-phase::after { animation: none; transform: none; }
html[data-motion="reduced"] [data-lx-words-ready] .lx-word { animation: none; opacity: 1; }
html[data-motion="reduced"] .lx-rotator__slot { display: flex; }
html[data-motion="reduced"] .lx-rotator__word { animation: none; opacity: 1; transform: none; }
html[data-motion="reduced"] [data-lx-sheen]::after { animation: none; opacity: 0.2; }
html[data-motion="reduced"] [data-lx-pin-heading] { position: static; }

html[data-motion="reduced"] .lx-frame__view img { position: static; animation: none; transform: none; }
html[data-motion="reduced"] .lx-frame__view { aspect-ratio: auto; }
html[data-motion="reduced"] .lx-work__index li,
html[data-motion="reduced"] .lx-work__index li::before { animation: none; opacity: 1; transform: none; }
