Polish: Fix pixie visibility bug, add flight ready countdown, SVG cauldron redesign, and upgrade riddles

This commit is contained in:
Maxi
2026-09-22 20:14:32 +02:00
parent 5551c4e54f
commit cb8851b7a4
12 changed files with 368 additions and 231 deletions
+88 -114
View File
@@ -1224,118 +1224,67 @@ body {
margin-bottom: 0.5rem;
}
.cauldron-wrapper {
position: relative;
width: 280px;
height: 280px;
display: flex;
justify-content: center;
align-items: center;
margin: 0.5rem 0;
touch-action: none;
}
.cauldron-pot {
width: 250px;
height: 230px;
background: radial-gradient(circle at 35% 30%, #4a4a55 0%, #202025 50%, #0d0d12 100%);
border-radius: 50% 50% 45% 45% / 30% 30% 60% 60%;
border: 6px solid #1a1a20;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.9), inset 0 -10px 20px rgba(0, 0, 0, 0.8);
.cauldron-visual-container {
width: 100%;
max-width: 280px;
margin: 0.5rem auto;
position: relative;
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 25px;
}
.cauldron-pot::before {
content: '';
position: absolute;
top: 5px;
width: 210px;
height: 48px;
border-radius: 50%;
background: #111116;
border: 4px solid #2d2d38;
}
.cauldron-pot::after {
content: '';
position: absolute;
bottom: -16px;
width: 120px;
height: 18px;
background: #000;
border-radius: 50%;
filter: blur(8px);
}
.potion-surface {
position: absolute;
top: 12px;
width: 190px;
height: 36px;
border-radius: 50%;
background: radial-gradient(ellipse at center, #2ecc71 0%, #16a085 60%, #0e5042 100%);
box-shadow: 0 0 20px #2ecc71;
overflow: hidden;
transition: background 1s ease, box-shadow 1s ease;
}
.potion-surface.state-stir {
background: radial-gradient(ellipse at center, #9b59b6 0%, #8e44ad 60%, #4a235a 100%);
box-shadow: 0 0 25px #9b59b6;
}
.potion-surface.state-heat {
background: radial-gradient(ellipse at center, #e67e22 0%, #d35400 60%, #78281f 100%);
box-shadow: 0 0 25px #e67e22;
}
.potion-surface.state-gold {
background: radial-gradient(ellipse at center, #fff6a5 0%, #f1c40f 50%, #b7950b 100%);
box-shadow: 0 0 35px #f1c40f, 0 0 60px rgba(241, 196, 15, 0.6);
animation: goldShimmer 1.5s infinite alternate;
}
@keyframes goldShimmer {
0% { filter: brightness(1) drop-shadow(0 0 10px #f1c40f); }
100% { filter: brightness(1.3) drop-shadow(0 0 30px #fff); }
}
.cauldron-fire {
position: absolute;
bottom: 5px;
font-size: 2.2rem;
animation: flameFlicker 0.8s infinite alternate;
pointer-events: none;
}
/* Stir Interactive Ring */
.stir-circle {
position: absolute;
width: 180px;
height: 180px;
border-radius: 50%;
border: 3px dashed var(--gold);
display: none;
align-items: center;
justify-content: center;
pointer-events: auto;
cursor: grab;
animation: rotateDashed 6s linear infinite;
}
.stir-circle.active {
display: flex;
.cauldron-svg {
width: 100%;
height: auto;
max-height: 230px;
filter: drop-shadow(0 15px 25px rgba(0, 0, 0, 0.85));
user-select: none;
-webkit-user-select: none;
}
.stir-arrow-hint {
font-size: 1.8rem;
position: absolute;
top: 5px;
right: 35px;
.bubble-anim {
animation: bubbleRise 1.6s infinite ease-in;
transform-origin: center;
}
.b1 { animation-delay: 0.1s; }
.b2 { animation-delay: 0.5s; }
.b3 { animation-delay: 0.9s; }
.b4 { animation-delay: 1.2s; }
.b5 { animation-delay: 0.3s; }
@keyframes bubbleRise {
0% { transform: translateY(5px) scale(0.6); opacity: 0; }
50% { opacity: 0.8; transform: translateY(-3px) scale(1.1); }
100% { transform: translateY(-12px) scale(0.3); opacity: 0; }
}
.animated-flames {
animation: flamePulse 1.2s infinite alternate ease-in-out;
transform-origin: center 260px;
}
@keyframes flamePulse {
0% { transform: scaleY(0.9) scaleX(0.95); opacity: 0.85; filter: drop-shadow(0 0 6px #e74c3c); }
100% { transform: scaleY(1.1) scaleX(1.05); opacity: 1; filter: drop-shadow(0 0 16px #f39c12); }
}
@keyframes stirSpoonAnim {
0% { transform: rotate(-8deg) translateX(-10px); }
50% { transform: rotate(8deg) translateX(10px); }
100% { transform: rotate(-8deg) translateX(-10px); }
}
.stirring-active {
animation: stirSpoonAnim 0.6s infinite ease-in-out;
transform-origin: 160px 40px;
}
@keyframes ingredientDropAnim {
0% { transform: translateY(-70px) scale(0.4); opacity: 0; }
60% { transform: translateY(10px) scale(1.2); opacity: 1; }
100% { transform: translateY(20px) scale(0.2); opacity: 0; }
}
@keyframes rotateDashed {
@@ -1650,28 +1599,35 @@ body {
}
.pixie-target {
display: none; /* Crucial bugfix: hidden until active! */
font-size: clamp(2.4rem, 8vw, 3.4rem);
position: absolute;
transform: translateY(120%);
transition: transform 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28);
pointer-events: none; /* Let hole handle tap directly! */
pointer-events: none;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.8));
animation: pixieFloat 0.8s infinite alternate ease-in-out;
user-select: none;
}
.pixie-target.active {
transform: translateY(0);
display: block;
animation: pixiePopIn 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28), pixieFloat 0.8s infinite 0.2s alternate ease-in-out;
}
.pixie-target.frozen {
filter: drop-shadow(0 0 12px #3498db) hue-rotate(180deg) brightness(1.2);
display: block;
filter: drop-shadow(0 0 16px #3498db) hue-rotate(180deg) brightness(1.3);
animation: none;
transform: translateY(0) scale(1.08);
transform: scale(1.15);
}
.pixie-target.neville {
display: block;
font-size: clamp(2.6rem, 9vw, 3.6rem);
animation: shakeNeville 0.4s infinite alternate;
animation: shakeNeville 0.35s infinite alternate;
}
@keyframes pixiePopIn {
0% { transform: translateY(60px) scale(0.3); opacity: 0; }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes pixieFloat {
@@ -1680,8 +1636,26 @@ body {
}
@keyframes shakeNeville {
0% { transform: translateY(0) rotate(-5deg); }
100% { transform: translateY(0) rotate(5deg); }
0% { transform: translateY(0) rotate(-6deg); }
100% { transform: translateY(0) rotate(6deg); }
}
/* Flight Start Overlay */
.flight-start-overlay {
position: absolute;
inset: 0;
background: rgba(12, 10, 20, 0.9);
border: 2px solid var(--gold);
border-radius: 14px;
z-index: 25;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1.5rem;
text-align: center;
backdrop-filter: blur(4px);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.9);
}
/* Mobile responsive adjustments */
+86 -52
View File
@@ -1,4 +1,4 @@
// Zaubertrank-Brauküche (Felix Felicis Potion Brewing Engine - Mobile First)
// Zaubertrank-Brauküche (Felix Felicis Potion Brewing Engine - SVG Overhaul)
(function() {
let currentStep = 1; // 1: Ingredients, 2: Stirring, 3: Heat, 4: Done
const ingredients = {
@@ -16,9 +16,18 @@
const stepBadge = document.getElementById('stepBadge');
const stepInstruction = document.getElementById('stepInstruction');
const stepSubtext = document.getElementById('stepSubtext');
const ingredientsShelf = document.getElementById('ingredientsShelf');
const potionSurface = document.getElementById('potionSurface');
const stirCircle = document.getElementById('stirCircle');
const step1Wrapper = document.getElementById('step1Wrapper');
const totalIngredientsCount = document.getElementById('totalIngredientsCount');
// SVG Elements
const gradStop1 = document.getElementById('gradStop1');
const gradStop2 = document.getElementById('gradStop2');
const gradStop3 = document.getElementById('gradStop3');
const liquidSurface = document.getElementById('liquidSurface');
const stirSpoon = document.getElementById('stirSpoon');
const dropItemAnim = document.getElementById('dropItemAnim');
// Controls
const stirPanel = document.getElementById('stirPanel');
const stirCountSpan = document.getElementById('stirCount');
const stirBtn = document.getElementById('stirBtn');
@@ -26,7 +35,6 @@
const tempIndicator = document.getElementById('tempIndicator');
const heatTimerSpan = document.getElementById('heatTimer');
const successPanel = document.getElementById('successPanel');
const cauldronZone = document.getElementById('cauldronZone');
// Step 1: Add Ingredient
window.addIngredient = function(type) {
@@ -35,7 +43,7 @@
ingredients[type]++;
if (window.soundFX) window.soundFX.playBottleClink();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(25);
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(30);
// Update UI Badge
if (type === 'dragon') {
@@ -49,14 +57,29 @@
if (ingredients.star >= targetIngredients.star) document.getElementById('ingStar').classList.add('done');
}
// Ripple effect on surface
potionSurface.style.transform = 'scale(1.12)';
setTimeout(() => potionSurface.style.transform = 'scale(1)', 200);
const total = ingredients.dragon + ingredients.mandrake + ingredients.star;
if (totalIngredientsCount) totalIngredientsCount.innerText = total;
// Visual drop animation in SVG
if (dropItemAnim) {
const icons = { dragon: '🐉', mandrake: '🌿', star: '✨' };
dropItemAnim.textContent = icons[type];
dropItemAnim.style.display = 'block';
dropItemAnim.style.animation = 'ingredientDropAnim 0.5s ease-out forwards';
setTimeout(() => {
dropItemAnim.style.display = 'none';
}, 550);
}
// Ripple pulse on liquid
if (liquidSurface) {
liquidSurface.style.transform = 'scale(1.06)';
setTimeout(() => liquidSurface.style.transform = 'scale(1)', 200);
}
// Check if all ingredients added
const total = ingredients.dragon + ingredients.mandrake + ingredients.star;
if (total === 4) {
setTimeout(startStep2, 500);
setTimeout(startStep2, 600);
}
};
@@ -67,11 +90,19 @@
currentStep = 2;
stepBadge.innerText = "Schritt 2: Gleichmäßig rühren";
stepInstruction.innerText = "Rühre den Kessel 3-mal um!";
stepSubtext.innerText = "Tippe auf den großen Rühr-Button oder wische über den Kessel.";
ingredientsShelf.style.display = 'none';
stirPanel.style.display = 'flex';
stirCircle.classList.add('active');
potionSurface.className = 'potion-surface state-stir';
stepSubtext.innerText = "Tippe auf den großen Rühr-Button, um die Essenzen zu vermengen.";
if (step1Wrapper) step1Wrapper.style.display = 'none';
if (stirPanel) stirPanel.style.display = 'flex';
// Show wooden spoon in cauldron
if (stirSpoon) stirSpoon.style.display = 'block';
// Shift liquid color to mystical purple
if (gradStop1 && gradStop2 && gradStop3) {
gradStop1.setAttribute('stop-color', '#9b59b6');
gradStop2.setAttribute('stop-color', '#8e44ad');
gradStop3.setAttribute('stop-color', '#341f47');
}
if (window.soundFX) window.soundFX.playStoneSlide();
}
@@ -79,37 +110,28 @@
window.doStir = function() {
if (currentStep !== 2) return;
completedStirs++;
stirCountSpan.innerText = completedStirs;
if (stirCountSpan) stirCountSpan.innerText = completedStirs;
if (stirBtn) stirBtn.innerHTML = `🥄 Kessel umrühren! (<span>${completedStirs}</span> / 3)`;
if (window.soundFX) window.soundFX.playBottleClink();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(35);
// Spin animation on surface
potionSurface.style.transform = 'rotate(' + (completedStirs * 180) + 'deg) scale(1.1)';
setTimeout(() => potionSurface.style.transform = 'rotate(' + (completedStirs * 180) + 'deg) scale(1)', 250);
// Animate stirring spoon
if (stirSpoon) {
stirSpoon.classList.remove('stirring-active');
void stirSpoon.offsetWidth; // re-flow
stirSpoon.classList.add('stirring-active');
}
if (completedStirs >= 3) {
stirCircle.classList.remove('active');
stirPanel.style.display = 'none';
setTimeout(startStep3, 500);
setTimeout(() => {
if (stirSpoon) stirSpoon.style.display = 'none';
if (stirPanel) stirPanel.style.display = 'none';
startStep3();
}, 600);
}
};
// Cauldron tap / swipe to stir as well
let touchStartX = 0;
cauldronZone.addEventListener('pointerdown', (e) => {
if (currentStep === 2) {
touchStartX = e.clientX;
}
});
cauldronZone.addEventListener('pointerup', (e) => {
if (currentStep === 2) {
window.doStir();
}
});
// Step 3: Heat Control
let currentTemp = 35; // percent (0-100)
let heatTimer = 0.0;
@@ -118,10 +140,16 @@
function startStep3() {
currentStep = 3;
stepBadge.innerText = "Schritt 3: Hitze balancieren";
stepInstruction.innerText = "Pumpe mit dem Blasebalg, um die Hitze im grünen Bereich zu halten!";
stepSubtext.innerText = "Halte die Anzeige für 3 Sekunden im grünen Idealbereich (50% - 85%).";
potionSurface.className = 'potion-surface state-heat';
heatPanel.classList.add('active');
stepInstruction.innerText = "Pumpe mit dem Blasebalg, um die Flammen zu nähren!";
stepSubtext.innerText = "Halte die Anzeige für 3 Sekunden im grünen Idealbereich (45% - 85%).";
if (heatPanel) heatPanel.classList.add('active');
// Shift liquid color to fiery orange
if (gradStop1 && gradStop2 && gradStop3) {
gradStop1.setAttribute('stop-color', '#e67e22');
gradStop2.setAttribute('stop-color', '#d35400');
gradStop3.setAttribute('stop-color', '#561d00');
}
if (window.soundFX) window.soundFX.playWhoosh();
@@ -144,17 +172,17 @@
function updateHeatLoop() {
if (currentStep !== 3) return;
// Slow cooling down
// Gentle cooling down
currentTemp = Math.max(0, currentTemp - 0.45);
tempIndicator.style.width = currentTemp + '%';
if (tempIndicator) tempIndicator.style.width = currentTemp + '%';
// Check ideal zone (50% to 85%) - forgiving on mobile!
if (currentTemp >= 50 && currentTemp <= 85) {
// Check ideal zone (45% to 85%) - forgiving on mobile!
if (currentTemp >= 45 && currentTemp <= 85) {
heatTimer += 0.05;
heatTimerSpan.innerText = heatTimer.toFixed(1);
tempIndicator.style.background = '#2ecc71';
if (heatTimerSpan) heatTimerSpan.innerText = heatTimer.toFixed(1);
if (tempIndicator) tempIndicator.style.background = '#2ecc71';
} else {
tempIndicator.style.background = 'linear-gradient(90deg, #3498db, #f39c12, #e74c3c)';
if (tempIndicator) tempIndicator.style.background = 'linear-gradient(90deg, #3498db, #f39c12, #e74c3c)';
}
if (heatTimer >= 3.0) {
@@ -172,10 +200,16 @@
stepBadge.style.color = "#2ecc71";
stepInstruction.innerText = "Felix Felicis strahlt in reinem Gold!";
stepSubtext.innerText = "Dein Zaubertrank ist perfekt gelungen.";
stepSubtext.innerText = "Dein Zaubertrank ist meisterhaft gelungen.";
heatPanel.classList.remove('active');
potionSurface.className = 'potion-surface state-gold';
if (heatPanel) heatPanel.classList.remove('active');
// Turn liquid into sparkling golden elixir
if (gradStop1 && gradStop2 && gradStop3) {
gradStop1.setAttribute('stop-color', '#fff6a5');
gradStop2.setAttribute('stop-color', '#ffd700');
gradStop3.setAttribute('stop-color', '#b7950b');
}
if (window.soundFX) window.soundFX.playMagicSuccess();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate([50, 100, 150]);
@@ -191,6 +225,6 @@
console.error("Brew complete error:", e);
}
successPanel.style.display = 'block';
if (successPanel) successPanel.style.display = 'block';
}
})();
+31 -7
View File
@@ -27,12 +27,36 @@
// Game Configuration
const TOTAL_RINGS_NEEDED = 12;
let gameRunning = true;
let gameRunning = false; // Does not start automatically!
let startTime = Date.now();
let score = 0;
let ringsCollected = 0;
let combo = 0;
window.startFlight = function() {
const overlay = document.getElementById('broomStartOverlay');
if (overlay) overlay.style.display = 'none';
rings.length = 0;
rings.push({ x: width + 60, y: height * 0.4, radius: 25, speed: 3.5, golden: false, collected: false, scale: 1, alpha: 1, pulse: 0 });
rings.push({ x: width + 290, y: height * 0.65, radius: 25, speed: 3.6, golden: true, collected: false, scale: 1, alpha: 1, pulse: 0 });
rings.push({ x: width + 530, y: height * 0.3, radius: 25, speed: 3.7, golden: false, collected: false, scale: 1, alpha: 1, pulse: 0 });
score = 0;
ringsCollected = 0;
combo = 0;
if (scoreVal) scoreVal.innerText = score;
if (ringsVal) ringsVal.innerText = `0/${TOTAL_RINGS_NEEDED}`;
if (comboVal) comboVal.innerText = '';
startTime = Date.now();
lastRingSpawn = Date.now();
gameRunning = true;
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(40);
};
// Player (Broomstick)
const player = {
x: 90,
@@ -206,13 +230,13 @@
btnBoost.addEventListener('pointerleave', endBoost);
}
// Pre-spawn 3 rings
rings.push({ x: width + 50, y: height * 0.4, radius: 25, speed: 3.5, golden: false, collected: false, scale: 1, alpha: 1, pulse: 0 });
rings.push({ x: width + 280, y: height * 0.65, radius: 25, speed: 3.6, golden: true, collected: false, scale: 1, alpha: 1, pulse: 0 });
rings.push({ x: width + 520, y: height * 0.3, radius: 25, speed: 3.7, golden: false, collected: false, scale: 1, alpha: 1, pulse: 0 });
function update() {
if (!gameRunning) return;
if (!gameRunning) {
// Idle gentle float before start
player.y = (height * 0.5) + Math.sin(Date.now() * 0.003) * 8;
player.angle = Math.sin(Date.now() * 0.003) * 0.04;
return;
}
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
if (timerVal) timerVal.innerText = elapsedSec + 's';
+15
View File
@@ -69,6 +69,21 @@
{ x: 0.34, y: 0.52 },
{ x: 0.34, y: 0.80 }
]
},
{
id: 'expecto_patronum',
name: 'Expecto Patronum 🦌',
incantation: '"Expecto Patronum!" (Meister-Schutzzauber)',
desc: 'Verbinde die 5 Sterne des Lichts im Pentagramm (1 ➔ 2 ➔ 3 ➔ 4 ➔ 5), um die Rüstung endgültig zu bannen!',
color: '#ffffff',
beamColor: '#bbf2f6',
points: [
{ x: 0.35, y: 0.18 },
{ x: 0.50, y: 0.65 },
{ x: 0.18, y: 0.35 },
{ x: 0.52, y: 0.35 },
{ x: 0.20, y: 0.65 }
]
}
];
+1
View File
@@ -92,6 +92,7 @@
targetEl.classList.remove('active');
targetEl.classList.remove('frozen');
targetEl.classList.remove('neville');
targetEl.innerText = '';
}
activeTargets.delete(holeIdx);
}