diff --git a/MemoryGame/Pages/Home.razor b/MemoryGame/Pages/Home.razor
index 207d9ee..ca05a22 100644
--- a/MemoryGame/Pages/Home.razor
+++ b/MemoryGame/Pages/Home.razor
@@ -21,7 +21,7 @@
}
- else if (_currentSession.IsComplete)
+ else if (_currentSession.IsComplete && !_showStatistics)
{
Spiel beendet!
@@ -36,6 +36,68 @@
+
+
+ }
+ else if (_currentSession.IsComplete && _showStatistics)
+ {
+ var words = GameService.GetWords()
+ .OrderBy(w => w.CorrectCount + w.IncorrectCount == 0 ? 1 : 0)
+ .ThenBy(w => w.CorrectCount + w.IncorrectCount == 0
+ ? 0
+ : (double)w.CorrectCount / (w.CorrectCount + w.IncorrectCount))
+ .ToList();
+
+ var playedWords = words.Where(w => w.CorrectCount + w.IncorrectCount > 0).ToList();
+ var overallAccuracy = playedWords.Count == 0
+ ? 0
+ : (int)Math.Round(100.0 * playedWords.Sum(w => w.CorrectCount) / playedWords.Sum(w => w.CorrectCount + w.IncorrectCount));
+
+
+
Statistik
+
+
+ @playedWords.Count von @words.Count Wörtern geübt
+ @if (playedWords.Count > 0)
+ {
+ · @overallAccuracy% richtig
+ }
+
+
+
+ @foreach (var word in words)
+ {
+ var total = word.CorrectCount + word.IncorrectCount;
+ var accuracy = total == 0 ? 0 : (int)Math.Round(100.0 * word.CorrectCount / total);
+ var barClass = total == 0 ? "bar-unplayed" : accuracy >= 70 ? "bar-green" : accuracy >= 40 ? "bar-orange" : "bar-red";
+
+
+

+
+
@(total == 0 ? "—" : $"{accuracy}% richtig")
+
+ }
+
+
+ @if (_confirmReset)
+ {
+
+ Alle Statistiken wirklich zurücksetzen?
+
+
+
+ }
+ else
+ {
+
+ }
+
+
}
else
@@ -96,6 +158,8 @@
private bool _showFeedback;
private string _feedbackMessage = "";
private string _feedbackClass = "";
+ private bool _showStatistics;
+ private bool _confirmReset;
protected override async Task OnInitializedAsync()
{
@@ -144,5 +208,23 @@
StateHasChanged();
}
-}
+ private void ShowStatistics()
+ {
+ _showStatistics = true;
+ }
+
+ private void GoToStart()
+ {
+ _currentSession = null;
+ _showStatistics = false;
+ _confirmReset = false;
+ }
+
+ private async Task ConfirmReset()
+ {
+ await GameService.ResetStatisticsAsync();
+ _confirmReset = false;
+ StateHasChanged();
+ }
+}
diff --git a/MemoryGame/Services/GameService.cs b/MemoryGame/Services/GameService.cs
index 6a25581..c5aacb6 100644
--- a/MemoryGame/Services/GameService.cs
+++ b/MemoryGame/Services/GameService.cs
@@ -160,4 +160,23 @@ public sealed class GameService
await _localStorage.SaveStatisticsAsync(_statistics);
}
+
+ public IReadOnlyList GetWords()
+ {
+ return _statistics?.Words ?? [];
+ }
+
+ public async Task ResetStatisticsAsync()
+ {
+ if (_statistics == null)
+ return;
+
+ foreach (var word in _statistics.Words)
+ {
+ word.CorrectCount = 0;
+ word.IncorrectCount = 0;
+ }
+
+ await _localStorage.SaveStatisticsAsync(_statistics);
+ }
}
diff --git a/MemoryGame/wwwroot/css/app.css b/MemoryGame/wwwroot/css/app.css
index 0da938b..e4bfaf1 100644
--- a/MemoryGame/wwwroot/css/app.css
+++ b/MemoryGame/wwwroot/css/app.css
@@ -332,6 +332,169 @@ code {
padding: 20px;
}
+
+/* Statistics Screen */
+.stats-screen {
+ width: 100%;
+ max-width: 900px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 20px 0;
+}
+
+.stats-screen h1 {
+ font-size: 2.2rem;
+ color: #333;
+ margin: 0;
+}
+
+.stats-summary {
+ font-size: 1.2rem;
+ color: #666;
+}
+
+.stats-list {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.stats-row {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 12px;
+ background-color: #f8f9fa;
+ border: 2px solid #ddd;
+ border-radius: 10px;
+}
+
+.stats-thumbnail {
+ width: 80px;
+ height: 80px;
+ object-fit: contain;
+ flex-shrink: 0;
+}
+
+.stats-word-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.stats-word-name {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: #333;
+ text-transform: capitalize;
+}
+
+.stats-bar-container {
+ width: 100%;
+ height: 14px;
+ background-color: #e0e0e0;
+ border-radius: 7px;
+ overflow: hidden;
+}
+
+.stats-bar {
+ height: 100%;
+ border-radius: 7px;
+ transition: width 0.4s ease;
+ min-width: 0;
+}
+
+.bar-green { background-color: #4CAF50; }
+.bar-orange { background-color: #FF9800; }
+.bar-red { background-color: #f44336; }
+.bar-unplayed { background-color: #e0e0e0; width: 0; }
+
+.stats-accuracy {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: #555;
+ min-width: 110px;
+ text-align: right;
+ flex-shrink: 0;
+}
+
+.btn-stats {
+ font-size: 1.2rem;
+ padding: 12px 35px;
+ background-color: #2196F3;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.3s;
+ margin-top: 10px;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .btn-stats:hover {
+ background-color: #1976D2;
+ }
+}
+
+.btn-stats:active {
+ transform: scale(0.98);
+}
+
+.btn-reset {
+ font-size: 1.1rem;
+ padding: 10px 30px;
+ background-color: #f44336;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.3s;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .btn-reset:hover {
+ background-color: #d32f2f;
+ }
+}
+
+.reset-confirm {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+ justify-content: center;
+ font-size: 1.1rem;
+ color: #333;
+}
+
+.btn-confirm-yes {
+ padding: 10px 20px;
+ background-color: #f44336;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 1rem;
+}
+
+.btn-confirm-no {
+ padding: 10px 20px;
+ background-color: #9e9e9e;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 1rem;
+}
+
@media (max-width: 768px) {
.question-word {
font-size: 3rem;
@@ -441,4 +604,70 @@ code {
.result-value {
font-size: 5rem;
}
+
+ .stats-screen {
+ max-width: 2000px;
+ gap: 35px;
+ padding: 30px 0;
+ }
+
+ .stats-screen h1 {
+ font-size: 4rem;
+ }
+
+ .stats-summary {
+ font-size: 2rem;
+ }
+
+ .stats-list {
+ gap: 20px;
+ }
+
+ .stats-row {
+ gap: 25px;
+ padding: 20px;
+ }
+
+ .stats-thumbnail {
+ width: 140px;
+ height: 140px;
+ }
+
+ .stats-word-name {
+ font-size: 2.2rem;
+ }
+
+ .stats-bar-container {
+ height: 22px;
+ border-radius: 11px;
+ }
+
+ .stats-bar {
+ border-radius: 11px;
+ }
+
+ .stats-accuracy {
+ font-size: 2rem;
+ min-width: 200px;
+ }
+
+ .btn-stats {
+ font-size: 2.2rem;
+ padding: 22px 65px;
+ }
+
+ .btn-reset {
+ font-size: 2rem;
+ padding: 18px 55px;
+ }
+
+ .reset-confirm {
+ font-size: 2rem;
+ gap: 20px;
+ }
+
+ .btn-confirm-yes, .btn-confirm-no {
+ font-size: 1.8rem;
+ padding: 18px 40px;
+ }
}
diff --git a/docs/plans/2026-02-24-statistics-screen-design.md b/docs/plans/2026-02-24-statistics-screen-design.md
new file mode 100644
index 0000000..892ed30
--- /dev/null
+++ b/docs/plans/2026-02-24-statistics-screen-design.md
@@ -0,0 +1,59 @@
+# Statistics Screen Design
+
+**Date:** 2026-02-24
+**Feature:** Word mastery statistics screen
+**Branch:** feature/statistics-screen
+
+## Summary
+
+Add a statistics screen accessible from the results screen that shows word mastery progress for all vocabulary words. Users can see how well they know each word and reset their statistics.
+
+## Navigation
+
+- Results screen gets a "Statistik anzeigen" button
+- Tapping it switches to the new `Statistics` view state in `Home.razor`
+- A "Zurück zum Start" button returns to the start screen
+- No routing changes required
+
+## Architecture
+
+A new `Statistics` value is added to the existing view state enum in `Home.razor`. Statistics data is read directly from `GameService` (already loaded in memory). A new `ResetStatisticsAsync()` method is added to `GameService` that clears all `CorrectCount`/`IncorrectCount` values and persists to localStorage.
+
+## Statistics Screen Layout
+
+**Header:** Summary line showing total words played and overall accuracy.
+
+**Word list:** Scrollable list of all 20 words, sorted by ascending accuracy (worst first). Words never played appear at the bottom.
+
+Each row contains:
+- Image thumbnail (~80px SVG)
+- Word name (German)
+- Accuracy percentage ("75% richtig") or "—" if never played
+- Progress bar (fills green based on accuracy)
+
+**Footer:**
+- "Zurücksetzen" button (with confirmation) — resets all statistics
+- "Zurück zum Start" button
+
+## Mastery / Progress Bar Logic
+
+- Accuracy = `CorrectCount / (CorrectCount + IncorrectCount)`
+- Never played: shown last, bar at 0%, label "—"
+- Bar color:
+ - Green: ≥ 70% accuracy
+ - Orange: 40–69% accuracy
+ - Red: < 40% accuracy
+
+## Sorting
+
+Words sorted ascending by accuracy so the words needing most practice appear at the top. Never-played words are treated as 0% accuracy but sorted after all played words.
+
+## Reset Flow
+
+Tapping "Zurücksetzen" shows a browser `confirm()` dialog (or inline confirmation). On confirmation, `GameService.ResetStatisticsAsync()` zeros all counts and saves to localStorage. The list refreshes to show all words at 0 / "—".
+
+## Files to Change
+
+- `MemoryGame/Pages/Home.razor` — add Statistics view state and UI
+- `MemoryGame/Services/GameService.cs` — add `ResetStatisticsAsync()` method
+- `MemoryGame/wwwroot/css/app.css` — add statistics screen styles (including tablet breakpoint)
diff --git a/docs/plans/2026-02-24-statistics-screen.md b/docs/plans/2026-02-24-statistics-screen.md
new file mode 100644
index 0000000..528f50d
--- /dev/null
+++ b/docs/plans/2026-02-24-statistics-screen.md
@@ -0,0 +1,526 @@
+# Statistics Screen Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Add a statistics screen accessible from the results screen showing word mastery progress with accuracy percentage, progress bars, and a reset option.
+
+**Architecture:** Add a `_showStatistics` boolean flag to `Home.razor` that toggles a new view state within the existing `if/else` chain. Expose words from `GameService` via a new public method. Add `ResetStatisticsAsync()` to zero all counts.
+
+**Tech Stack:** Blazor WebAssembly (.NET 10), C#, CSS (no additional dependencies)
+
+---
+
+### Task 1: Expose statistics words from GameService
+
+**Files:**
+- Modify: `MemoryGame/Services/GameService.cs`
+
+**Step 1: Add public `GetWords()` method to GameService**
+
+Add this method after `UpdateStatisticsAsync`:
+
+```csharp
+public IReadOnlyList GetWords()
+{
+ return _statistics?.Words ?? [];
+}
+```
+
+**Step 2: Verify it compiles**
+
+```bash
+cd MemoryGame && dotnet build
+```
+Expected: Build succeeded, 0 errors.
+
+**Step 3: Commit**
+
+```bash
+git add MemoryGame/Services/GameService.cs
+git commit -m "feat: expose GetWords() on GameService for statistics screen"
+```
+
+---
+
+### Task 2: Add ResetStatisticsAsync to GameService
+
+**Files:**
+- Modify: `MemoryGame/Services/GameService.cs`
+
+**Step 1: Add `ResetStatisticsAsync()` method**
+
+Add this method after `GetWords()`:
+
+```csharp
+public async Task ResetStatisticsAsync()
+{
+ if (_statistics == null)
+ return;
+
+ foreach (var word in _statistics.Words)
+ {
+ word.CorrectCount = 0;
+ word.IncorrectCount = 0;
+ }
+
+ await _localStorage.SaveStatisticsAsync(_statistics);
+}
+```
+
+**Step 2: Verify it compiles**
+
+```bash
+cd MemoryGame && dotnet build
+```
+Expected: Build succeeded, 0 errors.
+
+**Step 3: Commit**
+
+```bash
+git add MemoryGame/Services/GameService.cs
+git commit -m "feat: add ResetStatisticsAsync to GameService"
+```
+
+---
+
+### Task 3: Add statistics view state and UI to Home.razor
+
+**Files:**
+- Modify: `MemoryGame/Pages/Home.razor`
+
+**Step 1: Add `_showStatistics` field**
+
+In the `@code` block, add after `_feedbackClass`:
+
+```csharp
+private bool _showStatistics;
+```
+
+**Step 2: Add "Statistik anzeigen" button to the results screen**
+
+Find the results screen section (ends with ``).
+
+Replace:
+```razor
+
+```
+With:
+```razor
+
+
+```
+
+**Step 3: Change results screen condition**
+
+Find:
+```razor
+else if (_currentSession.IsComplete)
+```
+Replace with:
+```razor
+else if (_currentSession.IsComplete && !_showStatistics)
+```
+
+**Step 4: Add statistics screen section**
+
+Add this new block after the results screen closing `}` and before the final `else`:
+
+```razor
+else if (_currentSession.IsComplete && _showStatistics)
+{
+ var words = GameService.GetWords()
+ .OrderBy(w => w.CorrectCount + w.IncorrectCount == 0 ? 1 : 0)
+ .ThenBy(w => w.CorrectCount + w.IncorrectCount == 0
+ ? 0
+ : (double)w.CorrectCount / (w.CorrectCount + w.IncorrectCount))
+ .ToList();
+
+ var playedWords = words.Where(w => w.CorrectCount + w.IncorrectCount > 0).ToList();
+ var overallAccuracy = playedWords.Count == 0
+ ? 0
+ : (int)Math.Round(100.0 * playedWords.Sum(w => w.CorrectCount) / playedWords.Sum(w => w.CorrectCount + w.IncorrectCount));
+
+
+
Statistik
+
+
+ @playedWords.Count von @words.Count Wörtern geübt
+ @if (playedWords.Count > 0)
+ {
+ · @overallAccuracy% richtig
+ }
+
+
+
+ @foreach (var word in words)
+ {
+ var total = word.CorrectCount + word.IncorrectCount;
+ var accuracy = total == 0 ? 0 : (int)Math.Round(100.0 * word.CorrectCount / total);
+ var barClass = total == 0 ? "bar-unplayed" : accuracy >= 70 ? "bar-green" : accuracy >= 40 ? "bar-orange" : "bar-red";
+
+
+

+
+
@(total == 0 ? "—" : $"{accuracy}% richtig")
+
+ }
+
+
+ @if (_confirmReset)
+ {
+
+ Alle Statistiken wirklich zurücksetzen?
+
+
+
+ }
+ else
+ {
+
+ }
+
+
+
+}
+```
+
+**Step 5: Add `_confirmReset` field and helper methods**
+
+In the `@code` block, add after `_showStatistics`:
+
+```csharp
+private bool _confirmReset;
+```
+
+Add these methods after `SelectAnswer`:
+
+```csharp
+private void ShowStatistics()
+{
+ _showStatistics = true;
+}
+
+private void GoToStart()
+{
+ _currentSession = null;
+ _showStatistics = false;
+ _confirmReset = false;
+}
+
+private async Task ConfirmReset()
+{
+ await GameService.ResetStatisticsAsync();
+ _confirmReset = false;
+ StateHasChanged();
+}
+```
+
+**Step 6: Verify it compiles**
+
+```bash
+cd MemoryGame && dotnet build
+```
+Expected: Build succeeded, 0 errors.
+
+**Step 7: Commit**
+
+```bash
+git add MemoryGame/Pages/Home.razor
+git commit -m "feat: add statistics screen view state to Home.razor"
+```
+
+---
+
+### Task 4: Add CSS styles for statistics screen
+
+**Files:**
+- Modify: `MemoryGame/wwwroot/css/app.css`
+
+**Step 1: Add base statistics styles**
+
+Append to the end of the base styles section (before the `@media (max-width: 768px)` block at line 335):
+
+```css
+/* Statistics Screen */
+.stats-screen {
+ width: 100%;
+ max-width: 900px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 20px 0;
+}
+
+.stats-screen h1 {
+ font-size: 2.2rem;
+ color: #333;
+ margin: 0;
+}
+
+.stats-summary {
+ font-size: 1.2rem;
+ color: #666;
+}
+
+.stats-list {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.stats-row {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 12px;
+ background-color: #f8f9fa;
+ border: 2px solid #ddd;
+ border-radius: 10px;
+}
+
+.stats-thumbnail {
+ width: 80px;
+ height: 80px;
+ object-fit: contain;
+ flex-shrink: 0;
+}
+
+.stats-word-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.stats-word-name {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: #333;
+ text-transform: capitalize;
+}
+
+.stats-bar-container {
+ width: 100%;
+ height: 14px;
+ background-color: #e0e0e0;
+ border-radius: 7px;
+ overflow: hidden;
+}
+
+.stats-bar {
+ height: 100%;
+ border-radius: 7px;
+ transition: width 0.4s ease;
+ min-width: 0;
+}
+
+.bar-green { background-color: #4CAF50; }
+.bar-orange { background-color: #FF9800; }
+.bar-red { background-color: #f44336; }
+.bar-unplayed { background-color: #e0e0e0; width: 0; }
+
+.stats-accuracy {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: #555;
+ min-width: 110px;
+ text-align: right;
+ flex-shrink: 0;
+}
+
+.btn-stats {
+ font-size: 1.2rem;
+ padding: 12px 35px;
+ background-color: #2196F3;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.3s;
+ margin-top: 10px;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .btn-stats:hover {
+ background-color: #1976D2;
+ }
+}
+
+.btn-stats:active {
+ transform: scale(0.98);
+}
+
+.btn-reset {
+ font-size: 1.1rem;
+ padding: 10px 30px;
+ background-color: #f44336;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.3s;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .btn-reset:hover {
+ background-color: #d32f2f;
+ }
+}
+
+.reset-confirm {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+ justify-content: center;
+ font-size: 1.1rem;
+ color: #333;
+}
+
+.btn-confirm-yes {
+ padding: 10px 20px;
+ background-color: #f44336;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 1rem;
+}
+
+.btn-confirm-no {
+ padding: 10px 20px;
+ background-color: #9e9e9e;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 1rem;
+}
+```
+
+**Step 2: Add tablet-scale overrides for statistics screen**
+
+Inside the existing `@media (min-width: 2500px) and (min-height: 1550px)` block, append:
+
+```css
+ .stats-screen {
+ max-width: 2000px;
+ gap: 35px;
+ padding: 30px 0;
+ }
+
+ .stats-screen h1 {
+ font-size: 4rem;
+ }
+
+ .stats-summary {
+ font-size: 2rem;
+ }
+
+ .stats-list {
+ gap: 20px;
+ }
+
+ .stats-row {
+ gap: 25px;
+ padding: 20px;
+ }
+
+ .stats-thumbnail {
+ width: 140px;
+ height: 140px;
+ }
+
+ .stats-word-name {
+ font-size: 2.2rem;
+ }
+
+ .stats-bar-container {
+ height: 22px;
+ border-radius: 11px;
+ }
+
+ .stats-bar {
+ border-radius: 11px;
+ }
+
+ .stats-accuracy {
+ font-size: 2rem;
+ min-width: 200px;
+ }
+
+ .btn-stats {
+ font-size: 2.2rem;
+ padding: 22px 65px;
+ }
+
+ .btn-reset {
+ font-size: 2rem;
+ padding: 18px 55px;
+ }
+
+ .reset-confirm {
+ font-size: 2rem;
+ gap: 20px;
+ }
+
+ .btn-confirm-yes, .btn-confirm-no {
+ font-size: 1.8rem;
+ padding: 18px 40px;
+ }
+```
+
+**Step 3: Verify it builds**
+
+```bash
+cd MemoryGame && dotnet build
+```
+Expected: Build succeeded, 0 errors.
+
+**Step 4: Commit**
+
+```bash
+git add MemoryGame/wwwroot/css/app.css
+git commit -m "feat: add statistics screen CSS styles"
+```
+
+---
+
+### Task 5: Manual smoke test
+
+**Step 1: Run the app**
+
+```bash
+cd MemoryGame && dotnet run
+```
+
+Open browser at the URL shown (typically `http://localhost:5267`).
+
+**Step 2: Verify the following flows**
+
+- [ ] Play a full 10-round game
+- [ ] Results screen shows "Statistik anzeigen" button
+- [ ] Tapping it shows the statistics screen with all 20 words
+- [ ] Played words show accuracy % and colored progress bar
+- [ ] Unplayed words show "—" and are sorted to the bottom
+- [ ] "Zurücksetzen" shows inline confirmation; cancelling hides it
+- [ ] Confirming reset zeroes all counts and shows "—" for all words
+- [ ] "Zurück zum Start" returns to the start screen
+- [ ] Starting a new game and playing updates statistics correctly
+
+**Step 3: Commit any fixes found during testing, then push**
+
+```bash
+git push -u origin feature/statistics-screen
+```