diff --git a/static/css/style.css b/static/css/style.css
index 260c600..94e5cb5 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -4,6 +4,38 @@ body {
transition: background-color 0.3s, color 0.3s;
}
+/* Cursor-Styling */
+.cursor-char {
+ position: relative;
+}
+
+.cursor-char::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ bottom: -3px;
+ width: 100%;
+ height: 3px;
+ background-color: #0056b3;
+ animation: blink 0.7s infinite;
+}
+
+@keyframes blink {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+}
+
+/* Dark Theme Anpassungen für Cursor */
+[data-bs-theme="dark"] .cursor-char::after {
+ background-color: #0cf;
+ height: 3px;
+ animation: blink 0.7s infinite;
+}
+
/* Dark Theme Styles */
[data-bs-theme="dark"] {
--bs-body-bg: #121212;
@@ -64,6 +96,10 @@ body {
color: #f87171; /* Helleres Rot für besseren Kontrast */
}
+[data-bs-theme="dark"] .incorrect-char-overlay {
+ color: #ffc107;
+}
+
[data-bs-theme="dark"] .highlight-space,
[data-bs-theme="dark"] .highlight-tab,
[data-bs-theme="dark"] .highlight-newline {
@@ -80,7 +116,7 @@ body {
background-color: #2d2d2d;
color: #e0e0e0;
border-color: #0d6efd;
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
}
[data-bs-theme="dark"] .lesson-link:hover {
@@ -103,14 +139,16 @@ body {
/* Fortschritt und Statistik Link Styling */
.progress-toggle,
-.statistics-link {
+.statistics-link,
+.input-toggle {
cursor: pointer;
transition: all 0.3s ease;
color: inherit;
}
.progress-toggle:hover,
-.statistics-link:hover {
+.statistics-link:hover,
+.input-toggle:hover {
color: #ffc107 !important;
text-shadow: 0 0 10px #ffc107, 0 0 20px #ffc107;
transform: scale(1.05);
@@ -137,14 +175,15 @@ body {
line-height: 1.6;
letter-spacing: 0.5px;
user-select: none; /* Verhindert Textauswahl */
- -webkit-user-select: none; /* Für Safari */
- -moz-user-select: none; /* Für Firefox */
- -ms-user-select: none; /* Für Internet Explorer/Edge */
+ user-select: none; /* Für Safari */
+ user-select: none; /* Für Firefox */
+ user-select: none; /* Für Internet Explorer/Edge */
white-space: pre-wrap;
overflow: auto;
- word-wrap: break-word;
+ overflow-wrap: break-word;
overflow-wrap: break-word;
word-break: normal;
+ padding-top: 2.5em; /* Vergrößerter Abstand am oberen Rand */
}
.text-line {
@@ -152,7 +191,7 @@ body {
width: 100%;
white-space: pre-wrap;
font-family: monospace;
- word-wrap: break-word;
+ overflow-wrap: break-word;
overflow-wrap: break-word;
word-break: normal;
hyphens: none;
@@ -172,7 +211,7 @@ body {
#user-input:focus {
border-color: #0d6efd;
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
}
.highlight-correct {
@@ -184,6 +223,23 @@ body {
color: #dc3545;
text-decoration: underline;
font-weight: bold;
+ position: relative;
+}
+
+.incorrect-char-container {
+ position: relative;
+}
+
+.incorrect-char-overlay {
+ position: absolute;
+ top: -1em;
+ left: 0;
+ font-size: 0.7em;
+ color: #ffc107;
+ background-color: transparent;
+ font-weight: bold;
+ z-index: 10;
+ white-space: nowrap;
}
.badge {
@@ -194,7 +250,7 @@ body {
.list-group-item {
border: none;
padding: 0.75rem 1rem;
- border-bottom: 1px solid rgba(0,0,0,.125);
+ border-bottom: 1px solid rgb(0 0 0 / 12.5%);
}
.list-group-item:last-child {
@@ -219,7 +275,7 @@ body {
.btn:hover {
transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+ box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
}
.btn:active {
@@ -255,6 +311,7 @@ body {
font-size: 1rem;
}
}
+
/* Theme-Toggle-Button */
#theme-toggle {
display: flex;
diff --git a/static/js/script.js b/static/js/script.js
index 0d02aea..fa3cd15 100644
--- a/static/js/script.js
+++ b/static/js/script.js
@@ -31,7 +31,6 @@ document.addEventListener('DOMContentLoaded', function() {
initializeTheme();
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
- const userInputElement = document.getElementById('user-input');
const targetTextElement = document.getElementById('target-text');
const nextBtn = document.getElementById('next-btn');
const endBtn = document.getElementById('end-btn');
@@ -52,6 +51,19 @@ document.addEventListener('DOMContentLoaded', function() {
let userInput = ''; // Gesamte Eingabe
let keyStrokeCount = 0; // Zähler für alle Tastaturanschläge (inkl. Backspace)
let lines = fullText.split('\n');
+ let userScrolled = false; // Flag für manuelles Scrollen
+
+ // Setze Fokus auf das Dokument, um Tastatureingaben zu erfassen
+ document.addEventListener('click', function() {
+ document.body.focus();
+ });
+ document.body.tabIndex = -1;
+ document.body.focus();
+
+ // Event-Listener für manuelles Scrollen
+ targetTextElement.addEventListener('scroll', function() {
+ userScrolled = true;
+ });
// Berechne die aktuelle Zeile basierend auf currentCharIndex
function getCurrentLineIndex() {
@@ -88,150 +100,138 @@ document.addEventListener('DOMContentLoaded', function() {
return false;
});
- // Fokus auf das Eingabefeld setzen
- userInputElement.focus();
-
- // Verhindere Copy & Paste für das Eingabefeld
- userInputElement.addEventListener('paste', function(event) {
- event.preventDefault();
- alert('Einfügen (Paste) ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
- });
-
- userInputElement.addEventListener('copy', function(event) {
- event.preventDefault();
- alert('Kopieren (Copy) ist nicht erlaubt!');
- });
-
- userInputElement.addEventListener('cut', function(event) {
- event.preventDefault();
- alert('Ausschneiden (Cut) ist nicht erlaubt!');
- });
-
- // Verhindere Drag & Drop
- userInputElement.addEventListener('drop', function(event) {
- event.preventDefault();
- alert('Drag & Drop ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
- });
-
- // Verhindere das Einfügen über das Kontextmenü (rechte Maustaste)
- userInputElement.addEventListener('contextmenu', function(event) {
- event.preventDefault();
- return false;
- });
-
- // Verhindere das Einfügen über Tastenkombinationen (Strg+V, Cmd+V, etc.)
- userInputElement.addEventListener('keydown', function(event) {
- // Strg+V oder Cmd+V
- if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
+ // Tastatureingaben direkt erfassen
+ document.addEventListener('keydown', function(event) {
+ // Verhindere Standardaktionen für Steuertasten, außer Backspace, Enter und Pfeiltasten
+ if (event.ctrlKey || event.metaKey) {
event.preventDefault();
- alert('Einfügen (Strg+V / Cmd+V) ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
- return false;
+ return;
}
- // Strg+C oder Cmd+C
- if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
+ // Behandle Pfeiltasten
+ if (event.key === 'ArrowLeft') {
event.preventDefault();
- alert('Kopieren (Strg+C / Cmd+C) ist nicht erlaubt!');
- return false;
- }
-
- // Strg+X oder Cmd+X
- if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
- event.preventDefault();
- alert('Ausschneiden (Strg+X / Cmd+X) ist nicht erlaubt!');
- return false;
- }
-
- // Strg+A oder Cmd+A (Selektion verhindern)
- if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
- event.preventDefault();
- alert('Selektion (Strg+A / Cmd+A) ist nicht erlaubt!');
- return false;
- }
-
- // Rechtsklick-Menü-Tasten (Context Menu key)
- if (event.key === 'ContextMenu') {
- event.preventDefault();
- return false;
- }
- });
-
- // Starte die Zeitmessung beim ersten Tastendruck und zähle Tastaturanschläge
- userInputElement.addEventListener('keydown', function(event) {
- const cursorPosition = userInputElement.selectionStart;
- const textLength = userInputElement.value.length;
- let shouldCountKey = false;
-
- // Zähle Tastaturanschläge für alle Tasten, die keine Steuertasten sind (inkl. Backspace)
- // Beachte: event.key für Backspace ist 'Backspace'
- if (event.key.length === 1 || event.key === 'Backspace') {
- shouldCountKey = true;
- }
- // Pfeiltasten: Nur zählen, wenn sich der Cursor tatsächlich bewegt
- else if (event.key === 'ArrowLeft') {
- if (cursorPosition > 0) {
- shouldCountKey = true;
- }
- } else if (event.key === 'ArrowRight') {
- if (cursorPosition < textLength) {
- shouldCountKey = true;
- }
- }
-
- if (shouldCountKey) {
+ keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
- keyStrokeCount++;
- // Aktualisiere die Statistik sofort, um die Änderung anzuzeigen
+ if (currentCharIndex > 0) {
+ currentCharIndex--;
+ updateTextDisplay();
+ }
updateStatistics();
+ return;
+ }
+
+ if (event.key === 'ArrowRight') {
+ event.preventDefault();
+ keyStrokeCount++;
+ if (!startTime) {
+ startTime = new Date();
+ }
+ if (currentCharIndex < userInput.length) {
+ currentCharIndex++;
+ updateTextDisplay();
+ }
+ updateStatistics();
+ return;
+ }
+
+ // Behandle Backspace
+ if (event.key === 'Backspace') {
+ event.preventDefault();
+ keyStrokeCount++;
+ if (!startTime) {
+ startTime = new Date();
+ }
+ if (userInput.length > 0 && currentCharIndex > 0) {
+ userInput = userInput.slice(0, currentCharIndex - 1) + userInput.slice(currentCharIndex);
+ currentCharIndex--;
+ updateTextDisplay();
+ updateStatistics();
+ } else {
+ updateStatistics();
+ }
+ return;
+ }
+
+ // Behandle Enter (falls im Text vorhanden)
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ if (currentCharIndex < fullText.length && fullText[currentCharIndex] === '\n') {
+ userInput = userInput.slice(0, currentCharIndex) + '\n' + userInput.slice(currentCharIndex);
+ currentCharIndex++;
+ keyStrokeCount++;
+ if (!startTime) {
+ startTime = new Date();
+ }
+ updateTextDisplay();
+ updateStatistics();
+ }
+ return;
+ }
+
+ // Ignoriere andere Steuertasten
+ if (event.key.length > 1 && event.key !== ' ') {
+ return;
+ }
+
+ // Behandle normale Zeichen
+ event.preventDefault();
+ if (currentCharIndex < fullText.length) {
+ // Wenn der Cursor innerhalb des bereits eingegebenen Textes steht, ersetze das Zeichen
+ // Andernfalls hänge das Zeichen an
+ if (currentCharIndex < userInput.length) {
+ // Ersetze das Zeichen an der aktuellen Position
+ userInput = userInput.slice(0, currentCharIndex) + event.key + userInput.slice(currentCharIndex + 1);
+ } else {
+ // Hänge das Zeichen am Ende an
+ userInput = userInput.slice(0, currentCharIndex) + event.key + userInput.slice(currentCharIndex);
+ }
+ currentCharIndex++;
+ keyStrokeCount++;
+ if (!startTime) {
+ startTime = new Date();
+ }
+ updateTextDisplay();
+ updateStatistics();
+
+ // Wenn die gesamte Lektion abgeschlossen ist
+ if (currentCharIndex === fullText.length) {
+ // Speichere die Statistiken für diese Lektion
+ const lessonIndex = window.currentLessonIndex;
+ const charsPerMinute = parseInt(speedElement.textContent);
+ const errorRate = parseFloat(errorRateElement.textContent);
+ const wpm = parseInt(wpmElement.textContent);
+
+ fetch('/save_lesson_statistics', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ lesson_index: lessonIndex,
+ chars_per_minute: charsPerMinute,
+ error_rate: errorRate,
+ wpm: wpm
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.status === 'success') {
+ alert('Lektion beendet! Statistiken wurden gespeichert.');
+ } else {
+ alert('Lektion beendet, aber Statistiken konnten nicht gespeichert werden.');
+ }
+ })
+ .catch(error => {
+ console.error('Fehler beim Speichern der Statistiken:', error);
+ alert('Lektion beendet! (Fehler beim Speichern der Statistiken)');
+ });
+ }
}
});
- // Aktualisiere die Statistik bei jedem Tastendruck (keyup)
- userInputElement.addEventListener('keyup', function() {
- userInput = this.value;
- currentCharIndex = userInput.length;
-
- // Aktualisiere die Anzeige bei jeder Änderung
- updateTextDisplay();
-
- // Wenn die gesamte Lektion abgeschlossen ist
- if (currentCharIndex === fullText.length) {
- // Speichere die Statistiken für diese Lektion
- const lessonIndex = window.currentLessonIndex;
- const charsPerMinute = parseInt(speedElement.textContent);
- const errorRate = parseFloat(errorRateElement.textContent);
- const wpm = parseInt(wpmElement.textContent);
-
- fetch('/save_lesson_statistics', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- lesson_index: lessonIndex,
- chars_per_minute: charsPerMinute,
- error_rate: errorRate,
- wpm: wpm
- })
- })
- .then(response => response.json())
- .then(data => {
- if (data.status === 'success') {
- alert('Lektion beendet! Statistiken wurden gespeichert.');
- } else {
- alert('Lektion beendet, aber Statistiken konnten nicht gespeichert werden.');
- }
- })
- .catch(error => {
- console.error('Fehler beim Speichern der Statistiken:', error);
- alert('Lektion beendet! (Fehler beim Speichern der Statistiken)');
- });
- }
-
- updateStatistics();
- });
// Funktion zum Aktualisieren der Lektionsmarkierung
function updateLessonHighlight(lessonIndex) {
@@ -278,10 +278,9 @@ document.addEventListener('DOMContentLoaded', function() {
startTime = null;
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
lines = fullText.split('\n');
+ userScrolled = false; // Reset Scroll-Flag
updateTextDisplay();
document.getElementById('lesson-title').textContent = data.title;
- userInputElement.value = '';
- userInputElement.focus();
updateStatistics();
// Aktualisiere die Markierung der aktuellen Lektion
updateLessonHighlight(lessonIndex);
@@ -314,11 +313,10 @@ document.addEventListener('DOMContentLoaded', function() {
startTime = null;
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
lines = fullText.split('\n');
+ userScrolled = false; // Reset Scroll-Flag
updateTextDisplay();
// Aktualisiere den Lektionstitel
document.getElementById('lesson-title').textContent = data.title;
- userInputElement.value = '';
- userInputElement.focus(); // Fokus zurück auf Eingabefeld
updateStatistics();
// Aktualisiere die Markierung der aktuellen Lektion
updateLessonHighlight(data.index);
@@ -383,15 +381,23 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Hilfsfunktion zum Hervorheben eines Zeichens
- function highlightChar(char, type) {
+ function highlightChar(char, type, isCursor = false, incorrectChar = null) {
+ let className = `highlight-${type}`;
+ if (isCursor) {
+ className += ' cursor-char';
+ }
if (char === ' ') {
- return `·`;
+ return `·`;
} else if (char === '\t') {
- return `→`;
+ return `→`;
} else if (char === '\n') {
- return '↵
';
+ return `↵
`;
} else {
- return `${char}`;
+ if (type === 'incorrect' && incorrectChar !== null) {
+ return `${char}`;
+ } else {
+ return `${char}`;
+ }
}
}
@@ -421,14 +427,15 @@ document.addEventListener('DOMContentLoaded', function() {
for (let i = 0; i < line.length; i++) {
const currentChar = line[i];
const absoluteIndex = lineStart + i;
+ const isCursor = (absoluteIndex === currentCharIndex);
if (absoluteIndex < userInput.length) {
if (userInput[absoluteIndex] === currentChar) {
- highlightedLine += highlightChar(currentChar, 'correct');
+ highlightedLine += highlightChar(currentChar, 'correct', isCursor);
} else {
- highlightedLine += highlightChar(currentChar, 'incorrect');
+ highlightedLine += highlightChar(currentChar, 'incorrect', isCursor, userInput[absoluteIndex]);
}
} else {
- highlightedLine += highlightChar(currentChar, 'neutral');
+ highlightedLine += highlightChar(currentChar, 'neutral', isCursor);
}
}
@@ -436,14 +443,15 @@ document.addEventListener('DOMContentLoaded', function() {
if (j < lines.length - 1) {
const newlinePos = lineStart + line.length;
const newlineChar = '\n';
+ const isCursor = (newlinePos === currentCharIndex);
if (newlinePos < userInput.length) {
if (userInput[newlinePos] === newlineChar) {
- highlightedLine += highlightChar(newlineChar, 'correct');
+ highlightedLine += highlightChar(newlineChar, 'correct', isCursor);
} else {
- highlightedLine += highlightChar(newlineChar, 'incorrect');
+ highlightedLine += highlightChar(newlineChar, 'incorrect', isCursor, userInput[newlinePos]);
}
} else {
- highlightedLine += highlightChar(newlineChar, 'neutral');
+ highlightedLine += highlightChar(newlineChar, 'neutral', isCursor);
}
}
@@ -453,10 +461,9 @@ document.addEventListener('DOMContentLoaded', function() {
targetTextElement.innerHTML = newHTML;
- // Scrolle zur aktuellen Zeile (oben)
- const currentLineElement = targetTextElement.querySelector('.current-line');
- if (currentLineElement) {
- currentLineElement.scrollIntoView({ block: 'start', behavior: 'smooth' });
+ // Behalte Scroll-Position bei, es sei denn der Benutzer hat nicht manuell gescrollt
+ if (!userScrolled) {
+ targetTextElement.scrollTop = 0;
}
}
@@ -527,6 +534,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
+
// Initialisiere Statistik und Textfarben
updateTextDisplay();
updateStatistics();
diff --git a/templates/index.html b/templates/index.html
index e8cc9d5..e9cfad0 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -49,17 +49,6 @@
window.currentCharIndex = 0;
-
-