/* Startseite „Software-Reise“ (ADR-008) – Ergänzungen zu /styles/main.css (Tokens von dort)
   und /styles/header-footer.css. Ohne JS bzw. bei reduzierter Bewegung: statische, lesbare
   Fassung. Bewegte Zustände nur unter .chapter-live, .thread-live und im gepinnten Hero-Film. */

:root {
  --stage-radius: 14px;
  --node: 11px;
  --raster: color-mix(in srgb, var(--line) 40%, transparent);
  /* Bestätigter App-Button (Tap) und der daraus geformte Kasten (Zoom an Stopp 02). */
  --app-confirmed: color-mix(in srgb, var(--lime) 14%, #17181e);
  --packet-bg: #181d24;
  /* Film-Overlays an Stopp 04: Panel im Stil der Film-Panels, Paket als heller Lichtimpuls. */
  --film-panel: #0e0f13;
  --film-shadow: rgba(0, 0, 0, 0.55);
  --packet-core: #f7fdec;
  --packet-trail: rgba(242, 251, 224, 0.95);
  --packet-size: 11px;
  --packet-tail: 56px;
  /* Kapitel-Bühne: Bildschirm im Standby und Kurven der Wechsel (wie die Film-Stopptexte). */
  --screen-off: #050507;
  --screen-standby: 0.8;
  --ease-in: cubic-bezier(0.32, 0, 0.67, 0); /* power2.in */
  /* --ease-out (power2.out) steht in /styles/header-footer.css – die Leiste braucht ihn auch auf Unterseiten. */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* power2.inOut */
  /* Mini-Geschichten der Bühne: eine Bewegungssprache für alle Leistungen */
  --ease-settle: cubic-bezier(0.22, 1, 0.36, 1); /* Ankommen: Aufdecken, Ebene bewegt sich (= stage-row-in) */
  --phase-out: 0.2s; /* alter Zustand blendet aus – var(--ease-in) */
  --phase-in: 0.3s; /* neuer Zustand blendet ein – var(--ease-out), 0,1 s nach dem alten */
  --phase-reveal: 0.5s; /* Karte deckt sich von oben auf – var(--ease-settle) */
  --phase-reveal-lg: 0.7s; /* große Ebene deckt sich auf (SnapField-Bericht) */
  --phase-move: 0.6s; /* Gerät oder Ebene bewegt sich – var(--ease-settle) */
  --phase-step: 0.15s; /* Staffel Ursache → Wirkung */
  --phase-back: 0.25s; /* jeder Rückweg: var(--ease-in), ohne Staffel */
}
/* Staffel und Herkunft einer Phasenbewegung gelten nur für das Element, an dem sie stehen
   (ein Gerät mit Verzögerung gibt sie nicht an seinen Inhalt weiter). */
@property --phase-delay { syntax: "<time>"; inherits: false; initial-value: 0s; }
@property --phase-from { syntax: "*"; inherits: false; }

/* Der Film füllt den ganzen Bildschirm; die Leiste schwebt darüber. */
.field-hero { min-height: 100vh; min-height: 100svh; }
/* Ist das Intro höher als der Platz unter der Leiste (Zoom, niedrige Fenster), wächst der Hero und
   das Intro beginnt oben: Platz für die feste Leiste lassen (fits() in field-hero.js rechnet damit). */
.field-hero .hero-intro > .wrap { padding-top: calc(var(--header-h) + var(--s-4)); }
/* Seitengrund nach dem Film: feines Punktraster wie auf der Bühne (.chapter-stage), statisch.
   Hintergrund des Faden-Bereichs selbst – kein eigenes Element, keine Ebene; er liegt über
   der Tonebene der Produkte (.tone-layer, z-index -1) und unter allem Inhalt. Oben blendet
   eine Schicht im Seitengrund das Raster weich ein (kein Rand am Übergang aus dem Film).
   Ohne JS bzw. statisch deckt .section-alt die Fläche – dort dasselbe Raster auf dem Grundton. */
:root { --raster-page: color-mix(in srgb, var(--line) 60%, transparent); }
.thread-scope { background: linear-gradient(var(--bg), transparent) 0 0 / 100% 60svh no-repeat, radial-gradient(circle, var(--raster-page) 1px, transparent 1.5px) 0 0 / 28px 28px; }
.section-alt { background-image: radial-gradient(circle, var(--raster-page) 1px, transparent 1.5px); background-size: 28px 28px; }

/* ---------- Ladeanimation „Faden öffnet den Film“ ----------
   Nur mit JS und erlaubter Bewegung: html.loader-on setzt das Inline-Skript im head. Gecko und
   Wortmarke bauen sich auf, die Lime-Linie zeigt die Ladelage der ersten Film-Bilder; dann
   öffnet sie sich zum Fenster auf den Film (page-loader.js: Loch im Schleier per clip-path,
   Kanten per transform). Ohne JS kein Overlay. Sicherheitsnetz, falls ein Skript scheitert:
   loader-failsafe blendet es nach spätestens 3,6 s aus (Verzögerung = LATE_MS in page-loader.js);
   übernimmt das Modul, schaltet .is-driven das Netz ab. Über der Leiste (außer mit Fokus darin),
   unter dem Skip-Link (z-index 50). Nicht im Kontrastmodus, nicht bei
   Textfragment-Links und nicht bei sehr langsamer Verbindung (Inline-Skript im head). */
.page-loader { position: fixed; inset: 0; z-index: 45; display: none; place-content: center; justify-items: center; }
.loader-on:not(.reduced) .page-loader { display: grid; animation: loader-failsafe 0.3s linear 3.3s both, loader-gone 0s 3.6s both; }
.loader-on:not(.reduced) .page-loader.is-driven { animation: none; }
.page-loader.is-opening { pointer-events: none; }
/* Tastatur: Liegt der Fokus in der Leiste, solange die Ladeanimation steht, hebt sie sich über den
   Schleier (sonst war der Fokus bis ≈0,9 s verdeckt). Bei geringer Höhe ist die Leiste relativ
   positioniert (/styles/header-footer.css) – der z-index wirkt auch dort. */
.loader-on .site-header:focus-within { z-index: 46; }
@keyframes loader-failsafe { to { opacity: 0; } }
@keyframes loader-gone { to { visibility: hidden; } } /* getrennt: am Ende der Blende stand visibility noch auf visible (Rundung) und fing Klicks ab */
.loader-veil { position: absolute; inset: 0; background: var(--bg); }
.loader-brand { position: relative; display: flex; align-items: center; gap: 1rem; transition: opacity 0.25s var(--ease-in), transform 0.25s var(--ease-in); }
/* Gecko und Wortmarke erscheinen erst, wenn beide Bilder da sind (is-ready setzt das Inline-Skript
   im head): Bei langsamem Netz lief der Aufbau sonst ins Leere, und die Bilder sprangen einzeln herein. */
.loader-logo { width: auto; height: 4.25rem; opacity: 0; }
.is-ready .loader-logo { opacity: 1; animation: loader-logo-in 0.7s var(--ease-out) 0.25s both; }
/* Linie: Spur, Füllung (Ladelage) und Spitze als Lichtpunkt wie die Pakete auf dem Faden. */
.loader-line { position: relative; width: min(26rem, 72vw); height: 2px; margin-top: 2.5rem; background: var(--line-2); animation: loader-line-in 0.3s linear 0.15s both; }
.loader-fill, .loader-edge { position: absolute; background: var(--lime); box-shadow: 0 0 10px color-mix(in srgb, var(--lime) 55%, transparent); }
.loader-fill { inset: 0; transform: scaleX(0); transform-origin: 0 50%; transition: transform 0.5s var(--ease-out); }
.loader-edge { transform-origin: 0 50%; } /* Kanten des Fensters, Lage und Breite setzt page-loader.js */
/* Beim Öffnen werden die Kanten zum weichen Lichtschein (Kern gedämpft, breiter Schein), den
   page-loader.js verglimmen lässt – statt harter Lime-Linien (Rückmeldung Sven, 2026-09-25). */
.is-opening .loader-edge { background: color-mix(in srgb, var(--lime) 70%, transparent); box-shadow: 0 0 14px 3px color-mix(in srgb, var(--lime) 45%, transparent), 0 0 44px 12px color-mix(in srgb, var(--lime) 18%, transparent); }
/* Weiche Ränder des Fensters: Verläufe vom Schleier (--bg) ins Bild, Lage und Dicke setzt
   page-loader.js per transform. Außerhalb des Fensters liegen sie unsichtbar auf dem Schleier. */
.loader-feather { position: absolute; left: 0; top: 0; pointer-events: none; }
.loader-feather.is-top, .loader-feather.is-bottom { width: 100%; height: var(--feather, 7rem); }
.loader-feather.is-left, .loader-feather.is-right { width: var(--feather, 7rem); height: 100%; }
.loader-feather.is-top { transform-origin: 50% 0; background: linear-gradient(to bottom, var(--bg), color-mix(in srgb, var(--bg) 45%, transparent) 40%, transparent); }
.loader-feather.is-bottom { transform-origin: 50% 100%; background: linear-gradient(to top, var(--bg), color-mix(in srgb, var(--bg) 45%, transparent) 40%, transparent); }
.loader-feather.is-left { transform-origin: 0 50%; background: linear-gradient(to right, var(--bg), color-mix(in srgb, var(--bg) 45%, transparent) 40%, transparent); }
.loader-feather.is-right { transform-origin: 100% 50%; background: linear-gradient(to left, var(--bg), color-mix(in srgb, var(--bg) 45%, transparent) 40%, transparent); }
.loader-tip { position: absolute; inset: 0; transform: translateX(-100%); transition: transform 0.5s var(--ease-out), opacity 0.2s linear; }
.loader-tip::after { content: ""; position: absolute; right: -4px; top: 50%; width: 8px; height: 8px; margin-top: -4px; border-radius: 50%; background: var(--packet-core); box-shadow: 0 0 10px 2px color-mix(in srgb, var(--lime) 85%, transparent); }
/* Atmender Ring wie an der Spitze des Fadens (tip-breathe): zeigt „lädt“, auch solange die Skripte noch fehlen. */
.loader-tip::before { content: ""; position: absolute; right: -8px; top: 50%; width: 16px; height: 16px; margin-top: -8px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--lime) 60%, transparent); animation: tip-breathe 2.4s ease-in-out infinite; }
.is-opening .loader-brand { opacity: 0; transform: translateY(-0.75rem); }
.is-opening .loader-tip { opacity: 0; }
.is-opening .loader-line { opacity: 0; transition: opacity 0.1s linear 0.2s; } /* liegt dann unter der oberen Kante */
@keyframes loader-logo-in { from { clip-path: inset(0 100% 0 0); transform: translateX(-0.6rem); } to { clip-path: inset(0 0 0 0); } }
@keyframes loader-line-in { from { opacity: 0; } }
/* Schmal: Gruppe höher, damit das Fenster auf das Filmbild aufgeht und nicht auf den Introtext. */
@media (max-width: 47.99rem) {
  .page-loader { padding-bottom: 32svh; }
  .loader-brand { gap: 0.75rem; }
  .loader-logo { height: 2.75rem; }
  .loader-line { margin-top: 2.25rem; }
}

/* ---------- Ladeanimation „Gecko klettert ins Logo“ (Standard seit 2026-09-25) ----------
   Das Inline-Skript im head setzt html.loader-gecko zusammen mit loader-on. Das Logo baut sich auf (Endbild = gecko-96; Maße in Logo-Einheiten, 400 =
   Durchmesser): Der graue Ring zeichnet sich in zwei Hälften von unten nach oben, die weiße Fläche
   geht auf, dann klettert der Lime-Gecko (Umriss aus dem Logo, media/loader/gecko-climb.svg, unten
   verlängert) gerade von unten herein, beschnitten von der Fläche, und kommt mit erhobener
   Hand zum Stehen. Danach Wortmarke, Linie und Öffnen wie oben (page-loader.js wartet auf alle
   Auftritte). Der Ring liegt über dem Gecko und deckt dessen Schnittkante. */
:root { --logo-ring: #666666; --logo-disc: #ffffff; }
/* Größer als das Gecko-Bild der Standardfassung (4,5rem), damit das Klettern auch über RDP (≈30 Hz)
   klar zu sehen ist (Rückmeldung Sven: „Sollte der Gecko nicht animiert sein?“). */
.loader-emblem { display: none; position: relative; width: 5.75rem; height: 5.75rem; }
.loader-gecko .loader-emblem { display: block; }
.emblem-disc, .emblem-clip, .emblem-ring { position: absolute; inset: 0; width: 100%; height: 100%; }
.emblem-disc { fill: var(--logo-disc); opacity: 0; }
.emblem-clip { clip-path: circle(44% at 50% 50%); } /* r 176: knapp unter der Innenkante des Rings (173,3) */
/* Umriss-Datei 1:1 zum Logo (viewBox −40 0 480 520, Platz für die Verlängerung); Drehpunkt unten am Körper. */
.emblem-gecko { position: absolute; left: -10%; top: 0; width: 120%; height: 130%; max-width: none; opacity: 0; transform-origin: 39.58% 76.92%; }
/* Ring: Jede Seite zeigt nur ihre Hälfte (overflow), darin dreht die Ringhälfte der Gegenseite
   herein – transform statt stroke-dashoffset, das im Seitenstart am Hauptthread hing (Ring stand
   bis ≈0,8 s fast still, gemessen). Am Ende übernimmt der ganze Ring: keine Naht in der Mitte. */
.ring-half { position: absolute; top: 0; width: 50%; height: 100%; overflow: hidden; }
.ring-left { left: 0; }
.ring-right { left: 50%; }
.ring-arc { position: absolute; top: 0; width: 200%; height: 100%; max-width: none; fill: var(--logo-ring); }
.arc-left { left: 0; } /* rechte Hälfte, im Uhrzeigersinn: unten → links → oben */
.arc-right { left: -100%; } /* linke Hälfte, gegen den Uhrzeigersinn: unten → rechts → oben */
.emblem-ring { fill: none; stroke: var(--logo-ring); stroke-width: 26.7; opacity: 0; }
/* Takt: Ring 0,7 s → Fläche ab 0,2 s → Gecko gleitet 1,1 s ab 0,55 s → Wortmarke ab 1,6 s. */
.is-ready .arc-left { transform: rotate(180deg); animation: emblem-arc-in 0.7s var(--ease-in-out) both; }
.is-ready .arc-right { transform: rotate(-180deg); animation: emblem-arc-in 0.7s var(--ease-in-out) both; }
.is-ready .ring-half { opacity: 0; animation: emblem-hold 0.7s steps(1, end) both; }
.is-ready .emblem-ring { opacity: 1; animation: emblem-wait 0.7s steps(1, end) both; }
.is-ready .emblem-disc { opacity: 1; animation: emblem-disc-in 0.45s var(--ease-settle) 0.2s both; }
.is-ready .emblem-gecko { opacity: 1; animation: emblem-gecko-in 1.1s var(--ease-settle) 0.55s both; }
.loader-gecko .loader-logo { animation-duration: 0.6s; animation-delay: 1.6s; } /* Wortmarke, sobald der Gecko steht */
@keyframes emblem-arc-in { from { transform: none; } }
@keyframes emblem-hold { from { opacity: 1; } }
@keyframes emblem-wait { from { opacity: 0; } }
@keyframes emblem-disc-in { from { transform: scale(0); } }
/* Hereingleiten: gerade von unten nach oben, ohne Drehung und ohne Schlenker (Sven, 2026-09-25).
   Verschiebung in % der Umriss-Box. */
@keyframes emblem-gecko-in { from { transform: translateY(58%); } }
@media (max-width: 47.99rem) { .loader-emblem { width: 3.75rem; height: 3.75rem; } }

/* Gemeinsame Kleinteile */
/* KI-Kennzeichnung: klares Badge statt Raute (Rückmeldung „schwarze Karos“). */
.ki-badge { display: inline-flex; align-items: center; padding: 0.1rem 0.45rem; border-radius: 999px; background: var(--lime); color: var(--on-lime); font-family: var(--font-mono); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.1em; line-height: 1.4; }

/* ---------- Hero-Film „Feld → App → Software → Auswertung“ ----------
   Aufbau und Grundregeln aus /styles/main.css (.hero, .hero-seq, .journey-layer …);
   hier Bildquelle und die Overlays an den Stopps (App an Stopp 02, Export an Stopp 04). */
/* Seitengrund statt Schwarz: Nach dem Ausblenden des Films bleibt nur die Linie – ohne Tonsprung zur Seite. */
.field-hero { background: var(--bg); }
/* Standbild: Das scharfe Poster ist Hintergrund des Canvas (ohne JS und bis zum ersten
   Zeichnen); mit JS zeichnet das Canvas dasselbe Poster als Frame 0 deckend darüber. Am
   Canvas statt am Hero, damit es beim Ausblenden des Films mit ausblendet. Der Zoom an
   Stopp 02 skaliert das Canvas um einen Fixpunkt, den field-hero.js in CSS-Pixel umrechnet. */
.field-hero .hero-seq { background: var(--bg) image-set(url("/assets/home/journey-v4/poster-m.avif") type("image/avif"), url("/assets/home/journey-v4/poster-m.webp") type("image/webp")) center / cover no-repeat; }
@media (min-width: 48rem) { .field-hero .hero-seq { background-image: image-set(url("/assets/home/journey-v4/poster-2560.avif") type("image/avif"), url("/assets/home/journey-v4/poster-2560.webp") type("image/webp")); } }
/* LED-Ebene des Rack-Finales (hero-fx.js des Live-Stands) */
.hero-leds { z-index: 1; pointer-events: none; display: none; }
.js:not(.reduced) .hero-leds { display: block; }
/* Leere Overlay-Ebenen (LEDs vor dem Rack, Übergabelinie vor Stopp 05) nicht mitzeichnen. */
.hero-media.is-idle { visibility: hidden; }
/* Poster nur bis zum ersten gezeichneten Bild (field-hero.js), danach deckt der Canvas. */
.field-hero .hero-seq.is-painted { background-image: none; }
/* App-Overlay an Stopp 02: der Briefing-Screen als HTML exakt über dem Display des Films.
   Position/Größe setzt field-hero.js; die Zeilen bauen sich beim Erreichen gestaffelt auf. */
/* Ohne Ring-Schatten: Der Film zeigt den Displayrand selbst, und der Schatten kostete beim
   Zoom eine zusätzliche Shader-Kompilierung (gemessen). */
.field-hero .film-app { position: absolute; left: 0; top: 0; z-index: 3; display: none; width: 0; height: 0; border-radius: 11.5% / 5.3%; opacity: 0; pointer-events: none; transform-origin: 0 0; }
.js:not(.reduced) .field-hero .film-app { display: flex; }
.film-app.is-live :is(.app-card, .app-steps li, .app-list li) { animation: row-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both; }
.film-app.is-live .app-list li:nth-child(2), .film-app.is-live .app-steps li:nth-child(2) { animation-delay: 0.08s; }
.film-app.is-live .app-list li:nth-child(3), .film-app.is-live .app-steps li:nth-child(3) { animation-delay: 0.16s; }
/* Tap auf „Gelesen – bestätigen“ kurz vor der Fahrt in die Software: Fingerpunkt setzt auf,
   der Button gibt nach, Welle, dann „Bestätigt“. field-hero.js hält die tap-*-Keyframes an
   und stellt sie nach dem Scrollfortschritt – der Tap läuft vor- und rückwärts mit. */
.film-app .app-btn { position: relative; display: grid; }
.film-app .app-btn > :is(.app-btn-label, .app-btn-done) { grid-area: 1 / 1; }
.film-app .app-btn-done { display: inline-flex; align-items: center; justify-content: center; gap: 0.35em; opacity: 0; }
/* Häkchen in „Bestätigt“ und „Exportiert“ */
.app-check { fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.film-app .app-check { width: 1.15em; height: 1.15em; }
.film-app .app-tap { position: absolute; left: 60%; top: 55%; width: 3.2em; height: 3.2em; margin: -1.6em 0 0 -1.6em; pointer-events: none; }
.film-app .app-tap::before, .film-app .app-tap::after { content: ""; position: absolute; inset: 0; border-radius: 50%; opacity: 0; }
.film-app .app-tap::before { background: rgba(255, 255, 255, 0.5); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35); }
.film-app .app-tap::after { border: 2px solid rgba(255, 255, 255, 0.75); }
.film-app.is-tapped .app-tap::before { animation: tap-touch 0.9s cubic-bezier(0.3, 0, 0.2, 1) both; }
.film-app.is-tapped .app-tap::after { animation: tap-ripple 0.9s ease-out both; }
.film-app.is-tapped .app-btn { animation: tap-press 0.9s ease both; }
.film-app.is-tapped .app-btn-label { animation: tap-label-out 0.9s both; }
.film-app.is-tapped .app-btn-done { animation: tap-label-in 0.9s both; }
@keyframes tap-touch { 0% { opacity: 0; transform: scale(1.3); } 18% { opacity: 1; transform: scale(1); } 45% { opacity: 1; transform: scale(0.9); } 75%, 100% { opacity: 0; transform: scale(1.05); } }
@keyframes tap-ripple { 0%, 30% { opacity: 0; transform: scale(0.6); } 40% { opacity: 0.8; } 100% { opacity: 0; transform: scale(2.8); } }
@keyframes tap-press {
  0%, 15% { transform: none; background: var(--lime); color: var(--on-lime); }
  38% { transform: scale(0.96); background: color-mix(in srgb, var(--lime) 80%, #000); color: var(--on-lime); }
  60%, 100% { transform: none; background: var(--app-confirmed); color: var(--lime); box-shadow: inset 0 0 0 1.5px var(--lime); }
}
@keyframes tap-label-out { 0%, 45% { opacity: 1; } 55%, 100% { opacity: 0; } }
@keyframes tap-label-in { 0%, 50% { opacity: 0; transform: translateY(0.3em); } 70%, 100% { opacity: 1; transform: none; } }
/* Inhalt des Film-Pakets auf der Datenautobahn; Lage und Schrift als Anteil der Kastenbreite. */
.packet-info { position: absolute; left: 20%; top: 19%; display: grid; gap: 0.25em; line-height: 1.2; white-space: nowrap; }
.packet-title { font-weight: 700; color: var(--text); }
.packet-meta { font-size: 0.77em; font-weight: 400; color: var(--text-2); }
/* Film-Paket: Text im Lime-Kasten des Films (Clip 3 bis in die Auswertung), Lage je Frame
   aus data-packet-track (vermessen), Einblenden am ersten vermessenen Frame, Ausblenden vor dem Übergang in
   die Karte. Unter dem Textverlauf wie der Kasten im Film selbst. */
.field-hero .film-packet { position: absolute; left: 0; top: 0; z-index: 3; display: none; width: 0; height: 0; container-type: inline-size; opacity: 0; pointer-events: none; transform-origin: 0 0; }
.js:not(.reduced) .field-hero .film-packet { display: block; }
.film-packet .packet-info { font-size: 7.4cqw; font-family: var(--font-sans); }
/* Export an Stopp 04: kleines Element auf der Lime-Linie im Stil der Panels im Film. Lage,
   Breite und Einblendung setzt field-hero.js (data-export); der Fortschrittsbalken läuft mit
   dem Scrollen, is-done schaltet auf „Exportiert“. Maße in cqw der Elementbreite. Über dem
   Textverlauf der Stopps (z-index 4), sonst wirkt es abgedunkelt. */
.field-hero .film-export { position: absolute; left: 0; top: 0; z-index: 5; display: none; width: 0; container-type: inline-size; opacity: 0; pointer-events: none; transform-origin: 50% 50%; }
.js:not(.reduced) .field-hero .film-export { display: block; }
.film-export-card { display: grid; gap: 4.5cqw; padding: 7cqw 8cqw 8cqw; border: 1px solid var(--line-2); border-radius: 7cqw; background: var(--film-panel); box-shadow: 0 1.5cqw 10cqw var(--film-shadow); color: var(--text); font-family: var(--font-sans); line-height: 1.2; }
.film-export-head { display: flex; align-items: center; gap: 4cqw; font-size: 8.5cqw; font-weight: 700; }
.film-export-icon, .film-export-arrow { flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.film-export-icon { width: 1.3em; height: 1.3em; color: var(--lime); }
.film-export-arrow { width: 1.05em; height: 1.05em; }
.film-export-state { display: grid; font-size: 7.5cqw; color: var(--text-2); }
.film-export-run, .film-export-done { grid-area: 1 / 1; display: inline-flex; align-items: center; gap: 2.5cqw; transition: opacity 0.2s; }
.film-export .app-check { width: 1.1em; height: 1.1em; }
.film-export-done { opacity: 0; color: var(--lime); font-weight: 700; }
.film-export.is-done .film-export-run { opacity: 0; }
.film-export.is-done .film-export-done { opacity: 1; }
.film-export-bar { display: block; height: 2.4cqw; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--text) 12%, transparent); }
.film-export-fill { display: block; height: 100%; border-radius: inherit; background: var(--lime); transform: scaleX(0); transform-origin: 0 50%; }
/* Paket nach dem Export: heller Lichtimpuls mit Schweif, heller als die Lime-Linie des Films. */
.field-hero .film-drop { position: absolute; left: 0; top: 0; z-index: 5; display: none; width: var(--packet-size); height: var(--packet-size); translate: -50% -50%; border-radius: 50%; background: var(--packet-core); box-shadow: 0 0 10px 2px color-mix(in srgb, var(--lime) 85%, transparent); opacity: 0; pointer-events: none; }
.js:not(.reduced) .field-hero .film-drop { display: block; }
.film-drop::before { content: ""; position: absolute; left: 50%; bottom: 50%; width: 3px; height: var(--packet-tail); transform: translateX(-50%); border-radius: 3px; background: linear-gradient(0deg, var(--packet-trail), transparent); }

/* Intro (Rückmeldung Screen 1): klarer Tech-Look – Akzentwort in Lime statt Kasten
   (der Kasten ragte in die Zeile darüber), helle Schrift mit leichtem Schatten,
   Verlauf nur dort, wo Text steht; das Motiv darüber bleibt scharf. */
.field-hero .hl-text { color: var(--lime); }
/* Ausgewogene Umbrüche im Hero (keine einzelnen Wörter in der letzten Zeile). */
.field-hero :is(.display, .lede, .stop-title, .stop-desc) { text-wrap: balance; }
/* Film-Stopps: Fortschritt als fünf Segmente an der Nummer (statt losem Label darüber). */
.stop-bar { display: inline-flex; gap: 4px; margin-right: 0.8rem; vertical-align: middle; }
.stop-bar i { width: 18px; height: 2px; border-radius: 1px; background: color-mix(in srgb, var(--text) 25%, transparent); }
.stop-bar i.is-done { background: color-mix(in srgb, var(--lime) 45%, transparent); }
.stop-bar i.is-on { background: var(--lime); }
.field-hero .display { max-width: 18ch; font-size: clamp(2.2rem, 1rem + 3.6vw, 4.25rem); }
/* Themenzeile in der h1 (nennt das Seitenthema): klein und als eigene Zeile über dem Leitsatz.
   Laufweite und Zeilenhöhe eigens gesetzt – die em-Laufweite von .display erbt sonst als fester Wert. */
.field-hero .hero-theme { display: block; margin-bottom: var(--s-3); font-size: clamp(1.05rem, 0.95rem + 0.45vw, 1.3rem); line-height: 1.3; letter-spacing: -0.01em; text-shadow: 0 1px 14px rgba(0, 0, 0, 0.6); }
/* Kleine, niedrige Phones (z. B. iPhone SE mit Browserleisten): Der Hero-Text muss ganz in die Höhe
   passen, sonst baut field-hero.js die Film-Fahrt nicht (fits()). Überschrift und Abstände etwas knapper. */
@media (max-width: 30rem) and (max-height: 600px) {
  .field-hero .display { font-size: 1.8rem; }
  .field-hero .hero-theme { font-size: 0.8rem; margin-bottom: var(--s-2); }
  .field-hero .hero-intro .hero-actions { margin-top: var(--s-4); }
  .field-hero .hero-intro .lede { font-size: 0.95rem; }
}
.field-hero .hero-intro .kicker { max-width: none; }
.field-hero .hero-intro .kicker { color: var(--text); text-shadow: 0 1px 12px rgba(0, 0, 0, 0.65); }
.field-hero .hero-intro .lede { color: var(--text); text-shadow: 0 1px 14px rgba(0, 0, 0, 0.6); }
.field-hero .hero-scrim { background: linear-gradient(0deg, rgba(11, 11, 14, 0.94) 0%, rgba(11, 11, 14, 0.78) 34%, rgba(11, 11, 14, 0.3) 58%, transparent 74%); }
@media (min-width: 48rem) {
  .field-hero .hero-scrim { background: linear-gradient(0deg, rgba(11, 11, 14, 0.9) 0%, rgba(11, 11, 14, 0.62) 26%, rgba(11, 11, 14, 0.18) 46%, transparent 60%); }
}
.btn-ghost { background: transparent; color: var(--text); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--text) 55%, transparent); }
.btn-ghost::after { content: ""; width: 0.45rem; height: 0.45rem; margin-left: 0.75rem; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-2px) rotate(45deg); }
.btn-ghost:hover, .btn-ghost:focus-visible { background: transparent; color: var(--lime); box-shadow: inset 0 0 0 1.5px var(--lime); }
/* Unter 360 px bleibt „Zum Leistungsspektrum“ einzeilig: weniger Innenabstand statt Umbruch (sonst
   säße der zweizeilige Text am Rand der Pille, gemessen bei 320 und 340 px). */
@media (max-width: 22.49rem) { .field-hero .btn-ghost { padding-inline: var(--s-5); white-space: nowrap; } }
/* Scroll-Hinweis unten mittig in der Aussparung der Hero-Kante – dort beginnt später der Faden. */
.scroll-cue { display: none; position: absolute; left: 50%; bottom: 0.2rem; width: 1.5px; height: calc(var(--hero-edge-h) + 0.6rem); overflow: hidden; transform: translateX(-50%); background: color-mix(in srgb, var(--text) 20%, transparent); pointer-events: none; }
/* Bewegt per transform (Compositor): Eine Animation auf top löste in jedem Frame Layout und Paint aus. */
.scroll-cue i { position: absolute; left: 0; top: 0; width: 100%; height: 40%; background: var(--lime); box-shadow: 0 0 8px var(--lime); transform: translateY(-100%); animation: cue-run 2.2s cubic-bezier(0.65, 0, 0.35, 1) infinite; }
@keyframes cue-run { to { transform: translateY(250%); } }
/* Schmal steht der Button unten mittig – dort übernimmt er die Rolle des Hinweises. */
@media (min-width: 48rem) { .scroll-cue { display: block; } }
.reduced .scroll-cue, .no-js .scroll-cue { display: none; }
.motion-paused .scroll-cue i { animation-play-state: paused; }

/* ---------- Faden: Pakete, Knoten, Spitze ---------- */
/* overflow-anchor: none – sonst wählt Chrome ein Paket der Ebene als Scroll-Anker und landet
   beim Neuladen nach Mausrad-Scrollen am Kapitelanfang statt an der vorigen Stelle. */
.thread-fx { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; overflow: visible; overflow-anchor: none; }
.thread-scope .thread, .thread-live .thread-tip { overflow-anchor: none; }
.thread-packet { position: absolute; left: 0; top: 0; width: 28px; height: 2px; margin: -1px 0 0 -28px; transform-origin: 100% 50%; border-radius: 2px; background: linear-gradient(90deg, transparent, var(--lime)); opacity: 0; will-change: transform, opacity; }
.thread-packet::after { content: ""; position: absolute; right: -3px; top: -2px; width: 6px; height: 6px; border-radius: 50%; background: var(--lime-2); box-shadow: 0 0 8px 2px color-mix(in srgb, var(--lime) 55%, transparent); }
.thread-packet.is-bright::after { background: #f2ffd9; }

.thread-node { position: absolute; left: 0; top: 0; width: var(--node); height: var(--node); margin: calc(var(--node) / -2) 0 0 calc(var(--node) / -2); }
.thread-node-dot { position: absolute; inset: 0; border-radius: 50%; border: 1.5px solid var(--line-2); background: var(--bg); transition: background-color 0.3s ease, border-color 0.3s ease; }
/* Atmen des Knotens: fester Glanz, nur die Deckkraft läuft (ein animierter box-shadow ließ
   den Browser im Ruhezustand in jedem Frame neu zeichnen). */
.thread-node-dot::after { content: ""; position: absolute; inset: -1.5px; border-radius: inherit; box-shadow: 0 0 18px 5px color-mix(in srgb, var(--lime) 40%, transparent); opacity: 0; pointer-events: none; }
.thread-node-ring { position: absolute; inset: -1px; border-radius: 50%; border: 1.5px solid var(--lime); opacity: 0; }
.thread-node--small { --node: 7px; }
.thread-node.is-on .thread-node-dot { background: var(--lime); border-color: var(--lime); box-shadow: 0 0 12px 2px color-mix(in srgb, var(--lime) 45%, transparent); }
.thread-node.is-hit .thread-node-dot { animation: node-pop 420ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.thread-node.is-hit .thread-node-dot::after { animation: node-breathe 3.4s ease-in-out infinite; animation-delay: calc(420ms + var(--i, 0) * 300ms); }
.thread-node.is-hit .thread-node-ring { animation: ping 900ms cubic-bezier(0.22, 1, 0.36, 1); }

.thread-live .thread-tip { width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px; box-shadow: 0 0 14px 4px color-mix(in srgb, var(--lime) 55%, transparent); }
.thread-live .thread-tip::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--lime) 60%, transparent); animation: tip-breathe 2.4s ease-in-out infinite; }

@keyframes ping { 0% { opacity: 0.9; transform: scale(1); } 100% { opacity: 0; transform: scale(3.6); } }
@keyframes node-pop { 0% { transform: scale(0.6); } 55% { transform: scale(1.4); } 100% { transform: scale(1); } }
@keyframes node-breathe { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
@keyframes tip-breathe { 0%, 100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.7); opacity: 0; } }

/* ---------- Kapitel „Leistungsspektrum“ ---------- */
.chapter-steps .index-row { align-items: start; }
/* Inhaltsspalte darf schrumpfen (sonst drücken Schemata die Zeile über den Rand). */
.chapter-steps .index-row { grid-template-columns: auto minmax(0, 1fr); }
@media (min-width: 60rem) { .chapter-steps .index-row { grid-template-columns: 4rem 20rem minmax(0, 1fr); } }
.step-body { grid-column: 2; }
.step-media { grid-column: 2; margin-top: var(--s-5); }
@media (min-width: 60rem) {
  .step-body { grid-column: 3; }
  .step-media { grid-column: 2 / -1; }
}
.step-more { margin-top: var(--s-3); color: var(--text); }
/* Kontakt: dunkle Karte (OpenStreetMap-Daten, lokal gerendert: build-contact-map.mjs), Adresse darübergelegt.
   Schmal Karte über der Adresse; breit liegt die Adresskarte unten links, der Pin (rechts) bleibt frei.
   Der Quellenhinweis steht oben rechts, damit die Adresskarte ihn nicht berührt. */
.contact-place { position: relative; display: grid; gap: var(--s-4); }
.contact-map { position: relative; margin: 0; overflow: hidden; aspect-ratio: 3 / 2; border: 1px solid var(--line); border-radius: 14px; background: #0d0e12; }
.contact-map img { display: block; width: 100%; height: 100%; object-fit: cover; }
.contact-map-credit { position: absolute; top: var(--s-2); right: var(--s-2); max-width: none; padding: 0.15rem 0.45rem; border-radius: 4px; background: color-mix(in srgb, var(--bg) 82%, transparent); font-size: 0.66rem; color: var(--text-2); }
.contact-map-credit a { color: inherit; }
@media (min-width: 52rem) {
  .contact-place .contact-card { position: absolute; left: var(--s-4); bottom: var(--s-4); padding: var(--s-4) var(--s-5); border: 1px solid var(--line-2); border-radius: 12px; background: color-mix(in srgb, var(--bg) 90%, transparent); }
}
.step-phases { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); margin-top: var(--s-4); max-width: none; }
.step-phase { display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.12em; text-transform: uppercase; color: var(--text); }
.step-phase::before { content: ""; width: var(--status-dot); height: var(--status-dot); border-radius: 50%; background: var(--lime); }
/* Inhaltliche Stichpunkte je Leistung: normaler Lesetext mit kurzem Lime-Strich als Marker. */
.step-points { display: grid; gap: var(--s-2); margin-top: var(--s-4); color: var(--text-2); }
.step-points li { position: relative; padding-left: 1.1rem; }
.step-points li::before { content: ""; position: absolute; left: 0; top: 0.72em; width: 0.55rem; height: 2px; background: var(--lime); }
:is(.step-media, .product-media) figcaption { margin-top: var(--s-2); font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-2); }

/* App- und Web-Screens der Leistungen (Demo-Daten; Bausteine in screens.css) */
.step-media--screen .device-iphone { max-width: 15rem; }
.step-media--screen .device-browser { max-width: 40rem; }
.sf-comp { display: grid; gap: var(--s-5); justify-items: center; }
.sf-report picture, .sf-report img { display: block; width: 100%; height: auto; }
.sf-report { position: relative; }
/* Rahmen auf die eigene Breite bezogen (3,4 % des Handys, nicht des umgebenden Blocks) –
   sonst schneidet object-fit: cover das Upload-Bild seitlich an. */
.sf-comp .sf-phone { max-width: 13rem; padding: min(3.4%, calc(13rem * 0.034)); }
/* 01 Außendienst: Tagesplan im Browser und Tagesliste auf dem Smartphone (Endzustand).
   Schmal untereinander, breit nebeneinander. */
.dispatch { display: grid; gap: var(--s-5); justify-items: center; }
@media (min-width: 60rem) { .dispatch { grid-template-columns: minmax(0, 40rem) 15rem; justify-content: center; align-items: center; column-gap: var(--s-6); } }
.dispatch-brief .app-card-head { white-space: nowrap; }

/* Gepinntes Kapitel mit Bühne (nur breit und hoch genug, von chapter-scrolly.js gesetzt) */
/* Rand für die Leitung der Aktivierung: Paket und Schein an der Unterkante (Rückkehr an 05)
   und der Schweif am Start werden sonst vom Raster beschnitten. */
.chapter-live .chapter-grid { position: relative; display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.3fr); gap: var(--s-7); overflow: clip; overflow-clip-margin: 16px; }
.chapter-live .chapter-steps .index-row { grid-template-columns: 3rem minmax(0, 1fr); min-height: 90svh; padding-block: var(--s-6); align-content: start; }
.chapter-live .chapter-steps .chapter-step--long { min-height: 180svh; }
.chapter-live .index-title, .chapter-live .step-body { grid-column: 2; }
.chapter-live .chapter-steps .step-media { display: none; }
/* Sprung auf eine Leistung (Anker aus der Fußzeile): Titel unter der festen Leiste sichtbar. */
.chapter-steps .index-row[id] { scroll-margin-top: calc(var(--header-h) + var(--s-4)); }
/* Linke Spalte ist schmaler als die volle Satzbreite: „Leistungsspektrum“ liefe sonst an die Bühne. */
.chapter-live .section-head .section-title { font-size: clamp(1.9rem, 1rem + 2.2vw, 2.9rem); }
/* Lange Einzelwörter („Freiwilligenmanagement“) liefen zwischen 60 und 80rem aus der schmalen Spalte:
   Die Titel tragen weiche Trennstellen (&shy;, auch in den Fuß-Links – sonst wäre der Sprachwechsel
   nicht eindeutig); overflow-wrap fängt nur noch Wörter ohne Trennstelle ab. */
.chapter-live .index-title { overflow-wrap: break-word; }
.chapter-live .index-row:hover { background: none; }
/* Oben und unten je 6svh Luft unter der Leiste. */
.chapter-stage { position: sticky; top: calc(var(--header-h) + 6svh); height: calc(88svh - var(--header-h)); align-self: start; border: 1px solid var(--line-2); border-radius: var(--stage-radius); background-color: var(--bg-2); background-image: radial-gradient(circle, var(--raster) 1px, transparent 1.5px); background-size: 28px 28px; overflow: hidden; transition: border-color 0.6s ease; }
/* Verbunden: Solange ein Gerät eingeschaltet ist, bleibt die Bühnenkante leicht getönt. */
.chapter-stage.is-powered { border-color: color-mix(in srgb, var(--lime) 38%, var(--line-2)); }
.stage-count { position: absolute; left: 18px; top: 16px; z-index: 3; text-shadow: 0 0 8px var(--bg), 0 0 2px var(--bg); font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.14em; color: var(--lime); }
.stage-progress { position: absolute; left: 0; right: 0; top: 0; z-index: 3; height: 2px; background: var(--line); }
.stage-progress i { display: block; height: 100%; width: 100%; background: var(--lime); transform-origin: left center; transform: scaleX(0); transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1); }
/* Wechsel ohne Überblendung zweier Screens: Das alte Bild tritt kurz ab (raus -16 px,
   power2.in, 0,2 s), das neue kommt danach wie ein Film-Stopptext (rein 24 px, power2.out,
   ab 0,14 s). Als Übergänge statt Keyframes: Ein unterbrochener Wechsel läuft von seinem
   aktuellen Stand weiter, nichts springt auf volle Deckkraft oder startet neu. Jede Lage hat
   einen expliziten translate-Wert (nie none), sonst erzwingt der Wechsel ein Layout. */
.stage-item { position: absolute; inset: 0; container-type: size; display: grid; place-items: center; padding: 3.5rem 2rem 2.5rem; visibility: hidden; opacity: 0; translate: 0 24px; transition: opacity 0.2s var(--ease-in), translate 0.2s var(--ease-in), visibility 0s linear 0.2s; }
.chapter-stage[data-dir="up"] .stage-item:not(.is-active) { translate: 0 -24px; }
.stage-item.is-active { z-index: 2; visibility: visible; opacity: 1; translate: 0 0; transition: opacity 0.6s var(--ease-out) 0.14s, translate 0.6s var(--ease-out) 0.14s, visibility 0s; }
.stage-item.is-active:not(.is-swap) { transition: none; }
/* Nur das gerade abtretende Bild blendet aus; alle anderen verschwinden sofort (gleiches
   Gerät: das neue Bild deckt das alte genau ab; schnelles Überspringen: nichts stapelt sich). */
.stage-item:not(.is-active):not(.is-leaving) { transition: none; }
.stage-item.is-leaving { translate: 0 -16px; }
.chapter-stage[data-dir="up"] .stage-item.is-leaving { translate: 0 16px; }
.stage-item > figure { width: 100%; margin: 0; }
.stage-item figcaption { position: absolute; z-index: 3; left: 18px; bottom: 14px; margin: 0; color: var(--text); }
/* Schwacher Rahmenblitz, wenn ein Paket an der Spitze ankommt. */
.stage-flash { position: absolute; inset: 0; z-index: 4; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--lime); opacity: 0; pointer-events: none; transition: opacity 0.7s ease; }
.stage-flash.is-on { opacity: 0.5; transition-duration: 0.08s; }

/* Aktivierung (chapter-scrolly.js): Kommt die Linie an einer Nummer an, läuft ein Impuls als
   Datenleitung auf der Trennlinie der Leistung zur Bühne; der Rahmen zeichnet sich von dort
   beidseitig nach und klingt auf die getönte Kante ab. Die Abläufe laufen per Web Animations,
   hier stehen nur die Ruhezustände (unsichtbar). Paket wie im Film (--packet-*). */
.chapter-wire { position: absolute; left: 0; top: 0; z-index: 3; width: 0; height: 1.5px; margin-top: -0.75px; pointer-events: none; }
.chapter-wire-line { position: absolute; inset: 0; background: var(--lime); box-shadow: 0 0 6px color-mix(in srgb, var(--lime) 45%, transparent); transform: scaleX(0); transform-origin: left center; }
.chapter-wire-head { position: absolute; left: calc(var(--packet-size) / -2); top: 50%; width: var(--packet-size); height: var(--packet-size); margin-top: calc(var(--packet-size) / -2); border-radius: 50%; background: var(--packet-core); box-shadow: 0 0 10px 2px color-mix(in srgb, var(--lime) 85%, transparent); opacity: 0; }
.chapter-wire-head::before { content: ""; position: absolute; right: 50%; top: 50%; width: var(--packet-tail); height: 3px; margin-top: -1.5px; border-radius: 3px; background: linear-gradient(90deg, transparent, var(--packet-trail)); }
.stage-frame { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
.stage-frame path { fill: none; stroke: var(--lime); stroke-dasharray: 1 3; stroke-dashoffset: 1; }
.stage-frame-line { stroke-width: 1.5px; }
.stage-frame-glow { stroke-width: 7px; stroke-opacity: 0.16; }

/* Bühne: Gerät mittig. Bis die Leitung die Bühne erreicht, liegt ein Standby-Schleier auf
   dem Bildschirm; dann (is-live) bootet er: Eine Lichtkante läuft von oben nach unten und
   gibt den Bildschirm hinter sich frei, die Zeilen bauen sich auf. Ausschalten blendet den
   Schleier wieder ein. */
.stage-item .step-media--screen { display: grid; place-items: center; width: 100%; height: 100%; }
.stage-item .step-media--screen > .device-iphone { width: auto; height: 94%; max-width: none; }
/* Browser-Ansicht: Breite auch an der Bühnenhöhe begrenzen (Inhalt 16:10 plus Adressleiste 2,1rem),
   sonst lief sie bei niedrigen Fenstern (z. B. 1366×657) über die Bildunterschrift. cqh bezieht sich
   auf die Inhaltsbox von .stage-item (Größen-Container; die Bildunterschrift liegt im Polster unten). */
.stage-item .step-media--screen > .device-browser { width: min(100%, calc((100cqh - 2.25rem) * 1.6)); }
/* Schleier: .stage-screen setzt chapter-scrolly.js an die Bildschirme der Bühne. */
.stage-screen.device-browser { position: relative; }
.stage-screen::after { content: ""; position: absolute; inset: 0; z-index: 5; border-radius: inherit; background: var(--screen-off); opacity: var(--screen-standby); clip-path: inset(0 0 0 0); pointer-events: none; transition: opacity 0.18s var(--ease-in), clip-path 0.18s var(--ease-in); }
.stage-screen.device-browser::after { top: 2.1rem; border-radius: 0; }
/* Freigabe von oben (clip-path) im Takt der Lichtkante; die Deckkraft fällt erst danach,
   damit das Ausschalten wieder weich einblendet. Ausschalten mitten im Wisch schließt den
   Schleier von der aktuellen Kante aus (0,18 s), statt ihn schlagartig zu setzen. */
:is(.stage-item, .product-media).is-live .stage-screen::after { opacity: 0; clip-path: inset(100% 0 0 0); transition: clip-path 0.6s var(--ease-in-out), opacity 0s linear 0.6s; }
.stage-boot { position: absolute; left: 0; right: 0; top: 0; z-index: 6; height: 100%; opacity: 0; transform: translateY(-100%); background: linear-gradient(180deg, transparent calc(100% - 3.5rem), color-mix(in srgb, var(--lime) 12%, transparent) calc(100% - 2px), var(--lime) calc(100% - 2px)); pointer-events: none; }
.stage-screen.device-browser > .stage-boot { top: 2.1rem; height: calc(100% - 2.1rem); }
:is(.stage-item, .product-media).is-live .stage-boot { animation: stage-boot-move 0.6s var(--ease-in-out) both, stage-boot-fade 0.6s linear both; }
@keyframes stage-boot-move { from { transform: translateY(-100%); } to { transform: translateY(0); } }
@keyframes stage-boot-fade { 0%, 80% { opacity: 1; } 100% { opacity: 0; } }
/* Zeilenaufbau beim Einschalten; Ruhewert translateY(0) statt none (sonst Layout beim Start). */
.stage-item :is(.app-card, .app-rows li, .app-shifts li, .web-kpis div, .web-tr, .web-link) { transform: translateY(0); }
.stage-item.is-live :is(.app-card, .app-rows li, .app-shifts li, .web-kpis div, .web-tr, .web-link) { animation: stage-row-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; animation-delay: calc(0.12s + var(--n, 0) * 80ms); }
@keyframes stage-row-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
:is(.app-rows, .app-shifts, .web-table) > :nth-child(2) { --n: 1; }
:is(.app-rows, .app-shifts, .web-table) > :nth-child(3) { --n: 2; }
:is(.app-rows, .app-shifts, .web-table) > :nth-child(4) { --n: 3; }
:is(.app-rows, .app-shifts, .web-table) > :nth-child(5) { --n: 4; }
:is(.app-rows, .app-shifts, .web-table) > :nth-child(6) { --n: 5; }
.stage-item.is-live .app-scan-line { animation: scan 2.6s ease-in-out 0.6s infinite; }
@keyframes row-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
/* Scan-Linie per transform um ihre Ruhelage (top 48 % von 34cqw): ±30 % = ±10,2cqw –
   eine Animation auf top löste in jedem Frame Layout aus. */
@keyframes scan { 0%, 100% { transform: translateY(-10.2cqw); } 50% { transform: translateY(10.2cqw); } }

/* ---------- Mini-Geschichten auf der Bühne ----------
   chapter-scrolly.js setzt data-phase (a/b/c) und data-phase-dir (fwd/back) am .stage-item.
   a = Ausgangslage (Boot-Zustand, ohne eigene Bewegung) · b = etwas tritt hinzu · c = Ergebnis.
   Ruhe = Endzustand = Original-Figur; frühere Zustände nur bei erlaubter Bewegung. Übergänge
   eingeschaltet (is-live) wie beschrieben; sichtbar, aber nicht eingeschaltet (Standby, Ankunft
   von unten): jeder Wechsel 0,25 s ease-in (Rückwärtsregeln); verdeckte Items springen. Ein
   abtretendes Item hält seine Phase, bis es verdeckt ist (chapter-scrolly.js). Bausteine:
   .phase-reveal[data-at] Karte deckt sich von oben auf · .phase-enter[data-at] tritt hinzu
   (blendet ein, optional aus --phase-from) · [data-at] > .phase-was/.phase-now Zustand wechselt.
   Nie opacity/transform auf .app-card, .app-rows li, .app-shifts li, .web-kpis div, .web-tr,
   .web-link (stage-row-in hält beide fest) – dort nur .phase-reveal. */
.stage-item .phase-was { display: block; opacity: 0; translate: 0 -100%; }
.stage-item :is(.phase-now, .phase-enter) { opacity: 1; translate: 0 0; }
.stage-item .phase-reveal { clip-path: inset(0 0 0 0); }
[data-at] > :is(.phase-was, .phase-now) { --phase-delay: inherit; }
@media (prefers-reduced-motion: no-preference) {
  /* vorher: vor Phase b bzw. c */
  .stage-item[data-phase="a"] [data-at="b"] > .phase-was,
  .stage-item:is([data-phase="a"], [data-phase="b"]) [data-at="c"] > .phase-was { opacity: 1; translate: 0 0; }
  .stage-item[data-phase="a"] [data-at="b"] > .phase-now,
  .stage-item:is([data-phase="a"], [data-phase="b"]) [data-at="c"] > .phase-now { opacity: 0; translate: 0 100%; }
  .stage-item[data-phase="a"] .phase-enter[data-at="b"],
  .stage-item:is([data-phase="a"], [data-phase="b"]) .phase-enter[data-at="c"] { opacity: 0; translate: var(--phase-from, 0 0); }
  .stage-item[data-phase="a"] .phase-reveal[data-at="b"],
  .stage-item:is([data-phase="a"], [data-phase="b"]) .phase-reveal[data-at="c"] { clip-path: inset(0 0 100% 0); }
  /* Zustandswechsel wie ein Zählwerk: volle Höhe, Zelle beschnitten – nie beide Zustände
     übereinander (04 ohne eigene Zelle: die Zeile beschneidet). */
  .stage-item .phase-swap, .stage-item .app-shifts li[data-at] { overflow: clip; }
  /* vorwärts */
  .stage-item.is-live .phase-was { transition: opacity var(--phase-out) var(--ease-in) var(--phase-delay), translate var(--phase-out) var(--ease-in) var(--phase-delay); }
  .stage-item.is-live .phase-now { transition: opacity var(--phase-in) var(--ease-out) calc(var(--phase-delay) + 0.1s), translate var(--phase-in) var(--ease-out) calc(var(--phase-delay) + 0.1s); }
  .stage-item.is-live .phase-enter { transition: opacity var(--phase-in) var(--ease-out) var(--phase-delay), translate var(--phase-move) var(--ease-settle) var(--phase-delay); }
  .stage-item.is-live .phase-reveal { transition: clip-path var(--phase-reveal) var(--ease-settle) var(--phase-delay); }
  /* rückwärts: kurz, ease-in, ohne Staffel (Spezifität 0,5,0 über 0,3,0) */
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) :is(.phase-was, .phase-now, .phase-enter, .phase-reveal) { transition-duration: var(--phase-back); transition-delay: 0s; transition-timing-function: var(--ease-in); }
}

/* 01 auf der Bühne: Komposition wie SnapField (cqw/cqh), Richtung umgekehrt – vom Büro ins
   Feld. Ruhe = c: Browser oben rechts (80 %), Smartphone unten links (0,84). a Tagesplan groß ·
   b zuteilen, dann übergeben (Browser tritt zurück, Smartphone tritt hinzu) · c Briefing wird
   oben eingefügt (.phase-reveal, Liste rückt nach). Kein will-change auf dem Browser. */
.stage-item .dispatch { position: relative; display: block; width: 100%; height: 100%; container-type: size; }
.stage-item .dispatch > .dispatch-web { position: absolute; z-index: 1; right: 0; top: 6%; width: 80%; max-width: none; transform-origin: 100% 0; translate: 0 0; scale: 1; }
.stage-item .dispatch > .dispatch-phone { --phase-from: 0 4cqh; --phase-delay: 0.4s; position: absolute; z-index: 2; left: 0; bottom: 0; width: auto; height: min(94cqh, 100cqw); max-width: none; margin: 0; transform-origin: 0 100%; scale: 0.84; }
.stage-item .dispatch-status { --phase-delay: var(--phase-step); }
/* Kartenhöhe + Abstand im .app-body: 2 × 4,4cqw + 4cqw × 1,35 + 3,4cqw = 17,6cqw plus Rand
   2 × 1,5px. Bei Änderungen an .app-card oder .app-body mitziehen. */
.stage-item .dispatch-list { --brief-shift: calc(17.6cqw + 3px); translate: 0 0; }
@media (prefers-reduced-motion: no-preference) {
  .stage-item[data-phase="a"] .dispatch > .dispatch-web { translate: 0 10.3cqh; scale: 1.25; }
  .stage-item:is([data-phase="a"], [data-phase="b"]) .dispatch-list { translate: 0 calc(-1 * var(--brief-shift)); }
  .stage-item.is-live .dispatch > .dispatch-web { transition: translate var(--phase-move) var(--ease-settle) 0.3s, scale var(--phase-move) var(--ease-settle) 0.3s; }
  .stage-item.is-live .dispatch-list { transition: translate var(--phase-reveal) var(--ease-settle); }
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) :is(.dispatch > .dispatch-web, .dispatch-list) { transition-duration: var(--phase-back); transition-delay: 0s; transition-timing-function: var(--ease-in); }
}

/* 02 auf der Bühne: a Erfassung läuft (Ergebnisplatz leer) · b erfasst (Scanfeld im
   bestätigten Ton, Häkchen zeichnet sich, Ergebnis deckt sich auf) · c Überblick der Schicht
   (Wert wechselt, Scanfeld wieder bereit = Original). Häkchen und Füllung gibt es nur in b. */
.stage-item .accred-check { display: block; opacity: 0; clip-path: inset(0 0 0 0); }
/* isolation: dauerhafter Stapelkontext – der Deckkraftwechsel löst dann kein Layout aus. */
.stage-item .accred-scan::before { content: ""; position: absolute; inset: -1.5px; border-radius: inherit; background: var(--app-confirmed); box-shadow: inset 0 0 0 1.5px var(--lime); opacity: 0; isolation: isolate; }
.stage-item .accred-slot::before { content: ""; position: absolute; inset: 0; border-radius: 5cqw; box-shadow: inset 0 0 0 1.5px var(--line-2); opacity: 0; isolation: isolate; }
.stage-item .accred-card { --phase-delay: 0.3s; }
@media (prefers-reduced-motion: no-preference) {
  .stage-item[data-phase="a"] .accred-slot::before { opacity: 1; }
  .stage-item[data-phase="a"] .accred-check { clip-path: inset(0 100% 0 0); }
  .stage-item[data-phase="b"] .accred-scan .app-scan-line { opacity: 0; }
  .stage-item[data-phase="b"] .accred-scan::before, .stage-item[data-phase="b"] .accred-check { opacity: 1; }
  /* Ziel b: Linie aus, Feld bestätigt, Häkchen; Geisterplatz erst, wenn die Karte ihn deckt */
  .stage-item.is-live[data-phase="b"] .accred-scan .app-scan-line { transition: opacity var(--phase-out) var(--ease-in); }
  .stage-item.is-live[data-phase="b"] .accred-scan::before { transition: opacity var(--phase-in) var(--ease-out) 0.1s; }
  .stage-item.is-live[data-phase="b"] .accred-check { transition: opacity 0.15s linear 0.15s, clip-path 0.4s var(--ease-out) 0.15s; }
  .stage-item.is-live:is([data-phase="b"], [data-phase="c"]) .accred-slot::before { transition: opacity var(--phase-out) linear 0.8s; }
  /* Ziel c: erst der Wert, dann ist das Scanfeld wieder bereit */
  .stage-item.is-live[data-phase="c"] .accred-scan::before, .stage-item.is-live[data-phase="c"] .accred-check { transition: opacity var(--phase-out) var(--ease-in) 0.5s; }
  .stage-item.is-live[data-phase="c"] .accred-scan .app-scan-line { transition: opacity var(--phase-in) var(--ease-out) 0.6s; }
  /* rückwärts */
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) :is(.accred-check, .accred-scan .app-scan-line),
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) .accred-scan::before,
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) .accred-slot::before { transition-duration: var(--phase-back); transition-delay: 0s; transition-timing-function: var(--ease-in); }
}

/* SnapField (03) auf der Bühne: a Upload (iPhone groß) · b/c Bericht im Browser, iPhone
   klein; c Export Frame für Frame (Canvas). Ruhe = b/c; a nur bei erlaubter Bewegung. */
.stage-item .sf-comp { position: relative; display: block; width: 100%; height: 100%; container-type: size; }
.stage-item .sf-report { position: absolute; z-index: 1; right: 0; top: 8%; width: 84%; clip-path: inset(0 0 0 0); }
/* Handy wandert per transform (Compositor): Mitte von (50 %, 50 %) nach (14 %, 64 %), Höhe 94 % → 60 %. */
.stage-item .sf-phone { position: absolute; z-index: 2; left: 50%; top: 50%; width: auto; height: 94%; max-width: none; padding: calc(94cqh * 393 / 852 * 0.034); transform: translate(-50%, -50%) translate(-36cqw, 14cqh) scale(0.638); }
.sf-scrub { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: auto; opacity: 0; }
.stage-item[data-phase="c"] .sf-scrub.is-ready { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .stage-item[data-phase="a"] .sf-report { clip-path: inset(0 0 100% 0); }
  .stage-item[data-phase="a"] .sf-phone { transform: translate(-50%, -50%); }
  .stage-item.is-live .sf-report { transition: clip-path var(--phase-reveal-lg) var(--ease-settle); }
  .stage-item.is-live .sf-phone { transition: transform var(--phase-move) var(--ease-settle); }
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) :is(.sf-report, .sf-phone) { transition-duration: var(--phase-back); transition-timing-function: var(--ease-in); }
}

/* 04 auf der Bühne: a Planung (alle Schichten offen) · b Koordination (die ersten beiden
   werden nacheinander bestätigt – Staffel aus --n) · c Kommunikation (Nachricht deckt sich
   auf). li und Karte behalten opacity/transform für stage-row-in. */
.stage-item .app-shifts li[data-at] { --phase-delay: calc(var(--n, 0) * var(--phase-step)); }
@media (prefers-reduced-motion: no-preference) {
  .stage-item[data-phase="a"] .app-shifts li[data-at="b"]::before { opacity: 0; }
  .stage-item.is-live .app-shifts li[data-at]::before { transition: opacity var(--phase-in) var(--ease-out) calc(var(--n, 0) * var(--phase-step) + 0.1s); }
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) .app-shifts li[data-at]::before { transition-duration: var(--phase-back); transition-delay: 0s; transition-timing-function: var(--ease-in); }
}

/* 05 auf der Bühne: a Web-Applikation allein · b bestehende Standardsoftware tritt hinzu
   („ausstehend“) · c Integration (Leitung und Rahmen Lime, „verbunden“ = Original). */
.stage-item .web-link { --n: 4; }
.stage-item .web-node.is-ext { --phase-from: 2cqw 0; --phase-delay: var(--phase-step); }
.stage-item .web-node.is-ext::after { isolation: isolate; }
.stage-item .web-link-line > .phase-swap { --phase-delay: var(--phase-step); }
@media (prefers-reduced-motion: no-preference) {
  .stage-item:is([data-phase="a"], [data-phase="b"]) .web-node.is-ext::after { opacity: 0; }
  .stage-item.is-live .web-node.is-ext::after { transition: opacity var(--phase-in) var(--ease-out); }
  .chapter-stage .stage-item:is(.is-live[data-phase-dir="back"], .is-active:not(.is-live)[data-phase-dir]) .web-node.is-ext::after { transition-duration: var(--phase-back); transition-timing-function: var(--ease-in); }
}

/* Aktivierung in der Textspalte: Die Nummer der Leistung, an der die Linie steht, wird zum
   Chip im Stil des bestätigten App-Buttons (Film). Polster immer gesetzt, damit nichts springt. */
.thread-live .index-num { justify-self: start; align-self: start; margin-top: 0.2rem; padding: 0.1rem 0.45rem; border-radius: 999px; line-height: 1.4; transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease; }
.chapter-step.is-current .index-num { background: var(--app-confirmed); color: var(--lime); box-shadow: inset 0 0 0 1.5px var(--lime); }

/* Phasen-Punkte in der Textspalte leuchten je Unterzustand (Kapitel aktiv) */
/* Noch nicht erreichte Schritte als Lime-Ring statt grauer Punkt: kein Eindruck von „deaktiviert“. */
.chapter-armed .step-phase::before { background: transparent; box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--lime) 60%, transparent); transition: background-color 0.3s ease, box-shadow 0.3s ease; }
.chapter-armed .step-phase.is-on::before { background: var(--lime); box-shadow: 0 0 8px 2px color-mix(in srgb, var(--lime) 45%, transparent); }

/* ---------- KI im Prozess: Schiene ---------- */
.ki-rail-caption { margin-top: var(--s-6); font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-2); }
.ki-rail { position: relative; display: grid; gap: var(--s-4); margin-top: var(--s-5); padding-left: 1.6rem; }
.ki-rail::before { content: ""; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 1.5px; background: color-mix(in srgb, var(--lime) 55%, transparent); transform-origin: top center; }
.ki-step { position: relative; display: flex; align-items: center; gap: 0.5rem; font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text); }
.ki-step::before { content: ""; position: absolute; left: calc(-1.6rem + 1px); width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--lime); background: var(--lime); }
@media (min-width: 48rem) {
  .ki-rail { grid-template-columns: repeat(7, 1fr); gap: 0; padding-left: 0; padding-top: 1.8rem; }
  .ki-rail::before { left: 0; right: 0; top: 5px; bottom: auto; width: auto; height: 1.5px; transform-origin: left center; }
  .ki-step { flex-direction: column; align-items: flex-start; }
  .ki-step::before { left: 0; top: -1.8rem; }
}

/* Netz „KI im Prozess“ (Synthese). Layout hängt an html.js (Inline-Skript im head), nicht an
   einer Klasse aus ki-net.js: kein Layoutsprung beim Start des Moduls, keine veralteten Maße.
   Zustände (gedimmt/an) hängen an .ki-net-on (ki-net.js). Die Liste liefert Beschriftungen und
   Stationspunkte (::before), ein SVG davor die Linien. Maße als Tokens am <ol>.
   Schmal: Grundlinie --net-mx links (Nebenstränge links daneben), alle Beschriftungen in einer
   Flucht ab --net-pad, KI-Stationen rechts neben ihrer Beschriftung (--net-kx), ihr Punkt ist
   der Ausgang des neuronalen Netzes (unten in der Zeile).
   Breit: Grundlinie --net-m mit Beschriftungen darunter, KI-Stationen auf der Ebene --net-k
   mit Beschriftung darüber, unten Platz für die Rückkopplung. */
.ki-net-svg { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
.js .ki-rail { --net-pad: 2.75rem; --net-mx: 1.75rem; --net-kx: 12.5rem; padding: 1.5rem 0 2rem var(--net-pad); }
/* Keine gerade Schiene: Der Faden tritt am Planungspunkt ein (data-thread-entry, story-thread.js). */
.js .ki-rail::before { content: none; }
.js .ki-step { --dot-l: calc(var(--net-mx) - var(--net-pad) - 4.5px); }
.js .ki-step--ki { --dot-l: calc(var(--net-kx) - var(--net-pad) - 4.5px); --dot-t: calc(100% - 9px); min-height: 4.5rem; margin-block: 1.25rem; }
.js .ki-step::before { left: var(--dot-l); top: var(--dot-t, auto); }
@media (min-width: 48rem) {
  .js .ki-rail { --net-k: 2.1rem; --net-m: 6.6rem; --net-gap: 1.15rem; margin-top: var(--s-6); padding: calc(var(--net-m) + var(--net-gap)) 0 3.75rem 1.25rem; }
  .js .ki-step { --dot-l: 0px; --dot-t: calc(-1 * var(--net-gap) - 4.5px); }
  .js .ki-step.ki-step--ki { --dot-l: 0px; --dot-t: calc(var(--net-k) - 4.5px); min-height: 0; flex-direction: row; align-items: center; align-self: start; margin-block: calc(-1 * (var(--net-m) + var(--net-gap))) 0; }
}
/* Ab 60rem liegt die Rinne weit genug links: Beschriftungen wieder in der Textflucht. */
@media (min-width: 60rem) { .js .ki-rail { padding-left: 0; } }
/* Zustände (ki-net.js): Punkte gedimmt, bis die Front sie erreicht. */
.ki-net-on .ki-step::before { background: var(--bg); border-color: var(--line-2); transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; }
.ki-net-on .ki-step.is-lit::before { background: var(--lime); border-color: var(--lime); box-shadow: 0 0 10px 2px color-mix(in srgb, var(--lime) 45%, transparent); }
/* Linien: Spur gedimmt, gezeichnet in Lime; Nebenstränge zurückgenommen, Verbindungen der
   neuronalen Netze fein; Rückkopplung gestrichelt und leiser als der Ablauf, mit Richtungswinkel. */
.net-edge { fill: none; stroke-width: 1.5px; stroke-linecap: round; stroke-linejoin: round; }
.net-edge--lane, .net-edge--loop { stroke-width: 1.25px; }
.net-edge--nn { stroke-width: 1px; }
.net-base .net-edge { stroke: var(--line-2); }
.net-base .net-edge--nn { stroke: var(--line); }
.net-edge--loop { stroke-dasharray: 2 6; }
.net-lit .net-edge { stroke: var(--lime); }
.net-lit .net-edge--lane { stroke: color-mix(in srgb, var(--lime) 65%, transparent); }
.net-lit .net-edge--loop { stroke: color-mix(in srgb, var(--lime) 40%, transparent); }
.net-chevron { fill: none; stroke: var(--line-2); stroke-width: 1.25px; stroke-linecap: round; stroke-linejoin: round; transition: stroke 0.3s ease; }
.net-chevron.is-on { stroke: color-mix(in srgb, var(--lime) 55%, transparent); }
/* Neuronale Netze: Kanten je Schicht in einer Gruppe (eine Animation je Schicht). */
.net-layer { opacity: 0.5; }
/* Knoten: Netzknoten gefüllt, Nebenstränge als Ring (weitere Einsatzkräfte, unbeschriftet). */
.net-node { fill: var(--bg); stroke: var(--line-2); stroke-width: 1px; transition: fill 0.3s ease, stroke 0.3s ease; }
.net-node.is-on { fill: var(--lime); stroke: var(--lime); }
.net-node--ghost { stroke-width: 1.25px; }
.net-node--ghost.is-on { fill: var(--bg); stroke: color-mix(in srgb, var(--lime) 85%, transparent); }
.net-halo { fill: var(--lime); opacity: 0; }
.net-halos { opacity: 0; }
.net-halos .net-halo { opacity: 1; }

/* Angehalten bzw. reduzierte Bewegung: keine Endlos-Animationen */
.motion-paused .thread-node-dot, .motion-paused .thread-node-dot::after, .motion-paused .thread-tip::after, .motion-paused .app-scan-line { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .thread-node-dot, .thread-node-dot::after, .thread-node-ring, .thread-tip::after { animation: none; }
  .stage-item.is-live :is(.app-card, .app-rows li, .app-shifts li, .web-kpis div, .web-tr, .web-link, .app-scan-line) { animation: none; }
  .stage-item, .stage-item.is-active, .stage-progress i, .stage-flash, .stage-screen::after, .thread-live .index-num, .chapter-stage, :is(.stage-item, .product-media).is-live .stage-screen::after { transition: none; }
  .chapter-wire, .stage-frame, .stage-boot { display: none; }
}
