471 lines
20 KiB
HTML
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> |