fix: s3 board+palette init, module loading order, openMGTest API
- editor.html: script tags for all 14 modules now correctly BEFORE init - init script moved to last <script> block, runs after all modules loaded - openMGTest() now uses MG_<id>.preview() from module system - closeMGTest() calls mgTestActive.stop() to clean up properly - mgTestWrap div replaces static canvas (modules render into it) - BACKGROUNDS array extended with color+glow for theme-aware previews - game.html: getMgInfo() reads from loaded modules dynamically
This commit is contained in:
parent
016be6ea90
commit
43752fa8e4
2 changed files with 1089 additions and 146 deletions
934
editor.html
934
editor.html
File diff suppressed because one or more lines are too long
301
game.html
301
game.html
|
|
@ -398,13 +398,20 @@ const BG_THEMES={
|
||||||
};
|
};
|
||||||
const THEME = BG_THEMES[cfg.background] || BG_THEMES.space;
|
const THEME = BG_THEMES[cfg.background] || BG_THEMES.space;
|
||||||
|
|
||||||
// MG_INFO wird dynamisch aus den geladenen Modulen aufgebaut
|
const MG_INFO={
|
||||||
function getMgInfo(id) {
|
snake: {emoji:'🐍',name:'Snake', desc:'Steuere die Schlange! Iss das Essen ohne gegen die Wand zu fahren.',controls:'Pfeiltasten oder WASD'},
|
||||||
const mod = window[`MG_${id}`];
|
flappy: {emoji:'🐦',name:'Flappy Bird', desc:'Klick oder Leertaste, um durch die Röhren zu fliegen!', controls:'Klick / Leertaste'},
|
||||||
if (mod) return { emoji: mod.emoji, name: mod.name, desc: mod.desc, controls: mod.controls };
|
memory: {emoji:'🃏',name:'Memory', desc:'Finde alle Paare — so schnell wie möglich!', controls:'Mausklick'},
|
||||||
return { emoji: '🎮', name: id, desc: 'Mini-Game!', controls: 'Variiert' };
|
quiz: {emoji:'❓',name:'Quiz', desc:'Beantworte die Frage richtig!', controls:'Mausklick'},
|
||||||
}
|
reaction: {emoji:'⚡',name:'Reaktion', desc:'Drück den Knopf so schnell du kannst wenn er erscheint!', controls:'Klick / Leertaste'},
|
||||||
|
basketball:{emoji:'🏀',name:'Basketball', desc:'Ziehe den Ball und lass ihn los um zu werfen!', controls:'Maus ziehen'},
|
||||||
|
catch: {emoji:'🍎',name:'Äpfel fangen',desc:'Bewege den Korb mit den Pfeiltasten!', controls:'← → oder A/D'},
|
||||||
|
maze: {emoji:'🌀',name:'Labyrinth', desc:'Finde den Ausgang!', controls:'Pfeiltasten / WASD'},
|
||||||
|
simon: {emoji:'🔴',name:'Simon Says', desc:'Merke und wiederhole die Farb-Sequenz!', controls:'Mausklick'},
|
||||||
|
puzzle: {emoji:'🧩',name:'Rätsel', desc:'Löse die Aufgabe!', controls:'Mausklick'},
|
||||||
|
spotdiff: {emoji:'🔍',name:'Unterschiede',desc:'Finde alle Unterschiede!', controls:'Mausklick'},
|
||||||
|
typing: {emoji:'⌨️',name:'Tipp-Rennen', desc:'Tippe das Wort so schnell wie möglich!', controls:'Tastatur'},
|
||||||
|
};
|
||||||
|
|
||||||
const figEmoji = FIGURES[cfg.figure] || '🎮';
|
const figEmoji = FIGURES[cfg.figure] || '🎮';
|
||||||
const LETTERS = ['A','B','C','D'];
|
const LETTERS = ['A','B','C','D'];
|
||||||
|
|
@ -959,56 +966,47 @@ function enableRoll(){
|
||||||
|
|
||||||
/* ══════════ MINIGAME SYSTEM ══════════ */
|
/* ══════════ MINIGAME SYSTEM ══════════ */
|
||||||
function openMinigame(id){
|
function openMinigame(id){
|
||||||
const info = getMgInfo(id);
|
const info=MG_INFO[id]||{emoji:'🎮',name:id,desc:'Mini-Game!',controls:'Variiert'};
|
||||||
currentMgId = id;
|
currentMgId=id;
|
||||||
document.getElementById('mgTitle').innerHTML = `${info.emoji} ${info.name}`;
|
document.getElementById('mgTitle').innerHTML=`${info.emoji} ${info.name}`;
|
||||||
document.getElementById('mgDesc').textContent = info.desc;
|
document.getElementById('mgDesc').textContent=info.desc;
|
||||||
document.getElementById('mgControls').innerHTML = `🎮 ${info.controls}`;
|
document.getElementById('mgControls').innerHTML=`🎮 ${info.controls}`;
|
||||||
document.getElementById('mgResultBar').style.display = 'none';
|
document.getElementById('mgResultBar').style.display='none';
|
||||||
document.getElementById('btnMgContinue').style.display = 'none';
|
document.getElementById('btnMgContinue').style.display='none';
|
||||||
|
|
||||||
const wrap = document.getElementById('mgCanvasWrap');
|
const wrap=document.getElementById('mgCanvasWrap');
|
||||||
wrap.innerHTML = '';
|
wrap.innerHTML='';
|
||||||
wrap.style.minHeight = '';
|
|
||||||
|
|
||||||
if (mgActive && mgActive.stop) mgActive.stop();
|
if(mgActive&&mgActive.stop) mgActive.stop();
|
||||||
mgActive = null; mgResult = null;
|
mgActive=null; mgResult=null;
|
||||||
|
|
||||||
playSfx('mg');
|
playSfx('mg');
|
||||||
document.getElementById('mgOverlay').classList.add('open');
|
document.getElementById('mgOverlay').classList.add('open');
|
||||||
|
|
||||||
const W = Math.min(560, window.innerWidth - 80) - 48;
|
const W=Math.min(560, window.innerWidth-80)-48;
|
||||||
const H = 300;
|
const H=260;
|
||||||
|
|
||||||
// Theme aus THEME-Objekt
|
const launchers={
|
||||||
const modCfg = {
|
snake: launchSnake,
|
||||||
theme: { primary: THEME.primary, glow: THEME.glow },
|
flappy: launchFlappy,
|
||||||
quizData: quizData,
|
memory: launchMemory,
|
||||||
fieldIndex: pos,
|
quiz: launchQuiz,
|
||||||
devName: cfg.devName,
|
reaction:launchReaction,
|
||||||
gameName: cfg.name,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// onResult-Handler setzen
|
if(launchers[id]){ mgActive=launchers[id](wrap,W,H); }
|
||||||
window._mgOnResult = finishMinigame;
|
else {
|
||||||
if (window.MGAPI) MGAPI.onResult = finishMinigame;
|
// Placeholder for other games
|
||||||
|
wrap.style.minHeight=H+'px';
|
||||||
const mod = window[`MG_${id}`];
|
wrap.innerHTML=`<div style="text-align:center;padding:40px 20px">
|
||||||
if (mod) {
|
|
||||||
mgActive = mod.launch(wrap, W, H, modCfg);
|
|
||||||
} else {
|
|
||||||
// Fallback
|
|
||||||
wrap.style.minHeight = H + 'px';
|
|
||||||
wrap.innerHTML = `<div style="text-align:center;padding:40px 20px">
|
|
||||||
<div style="font-size:3rem;margin-bottom:12px">${info.emoji}</div>
|
<div style="font-size:3rem;margin-bottom:12px">${info.emoji}</div>
|
||||||
<div style="font-family:'Fredoka One',cursive;font-size:1.2rem;color:${THEME.primary};margin-bottom:8px">${info.name}</div>
|
<div style="font-family:'Fredoka One',cursive;font-size:1.2rem;color:${THEME.primary};margin-bottom:8px">${info.name}</div>
|
||||||
<div style="color:#a7a3c2;font-size:13px;margin-bottom:20px">Modul wird geladen...</div>
|
<div style="color:#a7a3c2;font-size:13px;margin-bottom:20px">Dieses Mini-Game wird bald verfügbar sein!</div>
|
||||||
<button onclick="finishMinigame(true)" style="background:linear-gradient(135deg,#7c3aed,#f5a623);color:#fff;font-family:'Fredoka One',cursive;font-size:1rem;border:none;border-radius:10px;padding:12px 28px;cursor:pointer;">✅ Bestanden!</button>
|
<button onclick="finishMinigame(true)" style="background:linear-gradient(135deg,#7c3aed,#f5a623);color:#fff;font-family:'Fredoka One',cursive;font-size:1rem;border:none;border-radius:10px;padding:12px 28px;cursor:pointer;">✅ Bestanden!</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function finishMinigame(won){
|
function finishMinigame(won){
|
||||||
if(mgActive&&mgActive.stop) mgActive.stop();
|
if(mgActive&&mgActive.stop) mgActive.stop();
|
||||||
mgActive=null;
|
mgActive=null;
|
||||||
|
|
@ -1053,6 +1051,213 @@ function skipMinigame(){
|
||||||
enableRoll();
|
enableRoll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ══ SNAKE ══ */
|
||||||
|
function launchSnake(wrap,W,H){
|
||||||
|
const canvas=document.createElement('canvas');
|
||||||
|
canvas.width=W; canvas.height=H;
|
||||||
|
wrap.appendChild(canvas);
|
||||||
|
const ctx=canvas.getContext('2d');
|
||||||
|
const SZ=20, cols=Math.floor(W/SZ), rows=Math.floor(H/SZ);
|
||||||
|
let snake=[{x:5,y:5},{x:4,y:5},{x:3,y:5}],dir={x:1,y:0},food={x:10,y:8},score=0,dead=false,started=true;
|
||||||
|
let raf,stopped=false;
|
||||||
|
const onKey=e=>{
|
||||||
|
if(e.key==='ArrowUp'&&dir.y===0)dir={x:0,y:-1};
|
||||||
|
if(e.key==='ArrowDown'&&dir.y===0)dir={x:0,y:1};
|
||||||
|
if(e.key==='ArrowLeft'&&dir.x===0)dir={x:-1,y:0};
|
||||||
|
if(e.key==='ArrowRight'&&dir.x===0)dir={x:1,y:0};
|
||||||
|
if(e.key==='w')dir={x:0,y:-1}; if(e.key==='s')dir={x:0,y:1};
|
||||||
|
if(e.key==='a')dir={x:-1,y:0}; if(e.key==='d')dir={x:1,y:0};
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown',onKey);
|
||||||
|
let last=0, endTimer=null;
|
||||||
|
function loop(ts){
|
||||||
|
if(stopped)return; raf=requestAnimationFrame(loop);
|
||||||
|
if(ts-last<140)return; last=ts;
|
||||||
|
if(!dead){
|
||||||
|
const h={x:snake[0].x+dir.x,y:snake[0].y+dir.y};
|
||||||
|
if(h.x<0||h.x>=cols||h.y<0||h.y>=rows||snake.some(s=>s.x===h.x&&s.y===h.y)){
|
||||||
|
dead=true;
|
||||||
|
if(!endTimer) endTimer=setTimeout(()=>finishMinigame(score>=3),1200);
|
||||||
|
} else {
|
||||||
|
snake.unshift(h);
|
||||||
|
if(h.x===food.x&&h.y===food.y){score++;food={x:Math.floor(Math.random()*cols),y:Math.floor(Math.random()*rows)};}
|
||||||
|
else snake.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H);
|
||||||
|
ctx.fillStyle=THEME.primary; ctx.beginPath(); ctx.arc(food.x*SZ+SZ/2,food.y*SZ+SZ/2,SZ*0.4,0,Math.PI*2); ctx.fill();
|
||||||
|
snake.forEach((s,i)=>{
|
||||||
|
ctx.fillStyle=i===0?THEME.primary:`${THEME.primary}99`;
|
||||||
|
ctx.fillRect(s.x*SZ+1,s.y*SZ+1,SZ-2,SZ-2);
|
||||||
|
});
|
||||||
|
ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`🍕 ${score} Ziel: 3`,8,18);
|
||||||
|
if(dead){
|
||||||
|
ctx.fillStyle='rgba(239,68,68,0.8)'; ctx.fillRect(0,H/2-28,W,56);
|
||||||
|
ctx.fillStyle='#fff'; ctx.font='bold 20px Fredoka One,cursive'; ctx.textAlign='center';
|
||||||
|
ctx.fillText(score>=3?'🎉 Geschafft!':'💀 Game Over',W/2,H/2+6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raf=requestAnimationFrame(loop);
|
||||||
|
return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══ FLAPPY ══ */
|
||||||
|
function launchFlappy(wrap,W,H){
|
||||||
|
const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas);
|
||||||
|
const ctx=canvas.getContext('2d');
|
||||||
|
let bird={y:H/2,vy:0},pipes=[{x:W,gap:Math.random()*(H-100)+30}],score=0,dead=false,started=false;
|
||||||
|
const GRAV=0.5,JUMP=-8,PW=38,GAP=85;
|
||||||
|
let raf,stopped=false,endTimer=null;
|
||||||
|
const jump=()=>{if(!dead){bird.vy=JUMP;started=true;}};
|
||||||
|
const onKey=e=>{if(e.code==='Space'){e.preventDefault();jump();}};
|
||||||
|
document.addEventListener('keydown',onKey); canvas.addEventListener('click',jump);
|
||||||
|
function loop(){
|
||||||
|
if(stopped)return; raf=requestAnimationFrame(loop);
|
||||||
|
ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H);
|
||||||
|
if(started&&!dead){
|
||||||
|
bird.vy+=GRAV; bird.y+=bird.vy;
|
||||||
|
pipes.forEach(p=>p.x-=2.8);
|
||||||
|
if(pipes[pipes.length-1].x<W-170) pipes.push({x:W,gap:Math.random()*(H-100)+30});
|
||||||
|
pipes=pipes.filter(p=>p.x>-PW);
|
||||||
|
pipes.forEach(p=>{if(p.x+PW<50&&!p.passed){p.passed=true;score++;}});
|
||||||
|
if(bird.y<0||bird.y+20>H)dead=true;
|
||||||
|
pipes.forEach(p=>{if(50<p.x+PW&&70>p.x&&(bird.y<p.gap||bird.y+20>p.gap+GAP))dead=true;});
|
||||||
|
if(dead&&!endTimer) endTimer=setTimeout(()=>finishMinigame(score>=3),1000);
|
||||||
|
}
|
||||||
|
pipes.forEach(p=>{
|
||||||
|
ctx.fillStyle=THEME.primary+'bb';
|
||||||
|
ctx.fillRect(p.x,0,PW,p.gap); ctx.fillRect(p.x,p.gap+GAP,PW,H-p.gap-GAP);
|
||||||
|
});
|
||||||
|
ctx.fillStyle=dead?'#ef4444':THEME.primary;
|
||||||
|
ctx.beginPath(); ctx.ellipse(50+14,bird.y+10,14,10,bird.vy*0.04,0,Math.PI*2); ctx.fill();
|
||||||
|
ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`🐦 ${score} Ziel: 3`,8,18);
|
||||||
|
if(!started){ctx.fillStyle='rgba(255,255,255,0.7)';ctx.textAlign='center';ctx.font='13px Nunito,sans-serif';ctx.fillText('Klicken oder Leertaste',W/2,H/2+40);}
|
||||||
|
if(dead){ctx.fillStyle='rgba(239,68,68,0.8)';ctx.fillRect(0,H/2-28,W,56);ctx.fillStyle='#fff';ctx.font='bold 20px Fredoka One,cursive';ctx.textAlign='center';ctx.fillText(score>=3?'🎉 Geschafft!':'💀 Daneben!',W/2,H/2+6);}
|
||||||
|
}
|
||||||
|
raf=requestAnimationFrame(loop);
|
||||||
|
return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);canvas.removeEventListener('click',jump);}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══ MEMORY ══ */
|
||||||
|
function launchMemory(wrap,W,H){
|
||||||
|
const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas);
|
||||||
|
const ctx=canvas.getContext('2d');
|
||||||
|
const emojis=['🐱','🐶','🦊','🐸','🦋','🐠'];
|
||||||
|
const all=[...emojis,...emojis].sort(()=>Math.random()-0.5);
|
||||||
|
let cards=all.map((e,i)=>({e,i,open:false}));
|
||||||
|
let flipped=[],matched=[],checking=false,moves=0;
|
||||||
|
let raf,stopped=false;
|
||||||
|
const C=4,R=3,cw=Math.floor((W-16)/(C+0.5)),ch=Math.floor((H-16)/R);
|
||||||
|
const ox=(W-C*cw)/2, oy=(H-R*ch)/2;
|
||||||
|
canvas.addEventListener('click',e=>{
|
||||||
|
if(checking)return;
|
||||||
|
const r=canvas.getBoundingClientRect();
|
||||||
|
const mx=e.clientX-r.left, my=e.clientY-r.top;
|
||||||
|
for(let ri=0;ri<R;ri++) for(let ci=0;ci<C;ci++){
|
||||||
|
const card=cards[ri*C+ci];
|
||||||
|
const x=ox+ci*cw, y=oy+ri*ch;
|
||||||
|
if(mx>=x&&mx<x+cw-4&&my>=y&&my<y+ch-4&&!card.open&&!matched.includes(card.i)){
|
||||||
|
card.open=true; flipped.push(card);
|
||||||
|
if(flipped.length===2){
|
||||||
|
moves++;checking=true;
|
||||||
|
setTimeout(()=>{
|
||||||
|
if(flipped[0].e===flipped[1].e) matched.push(flipped[0].i,flipped[1].i);
|
||||||
|
else flipped.forEach(c=>c.open=false);
|
||||||
|
flipped=[]; checking=false;
|
||||||
|
if(matched.length===cards.length) setTimeout(()=>finishMinigame(true),600);
|
||||||
|
},700);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function loop(){
|
||||||
|
if(stopped)return; raf=requestAnimationFrame(loop);
|
||||||
|
ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H);
|
||||||
|
ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`🃏 ${matched.length/2}/${emojis.length} Paare Züge: ${moves}`,8,18);
|
||||||
|
for(let ri=0;ri<R;ri++) for(let ci=0;ci<C;ci++){
|
||||||
|
const card=cards[ri*C+ci];
|
||||||
|
const x=ox+ci*cw, y=oy+ri*ch, isM=matched.includes(card.i);
|
||||||
|
ctx.fillStyle=isM?'rgba(16,185,129,0.2)':card.open?'#2e2b4a':'rgba(255,255,255,0.04)';
|
||||||
|
ctx.strokeStyle=isM?'#10b981':card.open?THEME.primary:'rgba(255,255,255,0.1)';
|
||||||
|
ctx.lineWidth=2;
|
||||||
|
ctx.beginPath(); ctx.roundRect(x+2,y+2,cw-8,ch-8,8); ctx.fill(); ctx.stroke();
|
||||||
|
if(card.open||isM){ ctx.font=`${Math.min(cw,ch)*0.5}px serif`; ctx.textAlign='center'; ctx.fillText(card.e,x+cw/2,y+ch/2+10); }
|
||||||
|
else { ctx.fillStyle='rgba(255,255,255,0.15)'; ctx.font='bold 16px Nunito,sans-serif'; ctx.textAlign='center'; ctx.fillText('?',x+cw/2,y+ch/2+5); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raf=requestAnimationFrame(loop);
|
||||||
|
return{stop:()=>{stopped=true;cancelAnimationFrame(raf);}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══ QUIZ ══ */
|
||||||
|
function launchQuiz(wrap,W,H){
|
||||||
|
const q=quizData[quizIdx]||null; quizIdx++;
|
||||||
|
if(!q||!q.question){ wrap.innerHTML=`<div style="text-align:center;padding:40px;font-family:'Nunito',sans-serif"><div style="font-size:2rem;margin-bottom:12px">❓</div><div style="color:#a7a3c2">Keine Quiz-Frage hinterlegt.</div><button onclick="finishMinigame(true)" style="margin-top:16px;background:#7c3aed;color:#fff;border:none;border-radius:10px;padding:10px 24px;cursor:pointer;font-family:'Fredoka One',cursive">OK</button></div>`; return{stop:()=>{}};}
|
||||||
|
let answered=false;
|
||||||
|
wrap.innerHTML=`
|
||||||
|
<div style="padding:20px;font-family:'Nunito',sans-serif;width:100%">
|
||||||
|
<div style="font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:${THEME.primary};margin-bottom:10px">❓ Quiz-Frage</div>
|
||||||
|
<div style="font-size:15px;font-weight:700;margin-bottom:16px;line-height:1.5;color:#fff">${q.question}</div>
|
||||||
|
<div id="quizAnswers" style="display:grid;grid-template-columns:1fr 1fr;gap:8px"></div>
|
||||||
|
<div id="quizResult" style="margin-top:12px;text-align:center;font-family:'Fredoka One',cursive;font-size:1.1rem;display:none"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const grid=wrap.querySelector('#quizAnswers');
|
||||||
|
LETTERS.forEach((L,i)=>{
|
||||||
|
const btn=document.createElement('button');
|
||||||
|
btn.style.cssText=`background:rgba(255,255,255,0.06);border:1.5px solid rgba(255,255,255,0.12);border-radius:10px;padding:10px 12px;font-family:'Nunito',sans-serif;font-size:13px;font-weight:700;color:#fff;cursor:pointer;display:flex;align-items:center;gap:8px;transition:all 0.2s;text-align:left;`;
|
||||||
|
btn.innerHTML=`<span style="font-family:'Fredoka One',cursive;font-size:0.9rem;color:${THEME.primary}">${L}</span>${q.answers[i]||'?'}`;
|
||||||
|
btn.addEventListener('mouseenter',()=>{ if(!answered) btn.style.borderColor=THEME.primary; });
|
||||||
|
btn.addEventListener('mouseleave',()=>{ if(!answered) btn.style.borderColor='rgba(255,255,255,0.12)'; });
|
||||||
|
btn.addEventListener('click',()=>{
|
||||||
|
if(answered)return; answered=true;
|
||||||
|
const correct=i===q.correct;
|
||||||
|
btn.style.borderColor=correct?'#10b981':'#ef4444';
|
||||||
|
btn.style.background=correct?'rgba(16,185,129,0.15)':'rgba(239,68,68,0.1)';
|
||||||
|
if(!correct){
|
||||||
|
const cBtn=grid.children[q.correct];
|
||||||
|
cBtn.style.borderColor='#10b981'; cBtn.style.background='rgba(16,185,129,0.15)';
|
||||||
|
}
|
||||||
|
const res=wrap.querySelector('#quizResult');
|
||||||
|
res.style.display='block';
|
||||||
|
res.style.color=correct?'#10b981':'#ef4444';
|
||||||
|
res.textContent=correct?'✅ Richtig!':'❌ Falsch!';
|
||||||
|
setTimeout(()=>finishMinigame(correct),1200);
|
||||||
|
});
|
||||||
|
grid.appendChild(btn);
|
||||||
|
});
|
||||||
|
return{stop:()=>{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══ REACTION ══ */
|
||||||
|
function launchReaction(wrap,W,H){
|
||||||
|
const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas);
|
||||||
|
const ctx=canvas.getContext('2d');
|
||||||
|
let phase='wait',waitEnd=0,reactionStart=0,times=[],best=Infinity;
|
||||||
|
let raf,stopped=false;
|
||||||
|
function reset(){phase='wait';waitEnd=performance.now()+(2+Math.random()*3)*1000;}reset();
|
||||||
|
const react=()=>{
|
||||||
|
if(phase==='ready'){const t=performance.now()-reactionStart;times.push(t);best=Math.min(best,t);if(times.length>=3){setTimeout(()=>finishMinigame(best<600),800);}else reset();}
|
||||||
|
else if(phase==='wait'){phase='toosoon';setTimeout(reset,1000);}
|
||||||
|
};
|
||||||
|
const onKey=e=>{if(e.code==='Space')react();};
|
||||||
|
document.addEventListener('keydown',onKey); canvas.addEventListener('click',react);
|
||||||
|
function loop(ts){
|
||||||
|
if(stopped)return; raf=requestAnimationFrame(loop);
|
||||||
|
if(phase==='wait'&&ts>waitEnd){phase='ready';reactionStart=performance.now();}
|
||||||
|
ctx.fillStyle=phase==='ready'?'#16a34a':phase==='toosoon'?'#7f1d1d':'#050508';
|
||||||
|
ctx.fillRect(0,0,W,H);
|
||||||
|
ctx.textAlign='center';
|
||||||
|
if(phase==='wait'){ctx.fillStyle='#a7a3c2';ctx.font='16px Nunito,sans-serif';ctx.fillText('Warte...',W/2,H/2-20);ctx.fillText(`${times.length}/3 Runden`,W/2,H/2+10);}
|
||||||
|
if(phase==='ready'){ctx.fillStyle='#fff';ctx.font='bold 36px Fredoka One,cursive';ctx.fillText('JETZT!',W/2,H/2);ctx.font='14px Nunito,sans-serif';ctx.fillText('Klick oder Leertaste!',W/2,H/2+36);}
|
||||||
|
if(phase==='toosoon'){ctx.fillStyle='#fca5a5';ctx.font='bold 24px Fredoka One,cursive';ctx.fillText('Zu früh! 😅',W/2,H/2);}
|
||||||
|
if(times.length>0){const last=times[times.length-1];ctx.fillStyle='rgba(255,255,255,0.6)';ctx.font='13px Nunito,sans-serif';ctx.fillText(`Letzte: ${Math.round(last)}ms Beste: ${Math.round(best)}ms`,W/2,H-16);}
|
||||||
|
ctx.textAlign='left';
|
||||||
|
}
|
||||||
|
raf=requestAnimationFrame(loop);
|
||||||
|
return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);canvas.removeEventListener('click',react);}};
|
||||||
|
}
|
||||||
|
|
||||||
/* ══════════ WIN / GAME OVER ══════════ */
|
/* ══════════ WIN / GAME OVER ══════════ */
|
||||||
function showWin(){
|
function showWin(){
|
||||||
|
|
@ -1088,19 +1293,5 @@ function showGameOver(){
|
||||||
function showMenu(){ showToast('☰ Menü kommt in der finalen Version!'); }
|
function showMenu(){ showToast('☰ Menü kommt in der finalen Version!'); }
|
||||||
function backToEditor(){ window.close(); }
|
function backToEditor(){ window.close(); }
|
||||||
</script>
|
</script>
|
||||||
<!-- ═══ Mini-Game Module ═══ -->
|
|
||||||
<script src="minigames/_api.js"></script>
|
|
||||||
<script src="minigames/snake.js"></script>
|
|
||||||
<script src="minigames/flappy.js"></script>
|
|
||||||
<script src="minigames/memory.js"></script>
|
|
||||||
<script src="minigames/quiz.js"></script>
|
|
||||||
<script src="minigames/reaction.js"></script>
|
|
||||||
<script src="minigames/basketball.js"></script>
|
|
||||||
<script src="minigames/catch.js"></script>
|
|
||||||
<script src="minigames/maze.js"></script>
|
|
||||||
<script src="minigames/simon.js"></script>
|
|
||||||
<script src="minigames/typing.js"></script>
|
|
||||||
<script src="minigames/puzzle.js"></script>
|
|
||||||
<script src="minigames/spotdiff.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue