big upgrade

This commit is contained in:
2026-05-10 12:19:58 +02:00
parent 5bd2019fc6
commit f7dd547f8d
38 changed files with 2363 additions and 329 deletions

View File

@@ -0,0 +1 @@
{"specId": "19abeaa5-fd7a-4c4e-9557-d63c62f2e8e1", "workflowType": "requirements-first", "specType": "feature"}

View File

@@ -0,0 +1,263 @@
# Design-Dokument System-Theme-Erkennung (Light/Dark Mode)
## Übersicht
Dieses Feature integriert die bereits installierte Bibliothek `next-themes` (v0.3.0) in die bestehende React + Vite + Tailwind + shadcn/ui-Webseite, um eine automatische Erkennung des Betriebssystem-Themes (hell/dunkel) sowie manuelles Umschalten zu ermöglichen.
Aktuell definiert `:root` in `src/index.css` das dunkle Farbschema. Es existiert zwar ein `.dark`-Block, dieser wird aber nicht aktiv genutzt. Die Tailwind-Konfiguration verwendet bereits `darkMode: ["class"]`, was perfekt mit `next-themes` zusammenarbeitet.
Kernentscheidungen:
- `next-themes` wird als ThemeProvider eingesetzt (bereits als Dependency vorhanden, `attribute: "class"` für Tailwind-Kompatibilität)
- Das aktuelle `:root`-Farbschema wird zum Light Theme umstrukturiert, ein neues Dark Theme wird unter `.dark` definiert
- Ein Theme-Toggle-Button wird im Header platziert (Desktop und Mobile)
- Ein Inline-Script in `index.html` verhindert Theme-Flackern beim Laden (FOUC-Prevention)
## Architektur
```mermaid
graph TD
A[index.html Inline-Script] -->|Setzt class auf html| B[html-Element]
B --> C[ThemeProvider next-themes]
C --> D[App.tsx]
D --> E[Header mit ThemeToggle]
D --> F[Alle Seiten & Komponenten]
G[localStorage theme] <-->|Lesen/Schreiben| C
H[prefers-color-scheme] -->|System-Erkennung| C
I[index.css :root Light] --> F
J[index.css .dark Dark] --> F
```
### Datenfluss
1. Beim Laden der Seite liest ein Inline-Script in `index.html` den `localStorage`-Wert (`theme`) aus. Falls keiner vorhanden ist, wird `prefers-color-scheme` geprüft. Die entsprechende CSS-Klasse (`dark` oder keine) wird auf `<html>` gesetzt noch bevor React rendert.
2. `ThemeProvider` aus `next-themes` übernimmt die Verwaltung im React-Baum. Er synchronisiert den Zustand mit `localStorage` und reagiert auf Änderungen der System-Einstellung via `matchMedia`-Listener.
3. Der `ThemeToggle`-Button im Header nutzt den `useTheme()`-Hook, um zwischen `light`, `dark` und `system` zu wechseln.
4. Alle Komponenten nutzen weiterhin `hsl(var(--...))` CSS-Variablen der Wechsel erfolgt rein über CSS-Klassen.
## Komponenten und Schnittstellen
### 1. ThemeProvider-Wrapper (`src/components/ThemeProvider.tsx`)
Dünner Wrapper um `ThemeProvider` aus `next-themes`:
```typescript
interface ThemeProviderProps {
children: React.ReactNode;
defaultTheme?: string; // Standard: "system"
storageKey?: string; // Standard: "theme"
enableSystem?: boolean; // Standard: true
disableTransitionOnChange?: boolean; // Standard: false
}
```
Konfiguration:
- `attribute="class"` Tailwind-kompatibel
- `defaultTheme="system"` Erstbesucher erhalten System-Theme
- `enableSystem={true}` Automatische Erkennung aktiv
- `storageKey="theme"` localStorage-Schlüssel
Wird in `App.tsx` als äußerster Wrapper um den gesamten Komponentenbaum eingebunden.
### 2. ThemeToggle-Komponente (`src/components/ThemeToggle.tsx`)
Button-Komponente mit Dropdown-Menü (shadcn/ui `DropdownMenu`):
```typescript
interface ThemeToggleProps {
className?: string;
}
```
Funktionalität:
- Zeigt ein Icon basierend auf dem aktuellen Theme (Sun, Moon, Monitor aus `lucide-react`)
- Dropdown mit drei Optionen: „Hell", „Dunkel", „System"
- Nutzt `useTheme()` Hook für `theme`, `setTheme`, `resolvedTheme`
- `aria-label` beschreibt den aktuellen Zustand
- Tastatur-bedienbar (Tab, Enter/Space)
- Mounted-Check verhindert Hydration-Mismatch (Icon wird erst nach Mount gerendert)
### 3. Anpassungen am Header (`src/components/Header.tsx`)
- ThemeToggle wird im Desktop-NavBody neben dem „Kontakt"-Button eingefügt
- ThemeToggle wird im Mobile-Nav-Header neben dem Hamburger-Menü eingefügt
- Positionierung: rechts, vor dem Kontakt-Button (Desktop) bzw. links vom Toggle-Icon (Mobile)
### 4. Inline-Script in `index.html`
Ein `<script>`-Block im `<head>`, der vor dem React-Bundle ausgeführt wird:
```javascript
// Liest localStorage("theme") aus
// Falls "system" oder nicht vorhanden: prüft prefers-color-scheme
// Setzt class="dark" auf <html> wenn Dark Mode aktiv
```
Dieses Script verhindert das sichtbare Flackern (FOUC) beim Seitenaufruf.
### 5. CSS-Variablen-Umstrukturierung (`src/index.css`)
- `:root` → Light Theme (helle Hintergründe, dunkle Texte)
- `.dark` → Dark Theme (das aktuelle `:root`-Farbschema wird hierhin verschoben)
- Alle bestehenden Komponentenstile (`.glass-nav`, `.card-minimal`, `.project-card`, `.btn`, etc.) werden auf Theme-Kompatibilität geprüft und ggf. angepasst
- Hardcodierte Farbwerte in Komponentenstilen werden durch CSS-Variablen ersetzt
### Komponentendiagramm
```mermaid
graph LR
subgraph App.tsx
TP[ThemeProvider]
TP --> Router
end
subgraph Header
TT[ThemeToggle]
TT -->|useTheme| TP
end
subgraph CSS
LT[":root Light Theme"]
DT[".dark Dark Theme"]
end
TP -->|class auf html| CSS
```
## Datenmodelle
### Theme-Zustand
```typescript
type ThemeValue = "light" | "dark" | "system";
// Von next-themes bereitgestellt via useTheme()
interface ThemeState {
theme: ThemeValue; // Gewählte Präferenz ("light" | "dark" | "system")
resolvedTheme: "light" | "dark"; // Tatsächlich angewendetes Theme
setTheme: (theme: ThemeValue) => void;
systemTheme: "light" | "dark"; // Aktuelle OS-Einstellung
}
```
### localStorage-Schema
| Schlüssel | Wert | Beschreibung |
|-----------|------|--------------|
| `theme` | `"light"` \| `"dark"` \| `"system"` | Gespeicherte Benutzerpräferenz |
### CSS-Custom-Properties (Light Theme neu zu erstellen)
```css
:root {
--background: 0 0% 100%; /* Weiß */
--foreground: 0 0% 9%; /* Fast Schwarz */
--card: 0 0% 98%; /* Sehr helles Grau */
--card-foreground: 0 0% 9%;
--primary: 198 93% 42%; /* Cyan-Blau (konsistent) */
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 94%; /* Helles Grau */
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 94%;
--muted-foreground: 0 0% 40%;
--accent: 0 0% 94%;
--accent-foreground: 0 0% 9%;
--border: 0 0% 88%;
--input: 0 0% 88%;
--ring: 198 93% 42%;
--destructive: 0 62% 50%;
--destructive-foreground: 0 0% 98%;
/* ... weitere Token analog */
}
```
### CSS-Custom-Properties (Dark Theme bestehendes Schema)
```css
.dark {
--background: 0 0% 0%; /* Schwarz (aktuelles :root) */
--foreground: 0 0% 92%;
--card: 0 0% 6%;
--card-foreground: 0 0% 92%;
--primary: 180 40% 50%;
--primary-foreground: 0 0% 3%;
/* ... restliche aktuelle :root-Werte */
}
```
## Korrektheitseigenschaften (Correctness Properties)
*Eine Korrektheitseigenschaft ist ein Merkmal oder Verhalten, das über alle gültigen Ausführungen eines Systems hinweg gelten soll im Wesentlichen eine formale Aussage darüber, was das System tun soll. Eigenschaften bilden die Brücke zwischen menschenlesbaren Spezifikationen und maschinell überprüfbaren Korrektheitsgarantien.*
### Property 1: System-Theme-Auflösung
*Für jede* beliebige System-Theme-Einstellung (hell oder dunkel) gilt: Wenn die Theme-Präferenz auf „system" steht (oder keine Präferenz gespeichert ist), dann soll das aufgelöste Theme (`resolvedTheme`) dem aktuellen System-Theme entsprechen.
**Validates: Requirements 1.1, 1.2, 1.3, 2.5**
### Property 2: Explizite Theme-Wahl überschreibt System
*Für jede* beliebige System-Theme-Einstellung und *für jede* explizite Theme-Wahl (`"light"` oder `"dark"`) gilt: Das aufgelöste Theme (`resolvedTheme`) soll immer der expliziten Wahl entsprechen, unabhängig vom System-Theme.
**Validates: Requirements 2.3, 2.4**
### Property 3: Persistenz-Round-Trip
*Für jeden* gültigen Theme-Wert (`"light"`, `"dark"`, `"system"`) gilt: Wenn der Wert über `setTheme()` gesetzt wird, dann soll der im `localStorage` gespeicherte Wert identisch sein, und beim erneuten Laden soll dieser gespeicherte Wert korrekt als aktive Präferenz wiederhergestellt werden.
**Validates: Requirements 3.1, 3.2**
## Fehlerbehandlung
| Szenario | Verhalten |
|----------|-----------|
| `localStorage` nicht verfügbar (blockiert, privater Modus) | ThemeProvider fällt auf System-Theme-Erkennung zurück. Kein Fehler wird geworfen. Die Seite funktioniert normal, nur die Persistenz entfällt. |
| `prefers-color-scheme` nicht unterstützt (ältere Browser) | `next-themes` fällt auf den `defaultTheme` zurück (`"system"` → wird als `"light"` aufgelöst). |
| Ungültiger Wert im `localStorage` | `next-themes` ignoriert ungültige Werte und fällt auf `defaultTheme` zurück. |
| JavaScript deaktiviert | Das Inline-Script in `index.html` wird nicht ausgeführt. Die Seite zeigt das Standard-CSS (Light Theme in `:root`). Kein Theme-Toggle verfügbar, aber die Seite bleibt nutzbar. |
| Hydration-Mismatch (SSR/SSG) | Nicht relevant für Vite-SPA. Der Mounted-Check im ThemeToggle verhindert trotzdem Icon-Flackern. |
## Teststrategie
### Property-Based Tests (fast-check)
Bibliothek: `fast-check` (für TypeScript/Vitest)
Konfiguration:
- Minimum 100 Iterationen pro Property-Test
- Jeder Test referenziert die zugehörige Design-Property
- Tag-Format: **Feature: system-theme-detection, Property {number}: {property_text}**
Die drei Korrektheitseigenschaften werden als Property-Based Tests implementiert:
1. **Property 1** Theme-Auflösungslogik bei „system"-Präferenz: Generiere zufällige System-Theme-Werte, setze Präferenz auf „system", prüfe `resolvedTheme === systemTheme`.
2. **Property 2** Explizite Wahl überschreibt System: Generiere zufällige Kombinationen aus System-Theme und expliziter Wahl, prüfe `resolvedTheme === expliziteWahl`.
3. **Property 3** Persistenz-Round-Trip: Generiere zufällige gültige Theme-Werte, speichere via `setTheme()`, lese aus `localStorage`, prüfe Gleichheit. Simuliere Neustart, prüfe ob Wert wiederhergestellt wird.
### Unit Tests (Vitest + React Testing Library)
Ergänzend zu den Property-Tests werden folgende Example-basierte Tests geschrieben:
- **ThemeToggle-Rendering**: Button ist im Header vorhanden (Req. 2.1)
- **Dropdown-Optionen**: Drei Optionen (Hell, Dunkel, System) sind vorhanden (Req. 2.2)
- **Icon-Zuordnung**: Korrektes Icon für jeden Theme-Zustand (Req. 2.6)
- **CSS-Token-Vollständigkeit**: Alle erforderlichen Custom Properties sind im Light Theme definiert (Req. 4.1)
- **Kontrastverhältnisse**: Schlüssel-Farbpaare erfüllen WCAG-AA (Req. 4.2)
- **Tastatur-Bedienbarkeit**: Tab-Navigation und Enter/Space funktionieren (Req. 6.1)
- **aria-label**: Beschreibt den aktuellen Zustand korrekt (Req. 6.2)
- **aria-live**: Zustandswechsel wird kommuniziert (Req. 6.3)
### Edge-Case Tests
- **localStorage blockiert**: Fallback auf System-Theme ohne Fehler (Req. 3.3)
### Integrationstests
- **matchMedia-Listener**: Simuliere OS-Theme-Wechsel, prüfe ob resolvedTheme sich aktualisiert (Req. 1.4)
### Nicht automatisiert testbar
- Visuell korrekte Darstellung der Komponentenstile im Light Theme (Req. 4.3) → Manuelle visuelle Prüfung
- Kein sichtbares Flackern beim Laden (Req. 5.2) → Manuelle visuelle Prüfung

View File

@@ -0,0 +1,80 @@
# Anforderungsdokument System-Theme-Erkennung (Light/Dark Mode)
## Einleitung
Die Webseite (React + Tailwind + shadcn/ui) verwendet aktuell ein fest eingestelltes dunkles Farbschema. Es soll eine automatische Erkennung der Betriebssystem-Einstellung (Light/Dark Mode) eingeführt werden, sodass die Webseite das passende Theme anzeigt. Zusätzlich soll der Benutzer das Theme manuell umschalten können. Die Präferenz wird im Browser gespeichert, damit sie bei erneutem Besuch erhalten bleibt.
## Glossar
- **Theme_Provider**: Die React-Kontextkomponente, die das aktuelle Theme verwaltet und an alle Kindkomponenten weitergibt. Basiert auf `next-themes`.
- **Theme_Toggle**: Die UI-Komponente (Button), mit der der Benutzer manuell zwischen Light Mode, Dark Mode und System-Automatik wechseln kann.
- **System_Theme**: Die vom Betriebssystem des Benutzers bevorzugte Farbeinstellung (hell oder dunkel), ermittelt über die CSS-Media-Query `prefers-color-scheme`.
- **Light_Theme**: Das helle Farbschema mit CSS-Custom-Properties für helle Hintergründe und dunkle Texte.
- **Dark_Theme**: Das dunkle Farbschema mit CSS-Custom-Properties für dunkle Hintergründe und helle Texte (aktuell als `:root`-Standard definiert).
- **Theme_Präferenz**: Die vom Benutzer gewählte Einstellung (`light`, `dark` oder `system`), gespeichert im `localStorage`.
## Anforderungen
### Anforderung 1: Automatische Erkennung des System-Themes
**User Story:** Als Besucher möchte ich, dass die Webseite automatisch erkennt, ob mein Betriebssystem auf hell oder dunkel eingestellt ist, damit die Seite sofort zum Erscheinungsbild meines Systems passt.
#### Akzeptanzkriterien
1. WHEN ein Benutzer die Webseite zum ersten Mal besucht und keine gespeicherte Theme_Präferenz vorhanden ist, THE Theme_Provider SHALL das System_Theme über die Media-Query `prefers-color-scheme` ermitteln und das entsprechende Farbschema anwenden.
2. WHILE das System_Theme auf „dunkel" eingestellt ist und die Theme_Präferenz auf „system" steht, THE Theme_Provider SHALL das Dark_Theme anwenden.
3. WHILE das System_Theme auf „hell" eingestellt ist und die Theme_Präferenz auf „system" steht, THE Theme_Provider SHALL das Light_Theme anwenden.
4. WHEN der Benutzer die Betriebssystem-Einstellung von hell auf dunkel oder umgekehrt ändert und die Theme_Präferenz auf „system" steht, THE Theme_Provider SHALL das angezeigte Theme innerhalb von 1 Sekunde aktualisieren, ohne dass die Seite neu geladen werden muss.
### Anforderung 2: Manuelles Umschalten des Themes
**User Story:** Als Besucher möchte ich das Theme manuell zwischen Light Mode, Dark Mode und System-Automatik umschalten können, damit ich unabhängig von meiner Systemeinstellung das bevorzugte Erscheinungsbild wählen kann.
#### Akzeptanzkriterien
1. THE Theme_Toggle SHALL im Header der Webseite sichtbar und erreichbar platziert sein.
2. WHEN der Benutzer den Theme_Toggle betätigt, THE Theme_Toggle SHALL die drei Optionen „Light", „Dark" und „System" zur Auswahl anbieten.
3. WHEN der Benutzer die Option „Light" auswählt, THE Theme_Provider SHALL das Light_Theme sofort anwenden.
4. WHEN der Benutzer die Option „Dark" auswählt, THE Theme_Provider SHALL das Dark_Theme sofort anwenden.
5. WHEN der Benutzer die Option „System" auswählt, THE Theme_Provider SHALL das Theme gemäß dem aktuellen System_Theme anwenden.
6. THE Theme_Toggle SHALL ein Icon anzeigen, das den aktuellen Theme-Zustand visuell darstellt (Sonne für Light, Mond für Dark, Monitor für System).
### Anforderung 3: Persistenz der Theme-Auswahl
**User Story:** Als wiederkehrender Besucher möchte ich, dass meine Theme-Auswahl gespeichert wird, damit ich beim nächsten Besuch nicht erneut umschalten muss.
#### Akzeptanzkriterien
1. WHEN der Benutzer eine Theme_Präferenz über den Theme_Toggle auswählt, THE Theme_Provider SHALL die Auswahl im localStorage des Browsers speichern.
2. WHEN der Benutzer die Webseite erneut besucht und eine gespeicherte Theme_Präferenz vorhanden ist, THE Theme_Provider SHALL die gespeicherte Präferenz anwenden.
3. IF der localStorage nicht verfügbar oder blockiert ist, THEN THE Theme_Provider SHALL auf die System_Theme-Erkennung zurückfallen und ohne Fehler weiterarbeiten.
### Anforderung 4: Definition des Light Themes
**User Story:** Als Besucher möchte ich ein visuell ansprechendes helles Farbschema sehen, das zum bestehenden Design der Webseite passt.
#### Akzeptanzkriterien
1. THE Light_Theme SHALL CSS-Custom-Properties für alle bestehenden Design-Token definieren (background, foreground, card, primary, secondary, muted, accent, border, input, ring, destructive, popover, sidebar, chart-1 bis chart-5).
2. THE Light_Theme SHALL helle Hintergrundfarben und dunkle Textfarben verwenden, die einen WCAG-AA-konformen Kontrast aufweisen.
3. THE Light_Theme SHALL die bestehenden Komponentenstile (glass-nav, card-minimal, text-gradient, btn-minimal, btn-outline, project-card) visuell korrekt darstellen.
4. WHEN das Light_Theme aktiv ist, THE Theme_Provider SHALL die CSS-Klasse vom `<html>`-Element so setzen, dass Tailwind-Utility-Klassen mit `dark:`-Präfix korrekt reagieren.
### Anforderung 5: Vermeidung von Theme-Flackern beim Laden
**User Story:** Als Besucher möchte ich beim Laden der Seite kein kurzes Aufblitzen des falschen Themes sehen, damit das Erlebnis professionell wirkt.
#### Akzeptanzkriterien
1. THE Theme_Provider SHALL ein Inline-Script im `<head>` des HTML-Dokuments verwenden, das die gespeicherte Theme_Präferenz oder das System_Theme ausliest und die entsprechende CSS-Klasse auf das `<html>`-Element setzt, bevor der Seiteninhalt gerendert wird.
2. WHEN die Seite geladen wird, THE Theme_Provider SHALL das korrekte Theme ohne sichtbares Flackern oder Farbwechsel anzeigen.
### Anforderung 6: Barrierefreiheit des Theme-Toggles
**User Story:** Als Besucher mit Einschränkungen möchte ich den Theme-Toggle per Tastatur und Screenreader bedienen können.
#### Akzeptanzkriterien
1. THE Theme_Toggle SHALL per Tastatur fokussierbar und bedienbar sein (Tab-Navigation und Enter/Space zum Aktivieren).
2. THE Theme_Toggle SHALL ein `aria-label`-Attribut besitzen, das den aktuellen Zustand und die Funktion beschreibt (z. B. „Theme wechseln, aktuell: Dunkel").
3. WHEN der Benutzer das Theme über den Theme_Toggle wechselt, THE Theme_Toggle SHALL den neuen Zustand über ein `aria-live`-Attribut oder eine gleichwertige Methode an Screenreader kommunizieren.

View File

@@ -0,0 +1,114 @@
# Implementation Plan: System-Theme-Erkennung (Light/Dark Mode)
## Overview
Integrate `next-themes` into the existing React + Vite + Tailwind + shadcn/ui app to enable automatic OS theme detection, manual theme switching (light/dark/system), and persistent user preference. The CSS variables in `src/index.css` will be restructured so `:root` defines the light theme and `.dark` defines the dark theme. A ThemeToggle component will be added to the Header, and an inline script in `index.html` will prevent FOUC.
## Tasks
- [x] 1. Restructure CSS variables and set up testing dependencies
- [x] 1.1 Restructure CSS custom properties in `src/index.css`
- Move the current `:root` dark color values into the `.dark` block
- Define new light theme values in `:root` (white backgrounds, dark text, matching design spec)
- Ensure all design tokens are covered: background, foreground, card, primary, secondary, muted, accent, border, input, ring, destructive, popover, sidebar, chart-1 through chart-5
- Update hardcoded color values in component styles (`.glass-nav`, `.card-minimal`, `.project-card`, `.btn`, `.text-gradient`, etc.) to use CSS variables where possible
- Add dark-mode-aware variants for component styles that use hardcoded HSL values
- _Requirements: 4.1, 4.2, 4.3, 4.4_
- [x] 1.2 Install `fast-check` as a dev dependency
- Run `npm install --save-dev fast-check`
- _Requirements: (testing infrastructure)_
- [x] 2. Create ThemeProvider wrapper and integrate into App
- [x] 2.1 Create `src/components/ThemeProvider.tsx`
- Export a thin wrapper around `ThemeProvider` from `next-themes`
- Configure with `attribute="class"`, `defaultTheme="system"`, `enableSystem={true}`, `storageKey="theme"`
- Accept `children` and optional override props
- _Requirements: 1.1, 1.2, 1.3, 3.1, 3.2_
- [x] 2.2 Wrap the App component tree with ThemeProvider in `src/App.tsx`
- Import ThemeProvider and wrap it as the outermost provider around the existing component tree
- _Requirements: 1.1, 2.3, 2.4, 2.5_
- [ ]* 2.3 Write property test: System-Theme-Auflösung (Property 1)
- **Property 1: System-Theme-Auflösung**
- Generate arbitrary system theme values (light/dark), set preference to "system", verify `resolvedTheme === systemTheme`
- Use `fast-check` with minimum 100 iterations
- **Validates: Requirements 1.1, 1.2, 1.3, 2.5**
- [ ]* 2.4 Write property test: Explizite Theme-Wahl überschreibt System (Property 2)
- **Property 2: Explizite Theme-Wahl überschreibt System**
- Generate arbitrary combinations of system theme and explicit choice ("light"/"dark"), verify `resolvedTheme === expliziteWahl`
- Use `fast-check` with minimum 100 iterations
- **Validates: Requirements 2.3, 2.4**
- [ ]* 2.5 Write property test: Persistenz-Round-Trip (Property 3)
- **Property 3: Persistenz-Round-Trip**
- Generate arbitrary valid theme values ("light", "dark", "system"), set via `setTheme()`, read from `localStorage`, verify equality and correct restoration on simulated reload
- Use `fast-check` with minimum 100 iterations
- **Validates: Requirements 3.1, 3.2**
- [x] 3. Checkpoint
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Create ThemeToggle component
- [x] 4.1 Create `src/components/ThemeToggle.tsx`
- Build a button component using shadcn/ui `DropdownMenu` and `Button`
- Use `useTheme()` hook from `next-themes` for `theme`, `setTheme`, `resolvedTheme`
- Display Sun icon (lucide-react) for light, Moon for dark, Monitor for system
- Implement mounted-state check to prevent hydration mismatch on icon rendering
- Dropdown offers three options: "Hell" (light), "Dunkel" (dark), "System" (system)
- Add `aria-label` describing current state (e.g., "Theme wechseln, aktuell: Dunkel")
- Ensure keyboard navigability (Tab, Enter/Space)
- Accept optional `className` prop for positioning flexibility
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 6.1, 6.2, 6.3_
- [ ]* 4.2 Write unit tests for ThemeToggle
- Test that the toggle button renders in the DOM
- Test that dropdown shows three options (Hell, Dunkel, System)
- Test correct icon rendering per theme state
- Test `aria-label` contains current theme state
- Test keyboard interaction (focus, Enter/Space opens dropdown)
- _Requirements: 2.1, 2.2, 2.6, 6.1, 6.2, 6.3_
- [x] 5. Integrate ThemeToggle into Header
- [x] 5.1 Add ThemeToggle to desktop navigation in `src/components/Header.tsx`
- Import ThemeToggle and place it in the `NavBody` actions area, before the "Kontakt" button
- _Requirements: 2.1_
- [x] 5.2 Add ThemeToggle to mobile navigation in `src/components/Header.tsx`
- Place ThemeToggle in the `MobileNavHeader`, positioned next to the hamburger toggle icon
- _Requirements: 2.1_
- [ ]* 5.3 Write unit tests for Header ThemeToggle integration
- Test that ThemeToggle is present in the rendered Header component
- _Requirements: 2.1_
- [x] 6. Add FOUC-prevention inline script to `index.html`
- [x] 6.1 Add inline `<script>` block in the `<head>` of `index.html`
- Read `localStorage.getItem("theme")`
- If value is "dark", add `class="dark"` to `<html>`
- If value is "system" or absent, check `window.matchMedia("(prefers-color-scheme: dark)")` and set class accordingly
- If value is "light", ensure no `dark` class is present
- Script must execute synchronously before any rendering
- _Requirements: 5.1, 5.2, 3.2_
- [ ]* 6.2 Write unit test for FOUC-prevention logic
- Extract the inline script logic into a testable function
- Test that correct class is applied for each localStorage value and system preference combination
- _Requirements: 5.1_
- [x] 7. Final checkpoint
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- Unit tests validate specific examples and edge cases
- The project uses TypeScript throughout — all new files should be `.tsx`/`.ts`
- `next-themes` is already installed (`^0.3.0`), `fast-check` needs to be added as a dev dependency
- `lucide-react` is already available for Sun/Moon/Monitor icons
- shadcn/ui `DropdownMenu` and `Button` components already exist in `src/components/ui/`