M3/Nachtschicht-Replay: S3.1 Replay-UI + S3.2 reduced-motion/Mobile — M3 abgeschlossen
added
- `src/components/profile/visitenkarte/nachtschicht-replay.tsx`: Client-Replay am Maschinenraum-Band. Play-Button (kein Autoplay), spielt die letzten ~72 h autonomer Arbeit als Vorlauf ab — Heatmap füllt sich Quadrat für Quadrat (lose an den Vorlauf gekoppelt), Journal-Titel ticken vorbei (`aria-live="polite"`). Pausierbar (Pause ⇄ Weiter ⇄ Nochmal). `prefers-reduced-motion` = statische chronologische Liste derselben Einträge, keine Bewegung (S3.2). Mobile = kürzerer Vorlauf (max 6 Einträge, schnellere Taktung). Bewusst keine Laufzeit-Imports aus `lib/journal`/`lib/autonomous-hours` (würden deren JSON in den Client-Chunk ziehen) — nur Type-Imports, Daten als Props.
- `src/components/profile/visitenkarte/nachtschicht-replay-island.tsx`: Lazy-Island-Wrapper (`dynamic`, `ssr:false`, `loading:null`) — hält die Replay-UI aus dem First-Load-JS (F7).
- `src/lib/autonomous-hours.ts`: `getHeatmapDays(count)` + `HeatmapDay` — geteilte Quelle für statische Heatmap und Replay.
- `src/lib/journal.ts`: `getRecentChangelogEntries(days, max)` + `ReplayEntry` — chronologische Changelog-Einträge der letzten Tage (Public-Regel: nur Datum, keine Uhrzeit).
changed
- `src/components/profile/visitenkarte/maschinenraum.tsx`: `Heatmap14` nutzt jetzt `getHeatmapDays(14)` statt eigener Schleife; das Replay-Island wird unter dem Band gemountet (statische Heatmap bleibt unverändert als No-JS-/SSR-Fallback).
- `src/app/globals.css`: `@keyframes nightshift-tick-in` (sanftes Einblenden des Tickers) + reduced-motion-Guard.
verification
- Gate grün: `npx eslint` 0 Errors (inkl. `react-hooks/set-state-in-effect` sauber gelöst — das Replay-Ende leitet sich ab, kein setState im Effect), `npx tsc --noEmit` 0, `npm run build` grün.
- SSR-Smoke (`next start`, curl `/`): HTTP 200, Maschinenraum-Band + statische 14-Tage-Heatmap im HTML; Replay-Island **nicht** im SSR (ssr:false → eigener Client-Chunk, First-Load unverändert, F7 gehalten).
- Datenpfad-Check: `getHeatmapDays(14)` → 6/14 aktive Tage in variierenden Buckets; `getRecentChangelogEntries(3,12)` → 12 reale chronologische Einträge (inkl. M1/M2-Arbeit) ⇒ Replay läuft auf echten Daten.