Files
Kinderspiele/game-server/public/spiele/klavier.html
T
2026-04-26 09:44:19 +02:00

471 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Klavier - KinderWelt</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
body {
background: linear-gradient(135deg, #2d1b4e 0%, #1a1a2e 100%);
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 15px;
}
h1 { color: white; margin-bottom: 5px; font-size: 24px; }
.mode-tabs { display: flex; gap: 10px; margin-bottom: 15px; }
.mode-btn {
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
padding: 8px 20px; border-radius: 20px; cursor: pointer; font-family: inherit;
}
.mode-btn.active { background: white; color: #2d1b4e; font-weight: bold; }
.practice-display {
background: rgba(255,255,255,0.1); border-radius: 15px; padding: 15px;
margin: 10px 0; color: white; min-height: 80px; width: 350px; text-align: center;
}
.practice-note {
font-size: 48px; display: inline-block; margin: 0 5px; transition: all 0.3s;
}
.practice-note.highlight {
transform: scale(1.3); text-shadow: 0 0 20px #4ade80;
animation: bounce 0.5s infinite alternate;
}
@keyframes bounce { from { transform: scale(1.3); } to { transform: scale(1.5); } }
.practice-note.correct { color: #4ade80; }
.practice-note.wrong { color: #ff6b6b; }
.piano {
display: flex; position: relative; margin: 15px 0;
background: #333; padding: 10px; border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.white-key {
width: 45px; height: 180px; background: white;
border: 2px solid #333; border-radius: 0 0 5px 5px;
display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
padding-bottom: 8px; cursor: pointer; font-size: 12px; color: #666;
transition: all 0.1s;
}
.white-key:active, .white-key.active, .white-key.highlight {
background: #ddd; transform: translateY(2px);
}
.white-key.highlight { background: #feca57 !important; }
.white-key.correct { background: #86efac !important; }
.black-key {
width: 28px; height: 110px; background: linear-gradient(180deg, #333 0%, #000 100%);
border-radius: 0 0 3px 3px; position: absolute; top: 10px;
display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
padding-bottom: 6px; cursor: pointer; color: #aaa; font-size: 9px;
transition: all 0.1s; z-index: 10;
}
.black-key:active, .black-key.active, .black-key.highlight {
background: #222; transform: translateY(2px);
}
.black-key.highlight { background: #feca57 !important; }
.black-1 { left: 36px; } .black-2 { left: 81px; }
.black-3 { left: 171px; } .black-4 { left: 216px; }
.black-5 { left: 261px; }
.songs-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
max-width: 400px; margin: 10px 0;
}
.song-card {
background: rgba(255,255,255,0.1); border: 2px solid white; border-radius: 15px;
padding: 15px; color: white; cursor: pointer; transition: all 0.2s; text-align: center;
}
.song-card:hover { background: rgba(255,255,255,0.3); transform: translateY(-3px); }
.song-card:disabled { opacity: 0.5; cursor: not-allowed; }
.song-emoji { font-size: 30px; margin-bottom: 5px; }
.song-title { font-weight: bold; font-size: 14px; }
.song-desc { font-size: 11px; color: #aaa; margin-top: 5px; }
.exercise-controls {
display: flex; gap: 10px; margin: 15px 0; flex-wrap: wrap; justify-content: center;
}
.btn {
background: white; border: none; border-radius: 10px; padding: 10px 20px;
font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
}
.btn-start { background: #4ade80; color: white; }
.btn-hint { background: #feca57; color: #333; }
.btn-stop { background: #ff6b6b; color: white; }
.btn-back { background: #6b7280; color: white; text-decoration: none; }
.progress {
width: 350px; height: 10px; background: rgba(255,255,255,0.2); border-radius: 5px;
margin: 10px 0; overflow: hidden;
}
.progress-bar {
height: 100%; background: linear-gradient(90deg, #4ade80, #22c55e);
width: 0%; transition: width 0.3s;
}
.feedback { font-size: 24px; margin: 10px 0; min-height: 30px; }
</style>
</head>
<body>
<h1>🎹 Klavier</h1>
<div class="mode-tabs">
<button class="mode-btn active" id="freeBtn" onclick="setMode('free')">🎵 Frei spielen</button>
<button class="mode-btn" id="practiceBtn" onclick="setMode('practice')">📚 Üben</button>
</div>
<!-- Übungs-Display -->
<div class="practice-display" id="practiceDisplay" style="display:none">
<div id="practiceNotes">Wähle ein Lied zum Üben!</div>
<div class="progress"><div class="progress-bar" id="progressBar"></div></div>
<div class="feedback" id="feedback"></div>
</div>
<!-- Lieder-Auswahl -->
<div class="songs-grid" id="songsGrid">
<div class="song-card" onclick="selectSong('twinkle')">
<div class="song-emoji"></div>
<div class="song-title">Twinkle Twinkle</div>
<div class="song-desc">C-C-G-G-A-A-G</div>
</div>
<div class="song-card" onclick="selectSong('entchen')">
<div class="song-emoji">🦆</div>
<div class="song-title">Alle meine Entchen</div>
<div class="song-desc">C-D-E-F-G-G</div>
</div>
<div class="song-card" onclick="selectSong('haenschen')">
<div class="song-emoji">🐰</div>
<div class="song-title">Hänschen klein</div>
<div class="song-desc">G-E-E-F-D-D-C</div>
</div>
<div class="song-card" onclick="selectSong('happy')">
<div class="song-emoji">🎂</div>
<div class="song-title">Happy Birthday</div>
<div class="song-desc">C-C-D-C-F-E</div>
</div>
<div class="song-card" onclick="selectSong('mary')">
<div class="song-emoji">🐑</div>
<div class="song-title">Mary Had a Lamb</div>
<div class="song-desc">E-D-C-D-E-E-E</div>
</div>
<div class="song-card" onclick="selectSong('schlaf')">
<div class="song-emoji">🌙</div>
<div class="song-title">Schlaf Kindlein</div>
<div class="song-desc">G-G-E-E-F-F-D</div>
</div>
</div>
<!-- Übungs-Controls -->
<div class="exercise-controls" id="practiceControls" style="display:none">
<button class="btn btn-start" onclick="startPractice()">▶️ Start</button>
<button class="btn btn-hint" onclick="showHint()">💡 Tipp</button>
<button class="btn btn-stop" onclick="stopPractice()">⏹️ Stop</button>
</div>
<!-- Piano -->
<div class="piano" id="piano">
<div class="white-key" data-note="C4" data-key="a">C<br><span style="font-size:9px;color:#999">A</span></div>
<div class="white-key" data-note="D4" data-key="s">D<br><span style="font-size:9px;color:#999">S</span></div>
<div class="white-key" data-note="E4" data-key="d">E<br><span style="font-size:9px;color:#999">D</span></div>
<div class="white-key" data-note="F4" data-key="f">F<br><span style="font-size:9px;color:#999">F</span></div>
<div class="white-key" data-note="G4" data-key="g">G<br><span style="font-size:9px;color:#999">G</span></div>
<div class="white-key" data-note="A4" data-key="h">A<br><span style="font-size:9px;color:#999">H</span></div>
<div class="white-key" data-note="B4" data-key="j">B<br><span style="font-size:9px;color:#999">J</span></div>
<div class="white-key" data-note="C5" data-key="k">C<br><span style="font-size:9px;color:#999">K</span></div>
<div class="black-key black-1" data-note="C#4" data-key="w">C#<br><span style="font-size:8px">W</span></div>
<div class="black-key black-2" data-note="D#4" data-key="e">D#<br><span style="font-size:8px">E</span></div>
<div class="black-key black-3" data-note="F#4" data-key="t">F#<br><span style="font-size:8px">T</span></div>
<div class="black-key black-4" data-note="G#4" data-key="z">G#<br><span style="font-size:8px">Z</span></div>
<div class="black-key black-5" data-note="A#4" data-key="u">A#<br><span style="font-size:8px">U</span></div>
</div>
<div class="exercise-controls">
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
</div>
<script>
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const frequencies = {
'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13,
'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00,
'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88, 'C5': 523.25
};
const noteEmojis = {
'C4': '🔴', 'C#4': '⚫', 'D4': '🟠', 'D#4': '⚫',
'E4': '🟡', 'F4': '🟢', 'F#4': '⚫', 'G4': '🔵',
'G#4': '⚫', 'A4': '🟣', 'A#4': '⚫', 'B4': '⚪', 'C5': '🔴'
};
const songs = {
twinkle: {
notes: ['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4'],
durations: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1]
},
entchen: {
notes: ['C4', 'D4', 'E4', 'F4', 'G4', 'G4', 'A4', 'A4', 'A4', 'A4', 'G4', 'F4', 'F4', 'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'D4', 'D4', 'C4'],
durations: [0.4, 0.4, 0.4, 0.4, 0.6, 0.6, 0.3, 0.3, 0.3, 0.3, 0.8, 0.3, 0.3, 0.3, 0.3, 0.4, 0.4, 0.3, 0.3, 0.3, 0.3, 0.8]
},
haenschen: {
notes: ['G4', 'E4', 'E4', 'F4', 'D4', 'D4', 'C4', 'G4', 'G4', 'E4', 'E4', 'F4', 'D4', 'D4', 'C4'],
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8]
},
happy: {
notes: ['C4', 'C4', 'D4', 'C4', 'F4', 'E4', 'C4', 'C4', 'D4', 'C4', 'G4', 'F4'],
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.8, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8]
},
mary: {
notes: ['E4', 'D4', 'C4', 'D4', 'E4', 'E4', 'E4', 'D4', 'D4', 'D4', 'E4', 'G4', 'G4'],
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.6, 0.4, 0.4, 0.6, 0.4, 0.4, 0.6]
},
schlaf: {
notes: ['G4', 'G4', 'E4', 'E4', 'F4', 'F4', 'D4', 'G4', 'G4', 'E4', 'E4', 'f4', 'f4', 'd4'],
durations: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1]
}
};
let currentMode = 'free';
let currentSong = null;
let currentNoteIndex = 0;
let practiceActive = false;
let waitingForNote = false;
function setMode(mode) {
currentMode = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
document.getElementById(mode + 'Btn').classList.add('active');
if (mode === 'practice') {
document.getElementById('practiceDisplay').style.display = 'block';
document.getElementById('practiceControls').style.display = 'flex';
} else {
document.getElementById('practiceDisplay').style.display = 'none';
document.getElementById('practiceControls').style.display = 'none';
stopPractice();
}
}
function selectSong(songName) {
currentSong = songName;
const song = songs[songName];
// Noten anzeigen
const display = document.getElementById('practiceNotes');
display.innerHTML = song.notes.map((note, i) =>
`<span class="practice-note" id="note-${i}">${noteEmojis[note]}</span>`
).join(' ');
currentNoteIndex = 0;
updateProgress();
// Demo abspielen
playSong(songName);
}
function startPractice() {
if (!currentSong) {
document.getElementById('feedback').textContent = '❌ Wähle zuerst ein Lied!';
return;
}
practiceActive = true;
currentNoteIndex = 0;
highlightNextNote();
document.getElementById('feedback').textContent = '🎵 Spiel die markierte Note!';
}
function highlightNextNote() {
if (!practiceActive || !currentSong) return;
const song = songs[currentSong];
// Alte Markierung entfernen
document.querySelectorAll('.practice-note').forEach(n => {
n.classList.remove('highlight', 'correct', 'wrong');
});
if (currentNoteIndex >= song.notes.length) {
// Fertig!
document.getElementById('feedback').textContent = '🎉 Super! Lied geschafft!';
playNote('C4', 0.3);
setTimeout(() => playNote('E4', 0.3), 200);
setTimeout(() => playNote('G4', 0.5), 400);
practiceActive = false;
return;
}
// Nächste Note markieren
const noteEl = document.getElementById('note-' + currentNoteIndex);
if (noteEl) noteEl.classList.add('highlight');
// Taste auf Piano markieren
const expectedNote = song.notes[currentNoteIndex];
highlightKey(expectedNote);
waitingForNote = true;
updateProgress();
}
function highlightKey(note) {
document.querySelectorAll('.white-key, .black-key').forEach(k => {
k.classList.remove('highlight');
});
const key = document.querySelector(`[data-note="${note}"]`);
if (key) key.classList.add('highlight');
}
function checkPracticeNote(playedNote) {
if (!practiceActive || !waitingForNote || !currentSong) return false;
const song = songs[currentSong];
const expectedNote = song.notes[currentNoteIndex];
const noteEl = document.getElementById('note-' + currentNoteIndex);
if (playedNote === expectedNote) {
// Richtig!
noteEl.classList.remove('highlight');
noteEl.classList.add('correct');
document.getElementById('feedback').textContent = '✅ Richtig!';
currentNoteIndex++;
waitingForNote = false;
setTimeout(() => highlightNextNote(), 500);
return true;
} else {
// Falsch
noteEl.classList.add('wrong');
document.getElementById('feedback').textContent = '❌ Das war ' + playedNote + ', versuche ' + expectedNote;
setTimeout(() => {
noteEl.classList.remove('wrong');
if (practiceActive) highlightNextNote();
}, 1000);
return false;
}
}
function showHint() {
if (!practiceActive || !currentSong) return;
const song = songs[currentSong];
const expectedNote = song.notes[currentNoteIndex];
// Note blinkt
const key = document.querySelector(`[data-note="${expectedNote}"]`);
if (key) {
key.style.animation = 'bounce 0.5s 3';
setTimeout(() => key.style.animation = '', 1500);
}
document.getElementById('feedback').textContent = '💡 Schau auf die gelbe Taste!';
}
function stopPractice() {
practiceActive = false;
waitingForNote = false;
currentNoteIndex = 0;
document.querySelectorAll('.practice-note').forEach(n => {
n.classList.remove('highlight', 'correct', 'wrong');
});
document.querySelectorAll('.white-key, .black-key').forEach(k => {
k.classList.remove('highlight');
});
document.getElementById('progressBar').style.width = '0%';
document.getElementById('feedback').textContent = '';
}
function updateProgress() {
if (!currentSong) return;
const song = songs[currentSong];
const progress = (currentNoteIndex / song.notes.length) * 100;
document.getElementById('progressBar').style.width = progress + '%';
}
function playNote(note, duration = 0.3) {
if (!frequencies[note]) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.frequency.value = frequencies[note];
osc.type = 'sine';
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.start();
osc.stop(audioCtx.currentTime + duration);
// Visuelles Feedback
const key = document.querySelector(`[data-note="${note}"]`);
if (key) {
key.classList.add('active');
setTimeout(() => key.classList.remove('active'), 200);
}
}
async function playSong(songName) {
const song = songs[songName];
if (!song) return;
for (let i = 0; i < song.notes.length; i++) {
playNote(song.notes[i], song.durations[i]);
await sleep(song.durations[i] * 1000);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Tasten-Events
document.querySelectorAll('.white-key, .black-key').forEach(key => {
key.addEventListener('mousedown', () => {
const note = key.dataset.note;
playNote(note);
if (currentMode === 'practice') {
checkPracticeNote(note);
}
});
key.addEventListener('touchstart', (e) => {
e.preventDefault();
const note = key.dataset.note;
playNote(note);
if (currentMode === 'practice') {
checkPracticeNote(note);
}
});
});
// Keyboard
document.addEventListener('keydown', (e) => {
const key = document.querySelector(`[data-key="${e.key.toLowerCase()}"]`);
if (key && !key.classList.contains('active')) {
const note = key.dataset.note;
playNote(note);
key.classList.add('active');
if (currentMode === 'practice') {
checkPracticeNote(note);
}
}
});
document.addEventListener('keyup', (e) => {
const key = document.querySelector(`[data-key="${e.key.toLowerCase()}"]`);
if (key) key.classList.remove('active');
});
</script>
</body>
</html>