Layout, Tool-Tipps und Feinheiten bei der Eingabe und der Trainingszeit-Darstellung verbessert

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jamulix 2025-10-28 23:21:54 +01:00
commit 10031abb05
13 changed files with 127 additions and 20 deletions

View file

@ -2,7 +2,16 @@
Willkommen beim Typewriter Trainer! Diese Hilfe erklärt alle Funktionen und gibt Tipps für effektives Tipptraining.
![Deutsche Tastatur](images/Quertz_10_Finger_Layout.jpg)
![Deutsche Tastatur](static/images/Quertz_10_Finger_Layout.jpg)
<!--
<div class="mt-4 text-center">
<img src="static/images/Quertz_10_Finger_Layout.jpg"
alt="Deutsches Tastatur-Layout"
style="max-width: 600px;">
<p class="mt-2 text-muted">Standard-Deutsches Tastaturlayout (QWERTZ)</p>
</div>
-->
---
@ -422,6 +431,8 @@ Der Text scrollt **automatisch**, sodass:
- 🚀 **Produktivität**: Bis zu 3x schneller tippen
- 🧠 **Weniger Ermüdung**: Ergonomischer für Ihre Hände
---
**Viel Erfolg beim Training!** 💪
---

View file

@ -1,6 +1,6 @@
# 🎹 Typewriter Tutor - Adaptiver Tipptrainer
# Typewriter Tutor - Adaptiver Tipptrainer
**Version:** v0.3.0 _(siehe [VERSION](VERSION) Datei)_
**Version:** v0.4.0 _(siehe [VERSION](VERSION) Datei)_
Ein moderner, webbasierter Tipptrainer mit **adaptivem Metronom** und umfangreichen Statistiken. Das Programm passt sich automatisch Ihrer Tippgeschwindigkeit an und hilft Ihnen, das Zehnfingerschreiben systematisch zu erlernen.
@ -369,7 +369,12 @@ Contributions sind willkommen! Bitte:
> **Hinweis:** Die aktuelle Versionsnummer wird zentral in der [VERSION](VERSION) Datei verwaltet.
### Version 0.3.0 (Aktuell)
### Version 0.4.0 (Aktuell)
- ✅ **Landing Page**: Begrüßungsseite mit Übersicht über Funktionen und direktem Zugang zum Training
- ✅ **Theme-Integration**: Dark/Light Mode Toggle auch auf der Landing Page
- ✅ **Verbesserte Navigation**: Zentrale Einstiegsseite für neue Nutzer
### Version 0.3.0
- ✅ **Maximale Metronom-Geschwindigkeit**: Konfigurierbar (Standard: 100 BPM) zur Vermeidung von Überforderung
- ✅ **Präzisions-Anzeige**: Positive Darstellung (Präzision statt Fehlerrate) für motivierendes Feedback
- ✅ **Persistente Settings**: Einstellungen werden in Datenbank gespeichert (nicht nur Session)

View file

@ -1 +1 @@
0.4.0
v0.4.5

View file

@ -120,6 +120,22 @@ body {
color: #a0a0a0; /* Hellgrau für Sonderzeichen */
}
/* Korrekt eingegebene Whitespace-Zeichen in dezent grau-grün (Dark Mode) */
[data-bs-theme="dark"] .highlight-correct.highlight-space,
[data-bs-theme="dark"] .highlight-correct.highlight-tab,
[data-bs-theme="dark"] .highlight-correct.highlight-newline {
color: #7fc49a;
font-weight: normal;
}
/* Falsch eingegebene Whitespace-Zeichen in rot (Dark Mode) */
[data-bs-theme="dark"] .highlight-incorrect.highlight-space,
[data-bs-theme="dark"] .highlight-incorrect.highlight-tab,
[data-bs-theme="dark"] .highlight-incorrect.highlight-newline {
color: #f87171;
font-weight: normal;
}
[data-bs-theme="dark"] .form-control {
background-color: #2d2d2d;
color: #e0e0e0;
@ -309,13 +325,29 @@ body {
max-width: 900px;
}
.highlight-space,
.highlight-tab,
.highlight-space,
.highlight-tab,
.highlight-newline {
color: #888;
font-weight: normal;
}
/* Korrekt eingegebene Whitespace-Zeichen in dezent grau-grün */
.highlight-correct.highlight-space,
.highlight-correct.highlight-tab,
.highlight-correct.highlight-newline {
color: #6ca87f;
font-weight: normal;
}
/* Falsch eingegebene Whitespace-Zeichen in rot */
.highlight-incorrect.highlight-space,
.highlight-incorrect.highlight-tab,
.highlight-incorrect.highlight-newline {
color: #dc3545;
font-weight: normal;
}
@media (max-width: 768px) {
.container {
padding: 0 10px;

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View file

@ -33,6 +33,9 @@ class TypewriterApp {
this.metronomeMode = 'automatic';
this.targetErrorRate = 5;
this.speedDisplay = 'zpm';
// Time display update interval
this.timeDisplayInterval = null;
}
/**
@ -70,6 +73,9 @@ class TypewriterApp {
this.metronomePlayer.start();
}
// Start time display update interval (updates every second)
this.startTimeDisplayUpdate();
console.log('Typewriter Tutor initialized successfully');
}
@ -315,6 +321,35 @@ class TypewriterApp {
}
}
/**
* Start the time display update interval
* Updates every second to show running time
*/
startTimeDisplayUpdate() {
// Clear any existing interval
if (this.timeDisplayInterval) {
clearInterval(this.timeDisplayInterval);
}
// Update every second
this.timeDisplayInterval = setInterval(() => {
// Only update if not paused
if (!this.sessionManager.isPaused) {
this.updateTrainingTimeDisplay();
}
}, 1000);
}
/**
* Stop the time display update interval
*/
stopTimeDisplayUpdate() {
if (this.timeDisplayInterval) {
clearInterval(this.timeDisplayInterval);
this.timeDisplayInterval = null;
}
}
/**
* Update text display
* @param {boolean} isKeystroke - Whether this is triggered by a keystroke
@ -490,6 +525,9 @@ class TypewriterApp {
* End session
*/
async endSession() {
// Stop time display update
this.stopTimeDisplayUpdate();
try {
const settings = {
metronome_enabled: this.metronomePlayer.enabled,

View file

@ -81,11 +81,26 @@ export class TextDisplayManager {
const escapedChar = this.escapeHtml(char);
if (char === ' ') {
return `<span class="${className} highlight-space">·</span>`;
if (type === 'incorrect' && incorrectChar !== null) {
const escapedIncorrectChar = this.escapeHtml(incorrectChar);
return `<span class="${className} highlight-space incorrect-char-container">·<span class="incorrect-char-overlay">${escapedIncorrectChar}</span></span>`;
} else {
return `<span class="${className} highlight-space">·</span>`;
}
} else if (char === '\t') {
return `<span class="${className} highlight-tab">→</span>`;
if (type === 'incorrect' && incorrectChar !== null) {
const escapedIncorrectChar = this.escapeHtml(incorrectChar);
return `<span class="${className} highlight-tab incorrect-char-container">→<span class="incorrect-char-overlay">${escapedIncorrectChar}</span></span>`;
} else {
return `<span class="${className} highlight-tab">→</span>`;
}
} else if (char === '\n') {
return `<span class="${className} highlight-newline">↵</span><br>`;
if (type === 'incorrect' && incorrectChar !== null) {
const escapedIncorrectChar = this.escapeHtml(incorrectChar);
return `<span class="${className} highlight-newline incorrect-char-container">↵<span class="incorrect-char-overlay">${escapedIncorrectChar}</span></span><br>`;
} else {
return `<span class="${className} highlight-newline">↵</span><br>`;
}
} else {
if (type === 'incorrect' && incorrectChar !== null) {
const escapedIncorrectChar = this.escapeHtml(incorrectChar);

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hilfe - Typewriter Trainer</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" sizes="any">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
@ -99,7 +99,7 @@
<div class="card shadow">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h2 class="mb-0">Anleitung</h2>
<div id="theme-toggle" style="cursor: pointer; position: relative;">
<div id="theme-toggle" style="cursor: pointer; position: relative;" title="Theme wechseln">
<i data-lucide="sun" id="theme-toggle-sun" style="width: 24px; height: 24px;"></i>
<i data-lucide="moon" id="theme-toggle-moon" style="width: 24px; height: 24px; display: none;"></i>
</div>
@ -112,6 +112,7 @@
{{ hilfe_html|safe }}
<!-- Deutsche Tastatur Grafik -->
<!--
<div class="mt-4 text-center">
<h3>Deutsches Tastatur-Layout</h3>
<img src="{{ url_for('static', filename='images/Quertz_10_Finger_Layout.jpg') }}"
@ -119,7 +120,8 @@
class="img-fluid mt-2"
style="max-width: 600px;">
<p class="mt-2 text-muted">Standard-Deutsches Tastaturlayout (QWERTZ)</p>
</div>
</div>
-->
</div>
</div>
</div>

View file

@ -23,25 +23,27 @@
<button id="settings-toggle"
style="width: 24px; height: 24px; cursor: pointer; border: none; background: none; padding: 0; color: white;"
aria-label="Einstellungen öffnen"
title="Einstellungen"
type="button">
<i data-lucide="settings" style="width: 24px; height: 24px;" aria-hidden="true"></i>
</button>
<button id="theme-toggle"
style="width: 24px; height: 24px; cursor: pointer; position: relative; border: none; background: none; padding: 0; color: white;"
aria-label="Zwischen hellem und dunklem Design wechseln"
title="Theme wechseln"
type="button">
<i data-lucide="sun" id="theme-toggle-sun" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;" aria-hidden="true"></i>
<i data-lucide="moon" id="theme-toggle-moon" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;" aria-hidden="true"></i>
</button>
<div id="lock-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
<div id="lock-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;" title="Fehler blockiert an/aus">
<i data-lucide="lock" id="lock-closed" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
<i data-lucide="unlock" id="lock-open" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
</div>
<div id="volume-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
<div id="volume-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;" title="Metronom-Ton an/aus">
<i data-lucide="volume-2" id="volume-on" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
<i data-lucide="volume-x" id="volume-off" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
</div>
<div id="play-pause-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
<div id="play-pause-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;" title="Eingabe pausieren/fortsetzen">
<i data-lucide="pause" id="play-pause-pause" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
<i data-lucide="play" id="play-pause-play" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
</div>

View file

@ -20,7 +20,7 @@
<div class="d-flex justify-content-between align-items-center">
<h2 class="mb-0">Einstellungen</h2>
<div class="d-flex align-items-center gap-2">
<div id="theme-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
<div id="theme-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;" title="Theme wechseln">
<i data-lucide="sun" id="theme-toggle-sun" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
<i data-lucide="moon" id="theme-toggle-moon" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
</div>

View file

@ -21,11 +21,11 @@
<div class="d-flex justify-content-between align-items-center">
<h2 class="mb-0">Lektionsstatistik</h2>
<div class="d-flex align-items-center gap-2">
<div id="theme-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
<div id="theme-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;" title="Theme wechseln">
<i data-lucide="sun" id="theme-toggle-sun" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
<i data-lucide="moon" id="theme-toggle-moon" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
</div>
<div onclick="window.location.href='/train'" style="width: 24px; height: 24px; cursor: pointer; display: flex; align-items: center; justify-content: center;" aria-label="Zurück zum Training">
<div onclick="window.location.href='/train'" style="width: 24px; height: 24px; cursor: pointer; display: flex; align-items: center; justify-content: center;" aria-label="Zurück zum Training" title="Zurück zum Training">
<i data-lucide="undo-2" style="width: 24px; height: 24px;"></i>
</div>
</div>

View file

@ -79,7 +79,9 @@
<!-- Logo -->
<div class="logo-icon">
🎹
<img src="{{ url_for('static', filename='images/typing_hands.jpg') }}"
alt="Typing Hands"
style="max-width: 200px; height: auto; border-radius: 8px;">
</div>
<!-- Title -->