From 37b77fdb4023ba0c4c1dac048c66be5e1194259c Mon Sep 17 00:00:00 2001 From: Lukas Cremer Date: Tue, 20 Jan 2026 19:48:07 +0100 Subject: [PATCH] initial page --- README.md | 89 +++++++++++++++ package.json | 17 +++ public/app.js | 202 ++++++++++++++++++++++++++++++++++ public/index.html | 39 +++++++ public/style.css | 274 ++++++++++++++++++++++++++++++++++++++++++++++ server.js | 74 +++++++++++++ tasks.json | 92 ++++++++++++++++ test-local.sh | 27 +++++ 8 files changed, 814 insertions(+) create mode 100644 README.md create mode 100644 package.json create mode 100644 public/app.js create mode 100644 public/index.html create mode 100644 public/style.css create mode 100644 server.js create mode 100644 tasks.json create mode 100755 test-local.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1b0e0e --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# MathQuest - Mathe-Lernseite für Kinder + +Eine einfache und spaßige Mathe-Lernseite mit Quest-Punktesystem für Kinder. + +## Features + +- 🎯 Einfache Mathe-Aufgaben (Addition, Subtraktion, Multiplikation) +- ⭐ Quest-Punktesystem - jede Aufgabe gibt Punkte +- 📊 Fortschrittsbalken zur Visualisierung der Gesamtpunktzahl +- 💾 Persistente Speicherung der Punkte (JSON-Datei) +- 🎨 Kindgerechtes, buntes Design + +## Installation + +1. Node.js installieren (falls noch nicht vorhanden) + +2. Abhängigkeiten installieren: +```bash +npm install +``` + +## Starten + +```bash +npm start +``` + +Die Seite läuft dann auf `http://localhost:3000` + +## Auf VPS deployen + +1. Code auf den VPS hochladen +2. `npm install` ausführen +3. Mit PM2 oder systemd als Service laufen lassen: + +### Mit PM2: +```bash +npm install -g pm2 +pm2 start server.js --name mathquest +pm2 save +pm2 startup +``` + +### Mit systemd: +Erstelle eine Service-Datei `/etc/systemd/system/mathquest.service`: +```ini +[Unit] +Description=MathQuest Server +After=network.target + +[Service] +Type=simple +User=dein-user +WorkingDirectory=/pfad/zum/mathquest +ExecStart=/usr/bin/node server.js +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Dann: +```bash +sudo systemctl enable mathquest +sudo systemctl start mathquest +``` + +4. Nginx als Reverse Proxy konfigurieren (optional) + +## Architektur + +- **tasks.json**: Enthält alle Mathe-Aufgaben (Fragen, Antworten, Punkte) + - Wird beim Prüfen von Antworten aktualisiert + - Speichert `userAnswer` und `isCorrect` für jede Aufgabe +- **Backend**: Prüft Antworten und speichert sie direkt in der Task +- **Frontend**: Lädt Aufgaben, berechnet Gesamtpunktzahl aus korrekt beantworteten Aufgaben + +## API Endpoints + +- `GET /api/tasks` - Lädt alle Aufgaben (inkl. gespeicherte Antworten) +- `POST /api/check-answer` - Prüft eine Antwort (taskId, answer) + - Speichert `userAnswer` und `isCorrect` in der Task + - Gibt zurück: `correct`, `correctAnswer`, `points` + +## Anpassungen + +- Aufgaben können in `tasks.json` angepasst werden +- Design kann in `public/style.css` angepasst werden +- Port kann über Umgebungsvariable `PORT` geändert werden diff --git a/package.json b/package.json new file mode 100644 index 0000000..fe66def --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "mathquest", + "version": "1.0.0", + "description": "Mathe-Lernseite für Kinder mit Quest-Punkten", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "node server.js" + }, + "keywords": ["math", "learning", "kids"], + "author": "", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5" + } +} diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..f387c43 --- /dev/null +++ b/public/app.js @@ -0,0 +1,202 @@ +// API Base URL +const API_BASE = ''; + +// Globale Variablen +let totalPoints = 0; +let tasks = []; + +// Initialisiere die App +async function init() { + await loadTasks(); + calculateTotalPoints(); + renderTasks(); + updateProgress(); +} + +// Berechne Gesamtpunktzahl aus abgeschlossenen Aufgaben +function calculateTotalPoints() { + totalPoints = tasks + .filter(task => task.isCorrect === true) + .reduce((sum, task) => sum + (task.points || 0), 0); + updatePointsDisplay(); +} + +// Lade Aufgaben vom Server +async function loadTasks() { + try { + const response = await fetch(`${API_BASE}/api/tasks`); + const data = await response.json(); + tasks = data.tasks || []; + } catch (error) { + console.error('Fehler beim Laden der Aufgaben:', error); + tasks = []; + } +} + +// Rendere Aufgaben +function renderTasks() { + const container = document.getElementById('taskContainer'); + container.innerHTML = ''; + + tasks.forEach(task => { + const taskCard = document.createElement('div'); + taskCard.className = 'task-card'; + taskCard.id = `task-${task.id}`; + + const isCompleted = task.isCorrect === true; + const hasAnswer = task.userAnswer !== undefined; + + taskCard.innerHTML = ` +
+
Aufgabe
+
⭐ ${task.points} Punkte
+
+
${task.question}
+
+ + +
+
+ ${isCompleted ? '✅ Richtig beantwortet!' : hasAnswer && !isCompleted ? '❌ Falsch - versuche es nochmal!' : ''} +
+ `; + + if (isCompleted) { + const statusEl = taskCard.querySelector('.task-status'); + statusEl.className = 'task-status success'; + } else if (hasAnswer && !isCompleted) { + const statusEl = taskCard.querySelector('.task-status'); + statusEl.className = 'task-status error'; + } + + container.appendChild(taskCard); + }); +} + +// Prüfe Antwort +async function checkAnswer(taskId) { + const task = tasks.find(t => t.id === taskId); + if (!task) return; + + const input = document.getElementById(`input-${taskId}`); + const status = document.getElementById(`status-${taskId}`); + const button = input.nextElementSibling; + + const userAnswer = parseInt(input.value); + + if (isNaN(userAnswer)) { + status.textContent = 'Bitte gib eine Zahl ein!'; + status.className = 'task-status error'; + return; + } + + // Prüfe ob Aufgabe bereits korrekt beantwortet wurde + if (task.isCorrect === true) { + status.textContent = '✅ Diese Aufgabe wurde bereits richtig beantwortet!'; + status.className = 'task-status completed'; + return; + } + + // Sende Antwort an Backend zur Prüfung + try { + const response = await fetch(`${API_BASE}/api/check-answer`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ taskId, answer: userAnswer }) + }); + + const data = await response.json(); + + // Lade Tasks neu, um die gespeicherte Antwort zu erhalten + await loadTasks(); + calculateTotalPoints(); + + if (data.correct) { + status.textContent = '🎉 Richtig! Super gemacht!'; + status.className = 'task-status success'; + + // Deaktiviere Input und Button + input.disabled = true; + button.disabled = true; + + // Zeige Erfolgsnachricht + showMessage(`🎉 ${data.points} Quest-Punkte verdient!`, 'success'); + } else { + status.textContent = '❌ Nicht ganz richtig. Versuch es nochmal!'; + status.className = 'task-status error'; + input.focus(); + } + + // Rendere Tasks neu, um den aktuellen Status anzuzeigen + renderTasks(); + updateProgress(); + } catch (error) { + console.error('Fehler beim Prüfen der Antwort:', error); + status.textContent = 'Fehler beim Prüfen. Bitte versuche es erneut.'; + status.className = 'task-status error'; + } +} + +// Aktualisiere Punkte-Anzeige +function updatePointsDisplay() { + document.getElementById('totalPoints').textContent = totalPoints; +} + +// Berechne maximale mögliche Punkte (Summe aller Tasks) +function calculateMaxPoints() { + return tasks.reduce((sum, task) => sum + (task.points || 0), 0); +} + +// Aktualisiere Progress-Balken +function updateProgress() { + const maxPoints = calculateMaxPoints(); + const percentage = maxPoints > 0 ? Math.min((totalPoints / maxPoints) * 100, 100) : 0; + + const progressBar = document.getElementById('progressBar'); + const progressText = document.getElementById('progressText'); + + progressBar.style.width = `${percentage}%`; + progressText.textContent = `${totalPoints} / ${maxPoints}`; +} + +// Zeige Nachricht +function showMessage(text, type) { + const message = document.getElementById('message'); + message.textContent = text; + message.className = `message ${type} show`; + + setTimeout(() => { + message.classList.remove('show'); + }, 3000); +} + +// Enter-Taste für Input-Felder +document.addEventListener('DOMContentLoaded', () => { + init(); + + // Event Listener für Enter-Taste + document.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + const input = document.activeElement; + if (input && input.classList.contains('task-input')) { + const taskId = input.id.replace('input-', ''); + checkAnswer(taskId); + } + } + }); +}); diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..c39f9b2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,39 @@ + + + + + + MathQuest - Mathe lernen mit Quest-Punkten! + + + +
+
+

🌟 MathQuest 🌟

+
+
Quest-Punkte
+
0
+
+
+ +
+
Fortschritt
+
+
+
+
0 / 100
+
+ +
+

Mathe-Aufgaben

+
+ +
+
+ +
+
+ + + + diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..de7ade1 --- /dev/null +++ b/public/style.css @@ -0,0 +1,274 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', cursive, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding: 20px; + color: #333; +} + +.container { + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 20px; + padding: 30px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); +} + +header { + text-align: center; + margin-bottom: 30px; +} + +h1 { + font-size: 2.5em; + color: #667eea; + margin-bottom: 20px; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); +} + +.points-display { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + padding: 20px; + border-radius: 15px; + color: white; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.points-label { + font-size: 1.2em; + margin-bottom: 10px; +} + +.points-value { + font-size: 3em; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); +} + +.progress-section { + margin-bottom: 30px; + text-align: center; +} + +.progress-label { + font-size: 1.3em; + color: #667eea; + margin-bottom: 10px; + font-weight: bold; +} + +.progress-bar-container { + width: 100%; + height: 30px; + background: #e0e0e0; + border-radius: 15px; + overflow: hidden; + margin-bottom: 10px; + box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%); + width: 0%; + transition: width 0.5s ease; + border-radius: 15px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +.progress-text { + font-size: 1.1em; + color: #666; + font-weight: bold; +} + +.task-section { + margin-top: 30px; +} + +.task-section h2 { + color: #667eea; + margin-bottom: 20px; + text-align: center; + font-size: 1.8em; +} + +.task-container { + display: grid; + gap: 20px; +} + +.task-card { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + padding: 25px; + border-radius: 15px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.task-card:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); +} + +.task-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.task-title { + font-size: 1.3em; + font-weight: bold; + color: #333; +} + +.task-points { + background: #ffd700; + color: #333; + padding: 5px 15px; + border-radius: 20px; + font-weight: bold; + font-size: 1.1em; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +.task-question { + font-size: 1.5em; + text-align: center; + margin: 20px 0; + color: #333; + font-weight: bold; +} + +.task-input-group { + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + margin-bottom: 15px; +} + +.task-input { + font-size: 1.5em; + padding: 10px 15px; + border: 3px solid #667eea; + border-radius: 10px; + width: 120px; + text-align: center; + font-family: 'Comic Sans MS', cursive; + font-weight: bold; +} + +.task-input:focus { + outline: none; + border-color: #764ba2; + box-shadow: 0 0 10px rgba(102, 126, 234, 0.3); +} + +.task-button { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px 25px; + font-size: 1.2em; + border-radius: 10px; + cursor: pointer; + font-family: 'Comic Sans MS', cursive; + font-weight: bold; + transition: transform 0.2s, box-shadow 0.2s; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); +} + +.task-button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.3); +} + +.task-button:active { + transform: translateY(0); +} + +.task-button:disabled { + background: #ccc; + cursor: not-allowed; + transform: none; +} + +.task-status { + text-align: center; + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; + min-height: 25px; +} + +.task-status.success { + color: #4caf50; +} + +.task-status.error { + color: #f44336; +} + +.task-status.completed { + color: #ff9800; +} + +.message { + position: fixed; + top: 20px; + right: 20px; + padding: 15px 25px; + border-radius: 10px; + font-weight: bold; + font-size: 1.2em; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + transform: translateX(400px); + transition: transform 0.3s ease; + z-index: 1000; +} + +.message.show { + transform: translateX(0); +} + +.message.success { + background: #4caf50; + color: white; +} + +.message.error { + background: #f44336; + color: white; +} + +@media (max-width: 600px) { + .container { + padding: 20px; + } + + h1 { + font-size: 2em; + } + + .points-value { + font-size: 2.5em; + } + + .task-input-group { + flex-direction: column; + } + + .task-button { + width: 100%; + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..06e4437 --- /dev/null +++ b/server.js @@ -0,0 +1,74 @@ +const express = require('express'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); + +const app = express(); +const PORT = process.env.PORT || 3000; +const TASKS_FILE = path.join(__dirname, 'tasks.json'); + +// Middleware +app.use(cors()); +app.use(express.json()); +app.use(express.static('public')); + +// Lade Aufgaben +function loadTasks() { + try { + const data = fs.readFileSync(TASKS_FILE, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Fehler beim Laden der Aufgaben:', error); + return []; + } +} + +// Speichere Aufgaben +function saveTasks(tasks) { + fs.writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2)); +} + +// API: Hole alle Aufgaben +app.get('/api/tasks', (req, res) => { + const tasks = loadTasks(); + res.json({ tasks }); +}); + + +// API: Prüfe Antwort +app.post('/api/check-answer', (req, res) => { + const { taskId, answer } = req.body; + + if (!taskId || answer === undefined) { + return res.status(400).json({ error: 'taskId und answer sind erforderlich' }); + } + + const tasks = loadTasks(); + const taskIndex = tasks.findIndex(t => t.id === taskId); + + if (taskIndex === -1) { + return res.status(404).json({ error: 'Aufgabe nicht gefunden' }); + } + + const task = tasks[taskIndex]; + const userAnswer = parseInt(answer); + const correctAnswer = parseInt(task.answer); + const isCorrect = userAnswer === correctAnswer; + + // Speichere Antwort und isCorrect in der Task + tasks[taskIndex].userAnswer = userAnswer; + tasks[taskIndex].isCorrect = isCorrect; + saveTasks(tasks); + + res.json({ + correct: isCorrect, + correctAnswer: correctAnswer, + points: isCorrect ? task.points : 0 + }); +}); + + +// Starte Server +app.listen(PORT, () => { + console.log(`MathQuest Server läuft auf http://localhost:${PORT}`); +}); diff --git a/tasks.json b/tasks.json new file mode 100644 index 0000000..d1145c3 --- /dev/null +++ b/tasks.json @@ -0,0 +1,92 @@ +[ + { + "id": "add-1", + "question": "5 + 3 = ?", + "answer": 8, + "points": 10, + "type": "addition", + "userAnswer": 8, + "isCorrect": true + }, + { + "id": "add-2", + "question": "7 + 4 = ?", + "answer": 11, + "points": 10, + "type": "addition", + "userAnswer": 11, + "isCorrect": true + }, + { + "id": "add-3", + "question": "9 + 6 = ?", + "answer": 15, + "points": 10, + "type": "addition", + "userAnswer": 15, + "isCorrect": true + }, + { + "id": "sub-1", + "question": "10 - 4 = ?", + "answer": 6, + "points": 15, + "type": "subtraction", + "userAnswer": 6, + "isCorrect": true + }, + { + "id": "sub-2", + "question": "15 - 7 = ?", + "answer": 8, + "points": 15, + "type": "subtraction", + "userAnswer": 8, + "isCorrect": true + }, + { + "id": "mult-1", + "question": "3 × 4 = ?", + "answer": 12, + "points": 20, + "type": "multiplication", + "userAnswer": 12, + "isCorrect": true + }, + { + "id": "mult-2", + "question": "5 × 6 = ?", + "answer": 30, + "points": 20, + "type": "multiplication", + "userAnswer": 30, + "isCorrect": true + }, + { + "id": "add-4", + "question": "12 + 8 = ?", + "answer": 20, + "points": 15, + "type": "addition", + "userAnswer": 20, + "isCorrect": true + }, + { + "id": "sub-3", + "question": "20 - 9 = ?", + "answer": 11, + "points": 15, + "type": "subtraction", + "userAnswer": 11, + "isCorrect": true + }, + { + "id": "mult-3", + "question": "4 × 7 = ?", + "answer": 28, + "points": 25, + "type": "multiplication", + "userAnswer": 28, + "isCorrect": true + } +] \ No newline at end of file diff --git a/test-local.sh b/test-local.sh new file mode 100755 index 0000000..73d2f53 --- /dev/null +++ b/test-local.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +echo "🚀 MathQuest - Lokaler Test" +echo "============================" +echo "" + +# Prüfe ob node_modules existiert +if [ ! -d "node_modules" ]; then + echo "📦 Installiere Dependencies..." + npm install + if [ $? -ne 0 ]; then + echo "❌ Fehler beim Installieren der Dependencies" + echo " Versuche: sudo chown -R \$USER:\$USER ~/.npm" + exit 1 + fi + echo "✅ Dependencies installiert" +else + echo "✅ Dependencies bereits installiert" +fi + +echo "" +echo "🌐 Starte Server auf http://localhost:3000" +echo " Drücke Ctrl+C zum Beenden" +echo "" + +# Starte Server +npm start