Files
mindflux/src/main/resources/templates/progress.html
T
AliceDarkest 1bfbd53062 AP6 DSGVO: Footer-Rollout, Datenschutz/Impressum, Datenexport, NeuralTest-Entfernung
- Footer-Fragment (fragments/layout :: footer) ueberall ausgerollt: 24 duplizierte
  Inline-Footer ersetzt, 11 bisher footerlose Seiten (Fehler-/Auth-Seiten + 8
  Spielseiten) ergaenzt -> Rechtslinks von jeder Seite erreichbar. Support/Feedback
  von href="#" auf mailto-Platzhalter umgestellt.
- Datenschutzerklaerung vervollstaendigt: NeuralNet-Messaging als Verarbeitungs-
  taetigkeit (Inhalte, Sender/Empfaenger, Lesebestaetigung, Loeschung via Kaskade),
  E-Mail-Token (SHA-256, 24h) + SMTP ueber mail.sysnaptik.de, konkrete Speicher-
  fristen, korrekte Drittland-/CDN-Aussage.
- Impressum strukturiert: Pflichtfelder nach § 5 DDG (Name, Anschrift, Vertretung,
  Kontakt, MStV) als auffaellige Platzhalter (--warn).
- Datenexport (Art. 20): GET /profile/export -> ProfileExportService sammelt
  Stammdaten, Scores, Achievements und NeuralNet-Daten (ohne Passwort) als
  JSON-Download; drei Repo-Query-Methoden ergaenzt.
- V22-Migration: Demo-User NeuralTest entfernt (NeuralNet-Daten per Kaskade).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFxcjtc9HNP4VECT6Ur1tE
2026-07-21 00:37:38 +02:00

156 lines
6.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de" data-theme="dark" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fortschritt – SysnaptiK Neural</title>
<link rel="stylesheet" href="/css/theme.css?v=4">
<script src="/js/theme.js?v=5"></script>
<script src="/js/vendor/chart.umd.min.js?v=4.4.7"></script>
<style>
.mf-progress-summary { display:flex; flex-wrap:wrap; align-items:center; gap:2rem; }
.mf-circular { position:relative; width:180px; height:180px; flex:0 0 auto; }
.mf-circular canvas { width:180px !important; height:180px !important; }
.mf-percentage { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--heading); font-size:1.6rem; font-weight:800; text-shadow:0 0 14px var(--glow-s); }
.mf-progress-info { flex:1; min-width:220px; }
.mf-progress-info p { color:var(--text); margin:.35rem 0; font-size:.92rem; }
.mf-progress-info strong { color:var(--heading); }
.mf-game-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:1rem; }
.mf-game-card { background:var(--surface-2); border:1px solid var(--border); border-radius:12px; padding:1rem; }
.mf-game-card h3 { color:var(--heading); font-size:1.05rem; margin-bottom:.5rem; }
.mf-game-card p { color:var(--muted); font-size:.84rem; margin:.25rem 0; }
.mf-game-card p span { color:var(--text); }
.mf-section-title { color:var(--accent); font-size:.78rem; letter-spacing:.12em; text-transform:uppercase; margin-bottom:.9rem; }
.mf-footer { text-align:center; color:var(--muted); font-size:.82rem; padding:1.6rem 1rem; }
.mf-footer a { color:var(--muted); text-decoration:none; margin:0 .15rem; }
.mf-footer a:hover { color:var(--accent); }
</style>
</head>
<body>
<div class="mf-theme-float"><button class="mf-theme"></button></div>
<div class="mf-container">
<div class="mf-page-head">
<a th:href="@{/dashboard}" class="mf-back">⟵ Zurück zum Dashboard</a>
</div>
<span class="mf-eyebrow">Deine Statistiken</span>
<h1 style="color:var(--heading); font-size:2.1rem; font-weight:800; margin:.3rem 0 .25rem;">Deine Statistiken</h1>
<p style="color:var(--muted); margin-bottom:1.4rem;">Bleib am Ball – jeder Schritt zählt!</p>
<nav th:replace="~{fragments/layout :: profileTabs('statistiken')}"></nav>
<!-- Gesamtfortschritt -->
<section class="mf-panel accent" style="margin-bottom:1.2rem;">
<div class="mf-progress-summary">
<div class="mf-circular">
<canvas id="progressCircle"></canvas>
<p class="mf-percentage" th:text="${xpPercentage + '%'}">78%</p>
</div>
<div class="mf-progress-info">
<p><strong>Erfasste Spielaktionen:</strong> <span th:text="${correctAnswers}">432</span></p>
<p><strong>Level (XP):</strong> <span th:text="${userLevel}">5</span></p>
<div class="mf-bar" style="margin:.6rem 0 .4rem;"><i th:style="'width:' + ${xpPercentage} + '%'"></i></div>
<p><span th:text="${currentXp}">2400</span> / <span th:text="${xpForNextLevel}">3000</span> XP</p>
</div>
</div>
</section>
<!-- Tatsächlich gemessene Spielzeit pro Woche -->
<section class="mf-panel" style="margin-bottom:1.2rem;">
<h2 class="mf-section-title">Spielzeit – Diese Woche</h2>
<p style="color:var(--muted);font-size:.82rem;margin:-.35rem 0 .9rem;">Nur die Dauer abgeschlossener, zeitgemessener Runden.</p>
<canvas id="learnTimeChart" width="600" height="180"></canvas>
</section>
<!-- Echte Spielstatistiken -->
<section class="mf-panel" th:if="${gameProgress != null}" style="margin-bottom:1.2rem;">
<h2 class="mf-section-title">Spiel-Statistiken</h2>
<div class="mf-game-grid">
<div class="mf-game-card" th:each="game : ${gameProgress}">
<h3 th:text="${game.name}">Hangman</h3>
<p>Beste Punktzahl: <span th:text="${game.highscore}">1200</span></p>
<p>Gespielt: <span th:text="${game.plays}">15</span>×</p>
<p>Letzter Zugriff: <span th:text="${game.lastPlayed}">14.04.2025</span></p>
</div>
</div>
</section>
</div>
<!-- Diagramme -->
<script th:inline="javascript">
// theme.js setzt die Plattformklasse noch im head. Die Charts entstehen danach,
// damit sie garantiert den bereits aktiven Neural-Akzent lesen.
window.addEventListener('DOMContentLoaded', () => {
// Die Plattform-Tokens liegen auf html.sn-platform > body. Von dort lesen, damit die
// Diagramme nicht auf die alte globale Akzentfarbe zurückfallen.
const css = getComputedStyle(document.body);
const accent = css.getPropertyValue('--accent').trim() || '#2bd96a';
const track = css.getPropertyValue('--surface-2').trim() || '#2c2c2c';
const tick = css.getPropertyValue('--text').trim() || '#f1f1f1';
const grid = css.getPropertyValue('--border').trim() || '#333';
const xpPercentage = /*[[${xpPercentage}]]*/ 0;
const weeklyMinutes = /*[[${weekly.minutes}]]*/ [0, 0, 0, 0, 0, 0, 0];
// Kreisdiagramm
const ctx = document.getElementById('progressCircle').getContext('2d');
new Chart(ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [xpPercentage, 100 - xpPercentage],
backgroundColor: [accent, track],
borderWidth: 0
}]
},
options: {
cutout: '75%',
plugins: {
legend: { display: false }
}
}
});
// Spielzeit-Balkendiagramm
const learnCtx = document.getElementById('learnTimeChart').getContext('2d');
new Chart(learnCtx, {
type: 'bar',
data: {
labels: ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'],
datasets: [{
label: 'Spielzeit (Minuten)',
data: weeklyMinutes,
backgroundColor: accent,
borderRadius: 6
}]
},
options: {
plugins: {
legend: {
labels: { color: tick }
}
},
scales: {
x: {
ticks: { color: tick },
grid: { color: grid }
},
y: {
beginAtZero: true,
ticks: { color: tick },
grid: { color: grid }
}
}
}
});
});
</script>
<!-- Footer -->
<footer th:replace="~{fragments/layout :: footer}"></footer>
</body>
</html>