feat: Add universal victory celebration modal, chamber item requirements, and fix broom & cauldron displays

This commit is contained in:
Maxi
2026-09-22 21:10:08 +02:00
parent cb8851b7a4
commit 8c3e60c1c4
19 changed files with 1149 additions and 92 deletions
+121
View File
@@ -230,6 +230,127 @@
window.showFloatingToast(`🎒 <strong>Neuer Gegenstand im Beutel:</strong> ${item.icon} ${item.name}!`);
};
// Confetti Animation Controller
let confettiAnimId = null;
function runVictoryConfetti() {
const canvas = document.getElementById('victoryConfettiCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const colors = ['#ffd700', '#f39c12', '#e74c3c', '#3498db', '#2ecc71', '#9b59b6', '#ffffff'];
const particles = [];
for (let i = 0; i < 90; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * -canvas.height * 0.5,
w: Math.random() * 9 + 5,
h: Math.random() * 5 + 3,
vx: (Math.random() - 0.5) * 4,
vy: Math.random() * 3 + 2.5,
color: colors[Math.floor(Math.random() * colors.length)],
angle: Math.random() * Math.PI * 2,
vAngle: (Math.random() - 0.5) * 0.15,
shape: Math.random() > 0.4 ? 'rect' : 'star'
});
}
let start = Date.now();
if (confettiAnimId) cancelAnimationFrame(confettiAnimId);
function drawConfetti() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const elapsed = (Date.now() - start) / 1000;
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
p.angle += p.vAngle;
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.angle);
ctx.fillStyle = p.color;
if (p.shape === 'star') {
ctx.beginPath();
ctx.arc(0, 0, p.w * 0.45, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
}
ctx.restore();
if (p.y > canvas.height + 20) {
p.y = -20;
p.x = Math.random() * canvas.width;
}
});
if (elapsed < 8) {
confettiAnimId = requestAnimationFrame(drawConfetti);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
confettiAnimId = requestAnimationFrame(drawConfetti);
}
// Unified Spectacular Victory Celebration Overlay
window.showChamberVictory = function(config) {
const modal = document.getElementById('victoryCelebrationModal');
if (!modal) {
// Fallback to standard modal if celebration overlay missing
if (window.modalTitle) window.modalTitle.innerText = config.title;
if (window.modalText) window.modalText.innerHTML = config.storyText;
if (window.modalBackdrop) window.modalBackdrop.classList.add('show');
return;
}
const badge = document.getElementById('vicChamberBadge');
const title = document.getElementById('vicTitle');
const statsBox = document.getElementById('vicStatsBox');
const itemBox = document.getElementById('vicItemRewardBox');
const rewardIcon = document.getElementById('vicRewardIcon');
const rewardName = document.getElementById('vicRewardName');
const rewardDesc = document.getElementById('vicRewardDesc');
const storyBox = document.getElementById('vicStoryBox');
const nextBtn = document.getElementById('vicNextBtn');
if (badge) badge.innerText = config.chamberBadge || 'Kammer Gemeistert';
if (title) title.innerText = config.title || 'Hervorragend!';
if (statsBox) {
statsBox.innerHTML = config.statsHtml || '';
statsBox.style.display = config.statsHtml ? 'flex' : 'none';
}
if (storyBox) storyBox.innerHTML = config.storyText || '';
if (config.item && itemBox) {
itemBox.style.display = 'flex';
if (rewardIcon) rewardIcon.innerText = config.item.icon || '✨';
if (rewardName) rewardName.innerText = config.item.name || '';
if (rewardDesc) rewardDesc.innerText = config.item.desc || '';
window.notifyNewItem(config.item);
} else if (itemBox) {
itemBox.style.display = 'none';
}
if (nextBtn) {
nextBtn.href = config.nextUrl || '/';
nextBtn.innerHTML = config.nextBtnText || '➔ Weiter zur nächsten Kammer ✨';
}
// Trigger Audio, Haptic & Particle Celebration
if (window.soundFX) window.soundFX.playMagicSuccess();
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate([60, 50, 80, 50, 180]);
}
modal.classList.add('show');
runVictoryConfetti();
};
// Auto-select first item if available
if (itemSlots.length > 0) {
selectItem(itemSlots[0]);