# Prüfprotokoll (Lovable-Übernahme, 02.11.2026)

Hinweis: Die Original-VALIDATION.md aus GitHub (Gradore/gradore@9d9a642) war nicht abrufbar (privates Repository). Dieses Protokoll enthält nur selbst ausgeführte Prüfungen.

## Tatsächlich geprüft (Headless Chromium, lokale Vorschau)
- Build: OK.
- HTTP 200 mit vollständigem HTML vom Server: `/`, `/analyse`; genau ein H1 auf `/`.
- HTTP 200: `/docs/SOURCES.md`, `/docs/COMPARATIVE_AUDIT.md`, `/docs/personas.json`, `/docs/VALIDATION.md`, `/styles.css`, `/app.js`, `/assets/*.webp`, `/assets/favicon.svg`.
- `/index.html` → 301 `/`, `/analyse.html` → 301 `/analyse`.
- Animation, gemessene Zwischenzustände: 0,2 s geschlossen; 1,5 s Rad dreht, Tür zu; 2,8 s Rad fast am Ende, Tür zu; 4,3 s und 5,6 s Tür öffnet sich; 7,0 s Endzustand „open“. Gesamtdauer ca. 6,1 s ab Sichtbarkeit.
- Replay („Erneut öffnen“) startet neu; „Bewegung aus“ setzt sofort Endzustand und deaktiviert Replay.
- Reduced Motion (emuliert): keine Animation, Tür geschlossen, „Bewegung reduziert“.
- Preiswechsel: 29 € / 39 € / 72 € / 119 € / 500 €, CTA-Text passt mit.
- Mobil 320/375/390/430: kein horizontaler Überlauf; Reihenfolge H1 → Tresor → Lead → CTA; Kontaktleiste fixiert.
- Keine JavaScript-Fehler.

## Nicht geprüft
- Echte iOS-Geräte (safe-area nur im Code), echte Screenreader, Lighthouse/Core Web Vitals, veröffentlichte Domain.

## Nachprüfung nach Ebenenkorrektur (02.11.2026)
- Fix: Text „IHR RAUM“ liegt jetzt in einer eigenen Ebene hinter der Tür.
- Gemessen bei 0,2 s / 1,5 s / 2,8 s Tür geschlossen (Rad dreht), Text nicht sichtbar (Screenshot 1,5 s).
- 4,3 s / 5,6 s Tür öffnet sich, Text erscheint erst hinter der Türkante (Screenshot 5,6 s); 7,0 s Endzustand „open“.
- Replay und „Bewegung aus“ erneut bestätigt.

## Scroll-Auslösung der Türanimation (02.11.2026, 20:30 UTC)
Änderung: `public/app.js` (Tresorlogik neu), `src/content/index.html` (nur `app.js?v=3` gegen veraltete Browser-Caches). Preise, Layout, SEO, Badge unverändert.

Verhalten: Seite lädt immer mit geschlossener Tür, kein Autoplay. Start nur nach echter Scrollpositionsänderung und wenn mindestens 40 % der Szene sichtbar sind (Anteil bezogen auf min(Szenenhöhe, Viewporthöhe), aus IntersectionObserver-Rechtecken, nicht `isIntersecting`). Unter 5 % sichtbar: laufende Sequenz abbrechen, Tür zurücksetzen, neu scharfstellen. Keine Sitzungs-/Besuchssperre. Scroll-Listener passiv, Prüfung per rAF gebündelt, keine Messung pro Frame ohne Scroll. Tab verborgen während Lauf: Abbruch und Reset. `pageshow` (BFCache): Reset, Observer neu verbunden. Resize/Orientierung: einmalige Neumessung. Statuszeile unter der Szene: „Zum Öffnen scrollen“ / „Tresor öffnet sich“ / „Tresor geöffnet“ / „Bewegung reduziert“.

Regressionstest: `tests/e2e/vault_scroll_check.py` (Headless Chromium, lokale Vorschau, ohne Replay für die Hauptabläufe). Ergebnis: 109 Prüfungen bestanden, 0 fehlgeschlagen, keine JavaScript-Fehler. Gemessen werden die berechneten Transformationen von Rad (rotate) und Tür (rotateY).
- Frischer Aufruf, 8 s gewartet, ohne Scroll: Rad 0°, Tür 0°, geschlossen (1280×800, 390×844, 320×568).
- Scroll → Animationsstart des Rads 8–49 ms nach dem Scroll (geschlossene Phase < 200 ms).
- Zwischenzustände ab Scrollstart (Beispiel 320×568): 1,4 s Rad 74,5°, Tür 0°; 2,6 s Rad 99,6°, Tür 0°; 4,2 s Rad 100°, Tür −52,5°; 5,0 s Tür −65,9°; 6,4 s Tür −68°, Endzustand „open“. Werte auf allen Breiten gleich (±1°).
- Wegscrollen: sofort Rad 0°, Tür 0°. Zurückscrollen: beginnt geschlossen (Rad < 5°, Tür 0°) und spielt komplett erneut.
- Schneller Richtungswechsel (5 Sprünge à 120 ms, dann 7 s außerhalb): geschlossen, keine laufende Klasse, kein verspätetes Öffnen; danach erneuter Eintritt spielt normal.
- Tab verborgen (emuliertes `visibilitychange`) während Lauf: Abbruch und Reset; zurück ohne Scroll 7 s: kein Autoplay; Scroll spielt.
- Zurück-Navigation von /analyse: geschlossen, kein Autoplay, Scroll spielt. Chromium headless hat die Seite dabei neu geladen (`back_forward`), nicht aus dem BFCache. BFCache-Wiederherstellung deshalb zusätzlich mit synthetischem `pageshow` (persisted) geprüft: Reset, kein Autoplay, Scroll spielt.
- Resize auf 844×390 (Querformat): Scroll spielt korrekt.
- Replay zweimal hintereinander startet jeweils neu; „Überspringen“ setzt Endzustand.
- „Bewegung aus“: Scroll startet nichts, Tür 0°. Reduced Motion (emuliert, 390×844): nach Scroll und 7 s Rad 0°, Tür 0°, Status „Bewegung reduziert“.
- Kein horizontaler Überlauf bei 320/390/1280. Build: OK.

Nicht geprüft: echte iOS/Android-Geräte, echter BFCache in einem normalen Browser, echter Tabwechsel (nur emuliert), Screenreader, veröffentlichte Domain.

## Opt-in bei System-Reduced-Motion (02.11.2026, 20:50 UTC)
Fehler: Bei Android mit „Bewegung reduzieren“ stand dauerhaft „Bewegung reduziert“, Replay war gesperrt, kein Weg zur Animation.
Fix: Dreistufige Einstellung nur für die Tresorszene (auto / an / aus, gespeichert nur für die Sitzung). Bei System-Reduce startet die Seite statisch mit Hinweis „Statisch, weil Ihr Gerät reduzierte Bewegung wünscht“ und Schaltfläche „Animation aktivieren“. Opt-in überstimmt JS und CSS nur in `.vault-stage.motion-optin`; übrige Reduced-Motion-Regeln unverändert. Kein Autoplay, Start beim nächsten Scroll. „Animation ausschalten“ macht die Szene wieder statisch. Cache-Buster `app.js?v=4`, `styles.css?v=4`.
Test `tests/e2e/vault_reduced_optin_check.py` (390×844, Chromium headless), alle bestanden:
- Reduce, Start: Rad 0°, Tür 0°, Replay gesperrt, Taste „Animation aktivieren“. Scroll ohne Opt-in: 0°/0°.
- Nach Opt-in kein Autoplay (0°), Replay aktiv. Scroll: 1,4 s Rad 76,2°/Tür 0°; 2,6 s 99,7°/0°; 4,2 s 100°/−53,5°; 5,0 s −66,2°; 6,4 s −68°, offen. Replay: bei 4,2 s Tür −53,5°.
- Ausschalten + Scroll: 0°/0°, „Animation ausgeschaltet“. Neu laden in derselben Sitzung: bleibt aus.
- Ohne Reduce: 1,4 s 74,5°/0°; 2,6 s 99,7°/0°; 4,2 s −53°; 6,4 s −68°, offen. Keine JS-Fehler. Build OK.
Nicht geprüft: echtes Android-Gerät; vollständige Suite nicht erneut ausgeführt.

## v5: Scroll-gekoppelte Tresorfahrt (02.11.2026, 21:35 UTC)
Ersetzt die Timer-/Opt-in-Lösung (v3/v4). Szene steht per `position:sticky` im Viewport, eigener Scrollbereich (Desktop 260svh, mobil 240svh, Reduced Motion 170/160svh). Fortschritt 0–1 steuert stufenlos: 0–0,2 Rad 0→100° + Riegel; 0,2–0,5 Tür 0→−100°, ab 0,44 ausgeblendet (vor dem Zoom ganz aus dem Kamerapfad); 0,5–0,92 Kamerafahrt in die Öffnung (Innenraum zentriert, Schriftzug blendet bis 0,62 aus); danach weicher Verlauf in #werte. Kein Timer, keine Buttons, keine Statuszeile, kein preventDefault. Reduced Motion: gleiche Kopplung, Zoomstärke 30 %, kürzerer Bereich, ohne Klick. Ohne JS: statische Szene ohne Scrollbereich. Cache-Buster `app.js?v=5`, `styles.css?v=5`.

Test `tests/e2e/vault_scroll_check.py` (neu, Chromium headless): 119 Prüfungen bestanden, 0 fehlgeschlagen. Gemessen (Rad / Tür / Kamera-Scale), Bühne bei allen Punkten fixiert (Abstand oben ≤ 0,2 px):
| Breite | p=0 | p=0,2 | p=0,5 | p=0,75 | p=1 |
|---|---|---|---|---|---|
| 1280×800 | 0/0/1 | 100/0/1 | 100/−100 (Deckkraft 0)/1 | 100/−100/2,48 | 100/−100/3,21 |
| 390×844 | 0/0/1 | 100/0/1 | 100/−100 (0)/1 | 100/−100/2,82 | 100/−100/3,71 |
| 320×568 | 0/0/1 | 100/0/1 | 100/−100 (0)/1 | 100/−100/2,21 | 100/−100/2,80 |
| 390×844 Reduce | 0/0/1 | 100/0/1 | 100/−100 (0)/1 | 100/−100/1,55 | 100/−100/1,81 |
| 1280×800 Reduce | 0/0/1 | 100/0/1 | 100/−100 (0)/1 | 100/−100/1,45 | 100/−100/1,66 |
Außerdem bestanden: Laden ohne Scroll bleibt geschlossen; stillstehender Scroll = keine Veränderung (1 s); Rückwärts auf 0,35 (Tür halb, kein Zoom) und auf 0 (geschlossen); #werte erreichbar; mobile Kontaktleiste oben und antippbar; nach Resize/Querformat bei p=0,5 Tür offen und Bühne fixiert; kein horizontaler Überlauf; keine JS-Fehler; ohne JS kein langer leerer Bereich, Text lesbar. Build OK.
Überholt: `tests/e2e/obsolete/` (Timer- und Opt-in-Tests v3/v4).
Nicht geprüft: echte iOS/Android-Geräte, Ruckelfreiheit auf schwachen Geräten, Screenreader, veröffentlichte Domain.

## v6: Tresor als Einstiegssequenz, Zoom in den echten Hero (02.11.2026, 22:00 UTC)
Umsetzung: Tresorszene steht als erste Ansicht vor dem Hero (Header bleibt). Tür und Öffnung teilen ein Rechteck (Seitenverhältnis 460/620 = SVG-viewBox, kein Letterboxing). Hinter der Tür liegt eine dekorative, `inert`/`aria-hidden` Vorschau des echten Hero (ohne h1, ohne IDs, ohne Links). Ablauf: Rad/Riegel (0–0,2), Tür komplett auf (0,2–0,5), Zoom durch die Öffnung (0,5–0,94), am Ende liegt der echte Hero exakt darunter (oben 0) und übernimmt. Reduce: Zoom auf 30 % Stärke, danach weiche Abblende zum Hero, ohne Klick. Die Klasse `scrolly` setzt jetzt `app.js` selbst; ohne JS oder bei Ladefehler normaler Fluss ohne Track. Cache-Buster v6.

Automatisch geprüft (`tests/e2e/vault_scroll_check.py`, Chromium headless, 148/148 bestanden):
- p=0 Öffnung und Tür identische Rechtecke (≤1 px) bei 1280×800, 390×844, 320×568, Reduce, ohne JS, mit blockiertem app.js und nach Größenwechsel.
- Gemessen Rad/Tür/Kamera-Scale bei p=0/.2/.5/.75/1:
  - 1280×800: 0/0/1 · 100/0/1 · 100/−100/1 · 100/−100/2,49 · 100/−100/3,37
  - 390×844: 0/0/1 · 100/0/1 · 100/−100/1 · 100/−100/1,63 · 100/−100/2,01
  - 320×568: 0/0/1 · 100/0/1 · 100/−100/1 · 100/−100/1,49 · 100/−100/1,79
  - Reduce 390×844: Endscale 1,30; Reduce 1280×800: 1,71
- p=1: echter Hero oben 0,00 px (1280), 0,39 px (390), −0,20 px (320); H1 und CTA im Viewport oberhalb der Kontaktleiste, CTA anklickbar; Intro unsichtbar.
- Genau eine h1, keine doppelten IDs; Vorschau inert ohne Links. Keine Steuerbuttons, kein Autoplay nach 2 s, Stillstand, Rückwärtsfahrt, #werte erreichbar, Preiswechsel 500 €, kein horizontaler Überlauf, keine JS-Fehler.
Visuell geprüft (Screenshots): Einstieg mobil/desktop, Zwischenstände, Übergabe. Pixelvergleich letzter Zoomframe vs. echter Hero: 1280 identisch (0 Pixel >40 Differenz); 390/320 Abweichung nur durch Subpixel-Versatz (<0,4 px) im Text.
Nicht geprüft: echte Geräte (iOS-Adressleiste/svh), schwache Geräte, Screenreader, Live-Domain.

## v7: Schubladen-Übergänge zwischen den Kapiteln (02.11.2026, 22:15 UTC)
Umsetzung: `app.js` setzt vor #werte, #sicherheit, #schliessfaecher, #ablauf, Galerie, #fragen und #kontakt je eine kurze Spur (+50 svh, Reduce +30 svh). Darin fährt eine Kassettenfront (Metallschienen, Schlüsselloch, Griff, Kapitelnummer/-titel) per Perspektive, Verschiebung nach vorn-unten und Kippen aus der Tresorwand. Die echte Sektion liegt darunter und steht am Ende oben bei 0. Kassettenhöhe und Farbton variieren leicht. Die Spuren sind `inert`/`aria-hidden`, ohne Links, Überschriften oder IDs, und nehmen nie Klicks an. Fokus in einem Kapitel blendet dessen Front aus. Ankersprünge landen auf der Sektion. Ohne JS oder bei Skriptfehler gibt es keine Spuren. Tür-Intro bleibt unverändert. Cache-Buster v7.

Automatisch geprüft (Chromium headless):
- `tests/e2e/drawer_scroll_check.py`: 172/172 bestanden bei 1280×800, 390×844, 320×568, Reduce, ohne JS und mit blockiertem app.js.
  - Gemessene Front-Bewegung (dy/tz in px) p=0 / .5 / 1: 1280: 0/0 · ~208/230 · 416/460; 320: 0/0 · 147/189 · 295/380; Reduce 390: 0/0 · 59/60 · 118/120.
  - p=1: Sektion oben −0,34…+0,49 px bei allen 7 Übergängen und allen Größen. Außerdem Rückwärtsfahrt, Stillstand, kein Überlauf, Zusatzweg ≤ 50 svh.
  - Nach den Übergängen: Größe 5 wählbar (500 €), FAQ per Klick offen, Anker #kontakt landet bei 0 und der Kontaktlink ist klickbar, Tastaturfokus wird nicht verdeckt. Eine h1, keine doppelten IDs, keine JS-Fehler.
- `tests/e2e/vault_scroll_check.py` (Intro): 148/148 weiterhin bestanden.
Visuell geprüft: Screenshots der Übergänge zu Sicherheit (390 px) und Schließfächer (1280 px) bei −0,4 / 0 / 0,5 / 0,8 / 1.
Nicht geprüft: echte Geräte (iOS-svh, Trägheits-Scroll), schwache Geräte, Screenreader, Live-Domain; Screenshots nicht für jedes der 7 Kapitel einzeln bewertet.
