Mobile First Overhaul: Touch gestures, dual tap/drag, instant responsiveness and ergonomic controls across all mini games

This commit is contained in:
Maxi
2026-09-22 19:57:56 +02:00
parent 2ce944d1cd
commit 5551c4e54f
13 changed files with 443 additions and 319 deletions
Binary file not shown.
+59 -18
View File
@@ -1551,12 +1551,13 @@ body {
box-shadow: inset 0 8px 12px rgba(0,0,0,0.8), inset 0 -8px 12px rgba(0,0,0,0.8);
position: relative;
user-select: none;
touch-action: pan-y;
}
.dial-symbol {
font-size: 2.4rem;
font-size: clamp(1.6rem, 5vw, 2.3rem);
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.8));
transition: transform 0.2s ease;
transition: transform 0.15s ease, opacity 0.15s ease;
}
.dial-label {
@@ -1576,7 +1577,7 @@ body {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
gap: 0.75rem;
}
.pixies-hud {
@@ -1591,18 +1592,37 @@ body {
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
}
.arena-start-overlay {
position: absolute;
inset: 0;
background: rgba(16, 11, 23, 0.92);
border: 3px solid var(--gold);
border-radius: 16px;
z-index: 20;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1rem;
text-align: center;
box-shadow: 0 0 35px rgba(0, 0, 0, 0.95);
backdrop-filter: blur(4px);
}
.pixies-arena {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.75rem;
gap: 0.5rem;
width: 100%;
aspect-ratio: 1 / 1;
background: linear-gradient(135deg, #1f1728, #100b17);
border: 3px solid #4a3461;
border-radius: 16px;
padding: 0.75rem;
padding: 0.5rem;
box-shadow: inset 0 0 25px rgba(0,0,0,0.8);
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
.pixie-hole {
@@ -1616,6 +1636,8 @@ body {
align-items: center;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
box-shadow: inset 0 6px 10px rgba(0,0,0,0.7);
}
@@ -1628,11 +1650,11 @@ body {
}
.pixie-target {
font-size: 3.2rem;
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: auto;
pointer-events: none; /* Let hole handle tap directly! */
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.8));
animation: pixieFloat 0.8s infinite alternate ease-in-out;
}
@@ -1648,7 +1670,7 @@ body {
}
.pixie-target.neville {
font-size: 3.4rem;
font-size: clamp(2.6rem, 9vw, 3.6rem);
animation: shakeNeville 0.4s infinite alternate;
}
@@ -1662,23 +1684,42 @@ body {
100% { transform: translateY(0) rotate(5deg); }
}
@media (max-width: 480px) {
/* Mobile responsive adjustments */
@media (max-width: 600px) {
.container {
padding: 0.5rem;
}
.parchment-scroll {
padding: 0.75rem 0.9rem;
margin-bottom: 0.75rem;
}
.parchment-title {
font-size: 1.35rem;
}
.riddle-poem {
font-size: 1rem !important;
line-height: 1.4 !important;
margin: 0.25rem 0 !important;
}
.cauldron-wrapper {
width: 240px;
height: 240px;
width: 220px;
height: 220px;
}
.cauldron-pot {
width: 215px;
height: 200px;
width: 200px;
height: 185px;
}
.dial-cylinder {
height: 75px;
height: 70px;
}
.dial-symbol {
font-size: 2rem;
.dial-step-btn {
height: 38px;
}
.pixie-target {
font-size: 2.6rem;
.cryptex-box {
padding: 0.75rem 0.4rem;
}
.cryptex-rings-row {
gap: 0.25rem;
}
}
+44 -75
View File
@@ -1,4 +1,4 @@
// Zaubertrank-Brauküche (Felix Felicis Potion Brewing Engine)
// Zaubertrank-Brauküche (Felix Felicis Potion Brewing Engine - Mobile First)
(function() {
let currentStep = 1; // 1: Ingredients, 2: Stirring, 3: Heat, 4: Done
const ingredients = {
@@ -19,9 +19,9 @@
const ingredientsShelf = document.getElementById('ingredientsShelf');
const potionSurface = document.getElementById('potionSurface');
const stirCircle = document.getElementById('stirCircle');
const stirTracker = document.getElementById('stirTracker');
const stirCounterText = document.getElementById('stirCounterText');
const stirPanel = document.getElementById('stirPanel');
const stirCountSpan = document.getElementById('stirCount');
const stirBtn = document.getElementById('stirBtn');
const heatPanel = document.getElementById('heatPanel');
const tempIndicator = document.getElementById('tempIndicator');
const heatTimerSpan = document.getElementById('heatTimer');
@@ -35,6 +35,7 @@
ingredients[type]++;
if (window.soundFX) window.soundFX.playBottleClink();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(25);
// Update UI Badge
if (type === 'dragon') {
@@ -49,105 +50,65 @@
}
// Ripple effect on surface
potionSurface.style.transform = 'scale(1.08)';
potionSurface.style.transform = 'scale(1.12)';
setTimeout(() => potionSurface.style.transform = 'scale(1)', 200);
// Check if all ingredients added
const total = ingredients.dragon + ingredients.mandrake + ingredients.star;
if (total === 4) {
setTimeout(startStep2, 600);
setTimeout(startStep2, 500);
}
};
// Step 2: Stirring
let completedStirs = 0;
let lastAngle = null;
let accumulatedAngle = 0;
function startStep2() {
currentStep = 2;
stepBadge.innerText = "Schritt 2: Gleichmäßig rühren";
stepInstruction.innerText = "Rühre den Sud 3 Runden im Uhrzeigersinn!";
stepSubtext.innerText = "Führe deinen Finger oder die Maus im Kreis über dem Kessel oder tippe ihn an.";
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';
stirCounterText.style.display = 'block';
stirPanel.style.display = 'flex';
stirCircle.classList.add('active');
potionSurface.className = 'potion-surface state-stir';
if (window.soundFX) window.soundFX.playStoneSlide();
}
// Touch / Pointer stirring logic
let isStirring = false;
function getAngle(e) {
const rect = cauldronZone.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
return Math.atan2(clientY - centerY, clientX - centerX) * (180 / Math.PI);
}
function onStirStart(e) {
window.doStir = function() {
if (currentStep !== 2) return;
isStirring = true;
lastAngle = getAngle(e);
}
function onStirMove(e) {
if (currentStep !== 2 || !isStirring) return;
e.preventDefault();
const angle = getAngle(e);
if (lastAngle !== null) {
let diff = angle - lastAngle;
// Handle wrap-around
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
// Only count clockwise
if (diff > 0) {
accumulatedAngle += diff;
if (accumulatedAngle >= 360) {
accumulatedAngle -= 360;
incrementStir();
}
}
}
lastAngle = angle;
}
function onStirEnd() {
isStirring = false;
lastAngle = null;
}
function incrementStir() {
completedStirs++;
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);
if (completedStirs >= 3) {
stirCircle.classList.remove('active');
stirCounterText.style.display = 'none';
setTimeout(startStep3, 600);
}
stirPanel.style.display = 'none';
setTimeout(startStep3, 500);
}
};
// Direct tap fallback for mobile
cauldronZone.addEventListener('click', () => {
if (currentStep === 2 && !isStirring) {
incrementStir();
// Cauldron tap / swipe to stir as well
let touchStartX = 0;
cauldronZone.addEventListener('pointerdown', (e) => {
if (currentStep === 2) {
touchStartX = e.clientX;
}
});
cauldronZone.addEventListener('mousedown', onStirStart);
window.addEventListener('mousemove', onStirMove);
window.addEventListener('mouseup', onStirEnd);
cauldronZone.addEventListener('touchstart', onStirStart, { passive: false });
window.addEventListener('touchmove', onStirMove, { passive: false });
window.addEventListener('touchend', onStirEnd);
cauldronZone.addEventListener('pointerup', (e) => {
if (currentStep === 2) {
window.doStir();
}
});
// Step 3: Heat Control
let currentTemp = 35; // percent (0-100)
@@ -158,7 +119,7 @@
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 Nadel für 4 Sekunden im grünen Bereich (60% - 85%).";
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');
@@ -169,19 +130,26 @@
window.pumpHeat = function() {
if (currentStep !== 3) return;
currentTemp = Math.min(100, currentTemp + 15);
currentTemp = Math.min(100, currentTemp + 18);
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(20);
const bellows = document.getElementById('bellowsBtn');
if (bellows) {
bellows.style.transform = 'scale(0.92)';
setTimeout(() => bellows.style.transform = 'scale(1)', 100);
}
};
function updateHeatLoop() {
if (currentStep !== 3) return;
// Slow cooling down
currentTemp = Math.max(0, currentTemp - 0.55);
currentTemp = Math.max(0, currentTemp - 0.45);
tempIndicator.style.width = currentTemp + '%';
// Check ideal zone (60% to 85%)
if (currentTemp >= 60 && currentTemp <= 85) {
// Check ideal zone (50% to 85%) - forgiving on mobile!
if (currentTemp >= 50 && currentTemp <= 85) {
heatTimer += 0.05;
heatTimerSpan.innerText = heatTimer.toFixed(1);
tempIndicator.style.background = '#2ecc71';
@@ -189,7 +157,7 @@
tempIndicator.style.background = 'linear-gradient(90deg, #3498db, #f39c12, #e74c3c)';
}
if (heatTimer >= 4.0) {
if (heatTimer >= 3.0) {
clearInterval(heatInterval);
finishPotionBrew();
}
@@ -210,6 +178,7 @@
potionSurface.className = 'potion-surface state-gold';
if (window.soundFX) window.soundFX.playMagicSuccess();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate([50, 100, 150]);
// API Completion Call
try {
+5
View File
@@ -142,6 +142,11 @@
player.boost = 1.0;
}, { passive: false });
canvas.addEventListener('touchcancel', () => {
touchSteering = false;
player.boost = 1.0;
});
// Pointer events for Desktop Mouse
canvas.addEventListener('pointerdown', e => {
if (e.pointerType === 'mouse') {
+48 -8
View File
@@ -1,4 +1,4 @@
// Magisches Runen-Drehschloss (Cryptex Engine)
// Magisches Runen-Drehschloss (Cryptex Engine - Mobile First)
(function() {
const DIALS = [
// Dial 0: Houses
@@ -49,9 +49,12 @@
const symEl = document.getElementById(`sym${dialIdx}`);
const lblEl = document.getElementById(`lbl${dialIdx}`);
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(20);
if (window.soundFX) window.soundFX.playStoneSlide();
// Quick flip animation
symEl.style.transform = dir > 0 ? 'translateY(15px) scale(0.8)' : 'translateY(-15px) scale(0.8)';
symEl.style.opacity = '0.3';
symEl.style.transform = dir > 0 ? 'translateY(12px) scale(0.85)' : 'translateY(-12px) scale(0.85)';
symEl.style.opacity = '0.4';
setTimeout(() => {
const currentItem = dial[state[dialIdx]];
@@ -59,11 +62,42 @@
lblEl.innerText = currentItem.name;
symEl.style.transform = 'translateY(0) scale(1)';
symEl.style.opacity = '1';
}, 100);
if (window.soundFX) window.soundFX.playStoneSlide();
}, 80);
};
// Mobile Swipe Handler for each cylinder
for (let i = 0; i < 4; i++) {
const cyl = document.getElementById(`cylinder${i}`);
if (!cyl) continue;
let startY = 0;
let isTouching = false;
cyl.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
startY = e.touches[0].clientY;
isTouching = true;
}
}, { passive: true });
cyl.addEventListener('touchend', (e) => {
if (!isTouching) return;
isTouching = false;
const endY = e.changedTouches[0].clientY;
const diff = endY - startY;
// If swiped more than 20px
if (Math.abs(diff) > 20) {
e.preventDefault();
if (diff < 0) {
window.stepDial(i, 1); // Swipe Up -> Next
} else {
window.stepDial(i, -1); // Swipe Down -> Prev
}
}
});
}
window.checkCryptex = async function() {
if (isUnlocked) return;
@@ -84,12 +118,15 @@
if (data.success) {
isUnlocked = true;
unlockBtn.style.display = 'none';
if (unlockBtn) unlockBtn.style.display = 'none';
if (window.soundFX) {
window.soundFX.playKeyChime();
window.soundFX.playMagicSuccess();
}
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate([60, 100, 150]);
}
// Show golden shine on cylinders
document.querySelectorAll('.dial-cylinder').forEach(c => {
@@ -111,10 +148,13 @@
setTimeout(() => {
if (successPanel) successPanel.style.display = 'block';
}, 800);
}, 600);
} else {
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate([40, 40]);
}
const box = document.querySelector('.cryptex-box');
box.style.transform = 'translateX(-8px)';
+117 -67
View File
@@ -1,4 +1,4 @@
// Wand Spell Tracing & Dueling Mini-Game (Das Zauberspruch-Duell)
// Wand Spell Tracing & Dueling Mini-Game (Das Zauberspruch-Duell - Mobile First)
(function() {
const canvas = document.getElementById('spellCanvas');
if (!canvas) return;
@@ -13,11 +13,13 @@
const modalText = document.getElementById('modalText');
const modalBtn = document.getElementById('modalBtn');
let width = 700, height = 440;
let width = 700, height = 400;
function resize() {
const rect = canvas.getBoundingClientRect();
width = Math.round(rect.width || canvas.parentElement.clientWidth || 700);
height = Math.round(Math.min(480, Math.max(280, width * 0.62)));
// Slightly taller on mobile so stars are large and easy to tap
const isMobile = width < 500;
height = Math.round(isMobile ? Math.min(360, Math.max(300, width * 0.85)) : Math.min(440, Math.max(320, width * 0.58)));
canvas.width = width;
canvas.height = height;
canvas.style.height = height + 'px';
@@ -30,43 +32,42 @@
id: 'flipendo',
name: 'Flipendo ⚡',
incantation: '"Flipendo!" (Ruck-Schockzauber)',
desc: 'Verbinde die 4 Runensterne im Zickzack-Muster, um den Schockzauber zu entfesseln!',
desc: 'Tippe oder ziehe die 4 Sterne von 1 bis 4 im Zickzack-Muster!',
color: '#00f2fe',
beamColor: '#4facfe',
// Relative coordinates (0 to 1) for responsive scaling
points: [
{ x: 0.22, y: 0.28 },
{ x: 0.18, y: 0.28 },
{ x: 0.52, y: 0.28 },
{ x: 0.25, y: 0.72 },
{ x: 0.55, y: 0.72 }
{ x: 0.20, y: 0.72 },
{ x: 0.54, y: 0.72 }
]
},
{
id: 'incendio',
name: 'Incendio 🔥',
incantation: '"Incendio!" (Feuer-Zauber)',
desc: 'Zeichne das lodernde Flammendreieck, um die Zielattrappe in Brand zu setzen!',
desc: 'Tippe oder ziehe die Sterne im Dreieck (1 ➔ 2 ➔ 3 ➔ 4), um Feuer zu entfachen!',
color: '#ff4500',
beamColor: '#ff8c00',
points: [
{ x: 0.38, y: 0.22 },
{ x: 0.55, y: 0.74 },
{ x: 0.22, y: 0.74 },
{ x: 0.38, y: 0.22 }
{ x: 0.36, y: 0.22 },
{ x: 0.54, y: 0.74 },
{ x: 0.18, y: 0.74 },
{ x: 0.36, y: 0.22 }
]
},
{
id: 'alohomora',
name: 'Alohomora 🗝️',
incantation: '"Alohomora!" (Entriegelungszauber)',
desc: 'Zeichne die magische Schlüsselloch-Kurve, um das verzauberte Siegel zu sprengen!',
desc: 'Verbinde die Sterne von 1 bis 4 in der Schlüsselloch-Kurve!',
color: '#ffd700',
beamColor: '#fff275',
points: [
{ x: 0.38, y: 0.25 },
{ x: 0.48, y: 0.38 },
{ x: 0.42, y: 0.52 },
{ x: 0.42, y: 0.78 }
{ x: 0.32, y: 0.22 },
{ x: 0.48, y: 0.36 },
{ x: 0.34, y: 0.52 },
{ x: 0.34, y: 0.80 }
]
}
];
@@ -81,6 +82,7 @@
let dummyShake = 0;
let spellCastAnim = 0;
let gameWon = false;
let resetTimer = null;
function setupSpellNodes() {
const spell = SPELLS[currentSpellIdx];
@@ -88,12 +90,13 @@
spellNodes = spell.points.map((pt, idx) => ({
x: Math.round(pt.x * width),
y: Math.round(pt.y * height),
radius: 20,
radius: width < 500 ? 24 : 20,
hit: idx < nextNodeIdx
}));
}
window.addEventListener('resize', resize);
window.addEventListener('orientationchange', () => setTimeout(resize, 200));
resize();
function updateSpellUI() {
@@ -107,7 +110,6 @@
}
updateSpellUI();
// Touch & Pointer Coordinates
function getCanvasCoords(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
return {
@@ -130,10 +132,46 @@
}
}
function checkNodeHit(x, y) {
if (nextNodeIdx >= spellNodes.length) return;
const targetNode = spellNodes[nextNodeIdx];
const dist = Math.hypot(x - targetNode.x, y - targetNode.y);
// Generous, thumb-friendly hit radius: 52px on mobile, 44px desktop
const hitRadius = width < 500 ? 52 : 44;
if (dist < hitRadius) {
targetNode.hit = true;
nextNodeIdx++;
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate(30);
}
if (window.soundFX) window.soundFX.playKeyChime();
addWandSpark(targetNode.x, targetNode.y, '#ffd700');
// Clear any pending idle reset
clearTimeout(resetTimer);
// Connect path to the node exactly
userPath.push({ x: targetNode.x, y: targetNode.y });
// All nodes connected for this spell!
if (nextNodeIdx >= spellNodes.length) {
castSpellSuccess();
}
}
}
function handleInputStart(x, y) {
if (gameWon) return;
isDrawing = true;
// If starting fresh or continuing
if (nextNodeIdx === 0) {
userPath = [{ x, y }];
} else {
userPath.push({ x, y });
}
checkNodeHit(x, y);
}
@@ -146,34 +184,23 @@
function handleInputEnd() {
isDrawing = false;
// If not all nodes hit, reset progress for this spell
if (nextNodeIdx < spellNodes.length) {
// MOBILE-FIRST IMPROVEMENT:
// DO NOT wipe nextNodeIdx on finger release!
// This allows players to either swipe continuously OR tap stars 1 -> 2 -> 3 -> 4!
// Only set an idle reset timeout if they abandon the spell for 10 seconds.
clearTimeout(resetTimer);
if (nextNodeIdx > 0 && nextNodeIdx < spellNodes.length) {
resetTimer = setTimeout(() => {
if (nextNodeIdx < spellNodes.length && !gameWon) {
nextNodeIdx = 0;
userPath = [];
setupSpellNodes();
}
}
function checkNodeHit(x, y) {
if (nextNodeIdx >= spellNodes.length) return;
const targetNode = spellNodes[nextNodeIdx];
const dist = Math.hypot(x - targetNode.x, y - targetNode.y);
// Tolerant hit radius for easy finger play: 36px
if (dist < 36) {
targetNode.hit = true;
nextNodeIdx++;
if (window.soundFX) window.soundFX.playKeyChime();
addWandSpark(targetNode.x, targetNode.y, '#ffd700');
// All nodes connected for this spell!
if (nextNodeIdx >= spellNodes.length) {
castSpellSuccess();
}
}, 10000);
}
}
// Touch events with touch-action none
// Touch events with passive false and touch-action none
canvas.addEventListener('touchstart', e => {
e.preventDefault();
if (e.touches.length > 0) {
@@ -195,7 +222,12 @@
handleInputEnd();
}, { passive: false });
// Pointer events for desktop
canvas.addEventListener('touchcancel', e => {
e.preventDefault();
handleInputEnd();
}, { passive: false });
// Pointer events for desktop mouse
canvas.addEventListener('pointerdown', e => {
if (e.pointerType === 'mouse') {
const p = getCanvasCoords(e.clientX, e.clientY);
@@ -217,11 +249,14 @@
function castSpellSuccess() {
const spell = SPELLS[currentSpellIdx];
spellCastAnim = 1.0;
dummyShake = 18;
dummyShake = 22;
if (window.soundFX) window.soundFX.playMagicSuccess();
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate([40, 60, 80]);
}
// Explosion on target dummy
const dummyX = width * 0.78;
const dummyX = width * 0.82;
const dummyY = height * 0.5;
for (let i = 0; i < 40; i++) {
const angle = Math.random() * Math.PI * 2;
@@ -267,12 +302,11 @@
modalText.innerHTML = `
Mit meisterhafter Zauberstabführung hast du alle 3 Zaubersprüche entfesselt und die verzauberte Rüstung bezwungen!<br><br>
<strong>Gelehrte Sprüche:</strong> Flipendo ⚡, Incendio 🔥 & Alohomora 🗝️<br><br>
Professor Flitwick applaudiert begeistert. Das uralte Schlosstor schwingt auf...
Dahinter flackern die unheimlichen Flammen von <strong>Snapes Flammenkammer</strong>!<br><br>
Die alte Rüstung verneigt sich ehrfürchtig. Das Tor öffnet sich und gibt den Weg zur dampfenden <strong>Zaubertrank-Brauküche</strong> frei!<br><br>
🎒 <em>Das goldene Duellanten-Abzeichen wurde deinem Zauberbeutel hinzugefügt!</em>
`;
modalBtn.innerText = "Weiter zu Snapes Flaschenrätsel 🧪";
modalBtn.onclick = () => { window.location.href = '/bottles'; };
modalBtn.innerText = "Weiter zur Zaubertrank-Brauküche ⚗️ ➔";
modalBtn.onclick = () => { window.location.href = '/brew'; };
modalBackdrop.classList.add('show');
}
@@ -302,12 +336,12 @@
if (spellCastAnim > 0) {
ctx.save();
ctx.strokeStyle = spell.beamColor;
ctx.lineWidth = 14 * spellCastAnim;
ctx.lineWidth = 16 * spellCastAnim;
ctx.shadowColor = spell.color;
ctx.shadowBlur = 30;
ctx.beginPath();
ctx.moveTo(width * 0.45, height * 0.5);
ctx.lineTo(width * 0.78, height * 0.5);
ctx.lineTo(width * 0.82, height * 0.5);
ctx.stroke();
ctx.restore();
spellCastAnim -= 0.05;
@@ -315,27 +349,27 @@
// Draw Enchanted Target Dummy (Right side)
ctx.save();
const dummyBaseX = width * 0.78 + (dummyShake > 0 ? (Math.random() - 0.5) * dummyShake : 0);
const dummyBaseX = width * 0.82 + (dummyShake > 0 ? (Math.random() - 0.5) * dummyShake : 0);
const dummyBaseY = height * 0.5;
if (dummyShake > 0) dummyShake *= 0.85;
// Dummy wooden post & shield
// Dummy wooden post
ctx.fillStyle = '#4a3319';
ctx.fillRect(dummyBaseX - 8, dummyBaseY - 60, 16, 120);
ctx.fillRect(dummyBaseX - 6, dummyBaseY - 50, 12, 100);
// Armor Breastplate
ctx.fillStyle = '#6c757d';
ctx.strokeStyle = '#e5b948';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(dummyBaseX, dummyBaseY - 10, 32, 0, Math.PI * 2);
ctx.arc(dummyBaseX, dummyBaseY - 10, 28, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// Armor Helmet
ctx.fillStyle = '#495057';
ctx.beginPath();
ctx.arc(dummyBaseX, dummyBaseY - 50, 20, 0, Math.PI * 2);
ctx.arc(dummyBaseX, dummyBaseY - 45, 18, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
@@ -345,15 +379,15 @@
ctx.shadowBlur = 15;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(dummyBaseX, dummyBaseY - 10, 42 + Math.sin(Date.now() * 0.008) * 4, 0, Math.PI * 2);
ctx.arc(dummyBaseX, dummyBaseY - 10, 38 + Math.sin(Date.now() * 0.008) * 4, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
// Draw Spell Tracing Guide Lines (Left / Center side)
// Draw Guide Lines between all nodes
if (spellNodes.length > 1 && !gameWon) {
ctx.save();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
ctx.setLineDash([8, 8]);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.setLineDash([6, 6]);
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(spellNodes[0].x, spellNodes[0].y);
@@ -364,7 +398,23 @@
ctx.restore();
}
// Draw Player's Drawn Line
// Draw Completed Connected Lines between hit nodes
if (nextNodeIdx > 0 && !gameWon) {
ctx.save();
ctx.strokeStyle = '#2ecc71';
ctx.lineWidth = 5;
ctx.shadowColor = '#2ecc71';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.moveTo(spellNodes[0].x, spellNodes[0].y);
for (let i = 1; i < nextNodeIdx; i++) {
ctx.lineTo(spellNodes[i].x, spellNodes[i].y);
}
ctx.stroke();
ctx.restore();
}
// Draw Active Player Finger Path
if (userPath.length > 1) {
ctx.save();
ctx.strokeStyle = spell.color;
@@ -382,21 +432,21 @@
ctx.restore();
}
// Draw Spell Nodes (Rune Stars to connect)
// Draw Spell Nodes (Stars with large numbers)
if (!gameWon) {
spellNodes.forEach((node, idx) => {
ctx.save();
const isNext = (idx === nextNodeIdx);
const isHit = node.hit;
ctx.shadowColor = isHit ? '#2ecc71' : (isNext ? spell.color : '#888');
ctx.shadowBlur = isNext ? 20 : 8;
ctx.shadowColor = isHit ? '#2ecc71' : (isNext ? spell.color : '#666');
ctx.shadowBlur = isNext ? 22 : 8;
ctx.fillStyle = isHit ? '#2ecc71' : (isNext ? '#ffffff' : '#332a42');
ctx.strokeStyle = isNext ? spell.color : '#e5b948';
ctx.fillStyle = isHit ? '#2ecc71' : (isNext ? '#ffffff' : '#221a30');
ctx.strokeStyle = isHit ? '#2ecc71' : (isNext ? spell.color : '#8c6e3b');
ctx.lineWidth = isNext ? 4 : 2;
const r = node.radius + (isNext ? Math.sin(Date.now() * 0.01) * 3 : 0);
const r = node.radius + (isNext ? Math.sin(Date.now() * 0.012) * 3 : 0);
ctx.beginPath();
ctx.arc(node.x, node.y, r, 0, Math.PI * 2);
ctx.fill();
@@ -404,7 +454,7 @@
// Number label
ctx.fillStyle = isHit ? '#000' : (isNext ? '#000' : '#e5b948');
ctx.font = 'bold 14px sans-serif';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(idx + 1, node.x, node.y);
+28 -17
View File
@@ -1,4 +1,4 @@
// Wichtel-Panik / Cornish Pixies Arcade Engine
// Wichtel-Panik / Cornish Pixies Arcade Engine (Mobile First)
(function() {
let score = 0;
const targetScore = 10;
@@ -10,12 +10,12 @@
// DOM Elements
const scoreEl = document.getElementById('pixieScore');
const timerEl = document.getElementById('pixieTimer');
const startBtn = document.getElementById('startPixieBtn');
const startOverlay = document.getElementById('startOverlay');
const startBtn = document.getElementById('startPixieBtn');
const successPanel = document.getElementById('pixieSuccess');
const holes = document.querySelectorAll('.pixie-hole');
const activeTargets = new Map(); // holeIndex -> { type: 'pixie'|'neville', timer: id }
const activeTargets = new Map(); // holeIndex -> { isNeville: bool, timer: id, clicked: bool }
window.startPixieHunt = function() {
if (gameRunning) return;
@@ -25,10 +25,11 @@
scoreEl.innerText = score;
timerEl.innerText = timeLeft;
startOverlay.style.display = 'none';
if (startOverlay) startOverlay.style.display = 'none';
if (successPanel) successPanel.style.display = 'none';
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(30);
// Start countdown
timerInterval = setInterval(() => {
@@ -45,7 +46,7 @@
function scheduleNextSpawn() {
if (!gameRunning) return;
const delay = Math.random() * 400 + 400; // 400ms - 800ms
const delay = Math.random() * 350 + 400; // 400ms - 750ms
spawnTimeout = setTimeout(() => {
spawnTarget();
scheduleNextSpawn();
@@ -66,13 +67,13 @@
const targetEl = document.getElementById(`target${holeIdx}`);
if (!targetEl) return;
// 15% chance Neville, 85% chance Pixie
const isNeville = Math.random() < 0.15;
// 12% chance Neville, 88% chance Pixie
const isNeville = Math.random() < 0.12;
targetEl.className = 'pixie-target active';
targetEl.innerText = isNeville ? '👦' : '🧚';
if (isNeville) targetEl.classList.add('neville');
const stayTime = Math.random() * 400 + 850; // 850ms - 1250ms
const stayTime = Math.random() * 450 + 900; // 900ms - 1350ms (forgiving for mobile)
const hideTimer = setTimeout(() => {
hideTarget(holeIdx);
@@ -95,11 +96,11 @@
activeTargets.delete(holeIdx);
}
// Touch / Click Handler on holes
// Pointerdown handler on holes (zero delay on touch devices!)
holes.forEach(hole => {
const idx = parseInt(hole.dataset.idx, 10);
function handleTap(e) {
function onHoleTrigger(e) {
if (!gameRunning) return;
e.preventDefault();
@@ -113,10 +114,12 @@
if (targetData.isNeville) {
// Clicked Neville -> Penalty!
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate([40, 40]);
score = Math.max(0, score - 2);
scoreEl.innerText = score;
targetEl.style.transform = 'scale(1.3) rotate(15deg)';
targetEl.style.transform = 'scale(1.25) rotate(15deg)';
if (window.showFloatingToast) {
window.showFloatingToast("🤕 <strong>Neville:</strong> »Aua! Ich bin doch kein Wichtel!« (-2 Pkt)");
}
@@ -132,6 +135,9 @@
if (window.soundFX) {
window.soundFX.playKeyChime();
}
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate(35);
}
score++;
scoreEl.innerText = score;
@@ -139,13 +145,13 @@
if (score >= targetScore) {
endGame(true);
} else {
setTimeout(() => hideTarget(idx), 400);
setTimeout(() => hideTarget(idx), 380);
}
}
}
hole.addEventListener('click', handleTap);
hole.addEventListener('touchstart', handleTap, { passive: false });
// Use pointerdown for instant touch & mouse response without delay
hole.addEventListener('pointerdown', onHoleTrigger);
});
async function endGame(won) {
@@ -159,6 +165,9 @@
if (won) {
if (window.soundFX) window.soundFX.playMagicSuccess();
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate([50, 100, 150]);
}
try {
const res = await fetch('/api/pixies/complete', {
@@ -178,10 +187,12 @@
} else {
if (window.soundFX) window.soundFX.playWhoosh();
startOverlay.style.display = 'block';
startBtn.innerText = "Erneut versuchen 🔄";
if (startOverlay) {
startOverlay.style.display = 'flex';
if (startBtn) startBtn.innerText = "Erneut versuchen 🔄";
}
if (window.showFloatingToast) {
window.showFloatingToast("⏰ Die Zeit ist abgelaufen! Die Wichtel haben gewonnen. Versuch es noch einmal!");
window.showFloatingToast("⏰ Die Zeit ist abgelaufen! Versuch es noch einmal.");
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;700&family=MedievalSharp&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/escape.css?v=2.0">
<link rel="stylesheet" href="/static/css/escape.css?v=3.0">
{% block head %}{% endblock %}
</head>
<body>
+29 -25
View File
@@ -5,10 +5,9 @@
{% block content %}
<div class="parchment-scroll">
<h1 class="parchment-title">⚗️ Kammer III: Die Zaubertrank-Brauküche ⚗️</h1>
<p class="riddle-poem" style="font-size: 1.25rem;">
"Dichte Dämpfe steigen aus dem massiven Kupferkessel auf.<br>
Um das nächste Kerkertor aufzuschmelzen, musst du einen legendären Trank vollenden:<br>
<strong>Felix Felicis – Das flüssige Gold des Glücks!</strong>"
<p class="riddle-poem" style="font-size: 1.15rem; margin: 0.3rem 0;">
"Snapes Kupferkessel dampft im Halbdunkel des Kerkers.<br>
Braue das flüssige Gold des Glücks: <strong>Felix Felicis</strong>!"
</p>
</div>
@@ -16,11 +15,11 @@
<!-- Recipe Instruction Scroll -->
<div class="recipe-book">
<span id="stepBadge" class="recipe-step-badge">Schritt 1: Zutaten zugeben</span>
<h3 id="stepInstruction" style="color: var(--gold); margin: 0.25rem 0; font-size: 1.35rem;">
<h3 id="stepInstruction" style="color: var(--gold); margin: 0.25rem 0; font-size: 1.25rem;">
Gib die 4 Essenzen in den Kessel!
</h3>
<p id="stepSubtext" style="color: var(--text-dim); font-size: 0.95rem; margin: 0;">
Tippe auf die Zutaten im Regal, um sie in den brodelnden Sud zu geben.
<p id="stepSubtext" style="color: var(--text-dim); font-size: 0.9rem; margin: 0;">
Tippe auf die Zutaten unten, um sie in den Kessel zu werfen.
</p>
</div>
@@ -28,8 +27,7 @@
<div class="cauldron-wrapper" id="cauldronZone">
<!-- Interactive Stir Ring Overlay -->
<div id="stirCircle" class="stir-circle">
<span class="stir-arrow-hint">↻</span>
<div id="stirTracker" style="width: 20px; height: 20px; background: var(--gold); border-radius: 50%; box-shadow: 0 0 10px #fff;"></div>
<span class="stir-arrow-hint">🥄</span>
</div>
<div class="cauldron-pot">
@@ -38,11 +36,6 @@
<div class="cauldron-fire">🔥</div>
</div>
<!-- Stir Status Hint -->
<div id="stirCounterText" style="display: none; color: var(--gold); font-size: 1.2rem; font-weight: bold;">
Rühre im Uhrzeigersinn: <span id="stirCount">0</span> / 3 Runden
</div>
<!-- Step 1: Ingredient Shelf -->
<div id="ingredientsShelf" class="ingredients-shelf">
<button type="button" class="ingredient-btn" id="ingDragon" onclick="addIngredient('dragon')">
@@ -62,35 +55,45 @@
</button>
</div>
<!-- Step 2: Stir Control Panel (Mobile Friendly!) -->
<div id="stirPanel" style="display: none; flex-direction: column; align-items: center; gap: 0.5rem; width: 100%;">
<button type="button" id="stirBtn" class="btn-magical" style="padding: 0.9rem 1.8rem; font-size: 1.15rem; width: 100%; max-width: 320px;" onclick="doStir()">
🥄 Kessel umrühren! (<span id="stirCount">0</span> / 3)
</button>
<span style="color: var(--text-dim); font-size: 0.85rem;">Tippe auf den Button oder wische über den Kessel</span>
</div>
<!-- Step 3: Heat Control Panel -->
<div id="heatPanel" class="heat-control-panel">
<div style="color: var(--text-light); font-size: 0.95rem;">
🌡️ Halte die Kesseltemperatur für <strong>4 Sekunden</strong> im grünen Bereich:
<div style="color: var(--text-light); font-size: 0.95rem; text-align: center;">
🌡️ Halte die Temperatur mit dem Blasebalg im grünen Bereich:
</div>
<div class="temp-bar-container">
<div class="temp-ideal-zone"></div>
<div id="tempIndicator" class="temp-indicator"></div>
</div>
<div style="display: flex; justify-content: space-between; width: 100%; max-width: 320px; font-size: 0.8rem; color: var(--text-dim);">
<span>Kalt (0°C)</span>
<span style="color: #2ecc71; font-weight: bold;">Ideal (70-90°C)</span>
<span>Überhitzt (120°C)</span>
<span>Kalt</span>
<span style="color: #2ecc71; font-weight: bold;">Ideal (50% - 85%)</span>
<span>Zu heiß!</span>
</div>
<div style="color: var(--gold); font-weight: bold; margin-top: 0.25rem;">
Ideale Hitze gehalten: <span id="heatTimer">0.0</span> / 4.0s
Ideale Hitze gehalten: <span id="heatTimer">0.0</span> / 3.0s
</div>
<button type="button" id="bellowsBtn" class="bellows-btn" onclick="pumpHeat()">
🔥 Blasebalg drücken!
🔥 Blasebalg pumpen!
</button>
</div>
<!-- Success Container -->
<div id="successPanel" style="display: none; text-align: center; margin-top: 1rem;">
<div id="successPanel" style="display: none; text-align: center; margin-top: 1rem; width: 100%;">
<div class="dungeon-card" style="border-color: #2ecc71;">
<div style="font-size: 3rem; margin-bottom: 0.5rem;">🧪✨🍀</div>
<h2 style="color: var(--gold); font-size: 1.8rem; margin-bottom: 0.5rem;">
✨ Felix Felicis vollendet! ✨
Felix Felicis vollendet!
</h2>
<p style="color: var(--text-light); max-width: 500px; margin: 0 auto 1.25rem auto;">
Der Kessel erstrahlt in goldenem Sonnenlicht! Eine Phiole des reinen flüssigen Glücks fließt magisch in deinen Zauberbeutel.
Der Kessel erstrahlt in gleißendem Gold! Eine Phiole des reinen flüssigen Glücks wandert in deinen <strong>Zauberbeutel</strong>.
</p>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
<a href="/cryptex" class="btn-magical">Zur nächsten Kammer: Das Drehschloss 🔐➔</a>
@@ -98,8 +101,9 @@
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/brew.js?v=1.0"></script>
<script src="/static/js/brew.js?v=2.0"></script>
{% endblock %}
+1 -1
View File
@@ -20,7 +20,7 @@
<div id="comboVal" style="color: #00ffff; font-weight: bold; min-width: 80px;"></div>
</div>
<canvas id="broomCanvas"></canvas>
<canvas id="broomCanvas" style="touch-action: none; -webkit-user-select: none; user-select: none; width: 100%; display: block;"></canvas>
</div>
<!-- Mobile Flight Cockpit & D-Pad -->
+25 -27
View File
@@ -5,12 +5,9 @@
{% block content %}
<div class="parchment-scroll">
<h1 class="parchment-title">🔐 Kammer IV: Das Runen-Drehschloss 🔐</h1>
<p class="riddle-poem" style="font-size: 1.25rem;">
"Vor dir ragt ein gewaltiges Tresortor mit einem massiven Messing-Zylinderschloss.<br>
In die Steinwand über dem Schloss ist ein altes Gründer-Rätsel gemeißelt:<br><br>
<em>»Vier Siegel schloss der Gründer Rat,<br>
bevor man je die Kammer betrat.<br>
Der <strong>Löwe</strong> wacht am Himmelstor,<br>
<p class="riddle-poem" style="font-size: 1.15rem; margin: 0.3rem 0;">
"Vor dir ragt das Tresortor der vier Gründer mit vier rotierenden Messingwalzen.<br>
<em>»Der <strong>Löwe</strong> wacht am Himmelstor,<br>
wo nachts die <strong>Silbersichel</strong> glimmt,<br>
das ew’ge <strong>Feuer</strong> niemals stirbt,<br>
bis der <strong>Zauberstab</strong> die Siegel bricht.«</em>"
@@ -26,64 +23,65 @@
<div class="cryptex-box">
<div class="cryptex-frame-line"></div>
<div class="cryptex-rings-row">
<!-- Dial 1 -->
<!-- Dial 0 -->
<div class="cryptex-dial" id="dialCol0">
<button type="button" class="dial-step-btn" onclick="stepDial(0, -1)" title="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" onclick="stepDial(0, 1)">
<button type="button" class="dial-step-btn" onclick="stepDial(0, -1)" aria-label="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" id="cylinder0" onclick="stepDial(0, 1)">
<div class="dial-symbol" id="sym0">🦁</div>
<div class="dial-label" id="lbl0">Löwe</div>
</div>
<button type="button" class="dial-step-btn" onclick="stepDial(0, 1)" title="Nächstes Symbol">▼</button>
<button type="button" class="dial-step-btn" onclick="stepDial(0, 1)" aria-label="Nächstes Symbol">▼</button>
</div>
<!-- Dial 2 -->
<!-- Dial 1 -->
<div class="cryptex-dial" id="dialCol1">
<button type="button" class="dial-step-btn" onclick="stepDial(1, -1)" title="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" onclick="stepDial(1, 1)">
<button type="button" class="dial-step-btn" onclick="stepDial(1, -1)" aria-label="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" id="cylinder1" onclick="stepDial(1, 1)">
<div class="dial-symbol" id="sym1">☀️</div>
<div class="dial-label" id="lbl1">Sonne</div>
</div>
<button type="button" class="dial-step-btn" onclick="stepDial(1, 1)" title="Nächstes Symbol">▼</button>
<button type="button" class="dial-step-btn" onclick="stepDial(1, 1)" aria-label="Nächstes Symbol">▼</button>
</div>
<!-- Dial 3 -->
<!-- Dial 2 -->
<div class="cryptex-dial" id="dialCol2">
<button type="button" class="dial-step-btn" onclick="stepDial(2, -1)" title="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" onclick="stepDial(2, 1)">
<button type="button" class="dial-step-btn" onclick="stepDial(2, -1)" aria-label="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" id="cylinder2" onclick="stepDial(2, 1)">
<div class="dial-symbol" id="sym2">💧</div>
<div class="dial-label" id="lbl2">Wasser</div>
</div>
<button type="button" class="dial-step-btn" onclick="stepDial(2, 1)" title="Nächstes Symbol">▼</button>
<button type="button" class="dial-step-btn" onclick="stepDial(2, 1)" aria-label="Nächstes Symbol">▼</button>
</div>
<!-- Dial 4 -->
<!-- Dial 3 -->
<div class="cryptex-dial" id="dialCol3">
<button type="button" class="dial-step-btn" onclick="stepDial(3, -1)" title="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" onclick="stepDial(3, 1)">
<button type="button" class="dial-step-btn" onclick="stepDial(3, -1)" aria-label="Vorheriges Symbol">▲</button>
<div class="dial-cylinder" id="cylinder3" onclick="stepDial(3, 1)">
<div class="dial-symbol" id="sym3">🗝️</div>
<div class="dial-label" id="lbl3">Schlüssel</div>
</div>
<button type="button" class="dial-step-btn" onclick="stepDial(3, 1)" title="Nächstes Symbol">▼</button>
<button type="button" class="dial-step-btn" onclick="stepDial(3, 1)" aria-label="Nächstes Symbol">▼</button>
</div>
</div>
</div>
<!-- Unlock Action Button -->
<div style="margin-top: 0.5rem; width: 100%; display: flex; justify-content: center;">
<button type="button" id="unlockCryptexBtn" class="btn-magical" style="padding: 0.9rem 2rem; font-size: 1.2rem;" onclick="checkCryptex()">
<div style="margin-top: 0.5rem; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 0.4rem;">
<button type="button" id="unlockCryptexBtn" class="btn-magical" style="padding: 0.9rem 2.2rem; font-size: 1.25rem; width: 100%; max-width: 320px;" onclick="checkCryptex()">
🔓 Siegel entriegeln
</button>
<span style="color: var(--text-dim); font-size: 0.85rem;">Tippe auf ▲/▼ oder wische auf den Walzen</span>
</div>
<!-- Success Panel -->
<div id="cryptexSuccess" style="display: none; text-align: center; margin-top: 1rem; width: 100%;">
<div class="dungeon-card" style="border-color: #2ecc71;">
<div style="font-size: 3rem; margin-bottom: 0.5rem;">🗝️✨</div>
<div style="font-size: 3rem; margin-bottom: 0.5rem;">🗝️✨🏆</div>
<h2 style="color: var(--gold); font-size: 1.8rem; margin-bottom: 0.5rem;">
Das Tor der Gründer steht offen!
</h2>
<p style="color: var(--text-light); max-width: 500px; margin: 0 auto 1.25rem auto;">
Die Messingbolzen schnappen lautstark zurück! Der uralte <strong>Schlüssel der Gründer</strong> fällt in deine Hand und liegt nun in deinem Beutel.
Die Messingbolzen schnappen lautstark zurück! Der uralte <strong>Schlüssel der Gründer</strong> wandert in deinen <strong>Zauberbeutel</strong>.
</p>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
<a href="/pixies" class="btn-magical">Zur nächsten Prüfung: Wichtel-Panik 🧚➔</a>
@@ -95,5 +93,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/cryptex.js?v=1.0"></script>
<script src="/static/js/cryptex.js?v=2.0"></script>
{% endblock %}
+11 -12
View File
@@ -5,10 +5,9 @@
{% block content %}
<div class="parchment-scroll">
<h1 class="parchment-title">🪄 Kammer II: Das Zauberspruch-Duell ⚡</h1>
<p class="riddle-poem" style="font-size: 1.25rem; margin: 0.5rem 0;">
"Eine uralte verzauberte Rüstung bewacht die Pforte zur Flammenkammer.<br>
Zücke deinen Zauberstab! Verbinde die leuchtenden Runensterne mit dem Finger oder der Maus,<br>
um mächtige Hogwarts-Zaubersprüche zu entfesseln und die Rüstung zu bezwingen!"
<p class="riddle-poem" style="font-size: 1.15rem; margin: 0.3rem 0;">
"Zücke deinen Zauberstab! Verbinde die Runensterne <strong>1 bis 4</strong> (durch Ziehen oder Antippen),
um die Sprüche zu wirken und die verzauberte Rüstung zu bezwingen!"
</p>
</div>
@@ -22,22 +21,22 @@
<canvas id="spellCanvas" style="touch-action: none; -webkit-user-select: none; user-select: none; width: 100%; display: block;"></canvas>
</div>
<div class="dungeon-card" style="margin-top: 1.5rem; text-align: center;">
<p id="spellDesc" style="font-size: 1.15rem; color: var(--gold); margin-bottom: 0.6rem;">
<div class="dungeon-card" style="margin-top: 1rem; text-align: center; padding: 0.75rem 1rem;">
<p id="spellDesc" style="font-size: 1.1rem; color: var(--gold); margin-bottom: 0.4rem;">
Verbinde die leuchtenden Runensterne von 1 bis 4, um den Zauber zu wirken!
</p>
<p style="color: var(--text-dim); font-size: 0.95rem;">
💡 <em>Tipp: Ziehe deinen Finger oder die Maus flüssig durch die nummerierten Sterne auf dem Bildschirm.</em>
<p style="color: var(--text-dim); font-size: 0.9rem; margin: 0;">
💡 <em>Tipp: Ziehe mit dem Daumen über die Sterne oder tippe sie einfach der Reihe nach (1 ➔ 2 ➔ 3 ➔ 4) an!</em>
</p>
</div>
<div style="text-align: center; margin-top: 1.5rem;">
<a href="/bottles" class="btn-magical">
Direkt weiter zu Snapes Flammenkammer 🧪 ➔
<div style="text-align: center; margin-top: 1.25rem;">
<a href="/brew" class="btn-magical">
Direkt weiter zur Brauküche ⚗️ ➔
</a>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/duel.js?v=1.0"></script>
<script src="/static/js/duel.js?v=2.0"></script>
{% endblock %}
+17 -10
View File
@@ -5,9 +5,8 @@
{% block content %}
<div class="parchment-scroll">
<h1 class="parchment-title">🧚 Kammer V: Wichtel-Panik 🧚</h1>
<p class="riddle-poem" style="font-size: 1.25rem;">
"Lockharts Eisenkäfig ist aufgeplatzt! Ein Schwarm frecher kornischer Wichtel wirbelt durch den Raum.<br>
Banne <strong>10 Wichtel mit dem Einfrier-Zauber <em>»Immobilus!«</em> ❄️</strong> mit schnellen Fingern!<br>
<p class="riddle-poem" style="font-size: 1.15rem; margin: 0.3rem 0;">
"Lockharts Käfig ist zerbrochen! Banne <strong>10 Wichtel mit dem Einfrier-Zauber <em>»Immobilus!«</em> ❄️</strong>!<br>
<span style="color: #e74c3c;">Vorsicht: Tippe nicht auf Neville Longbottom (👦), der an der Decke baumelt!</span>"
</p>
</div>
@@ -23,6 +22,20 @@
</div>
</div>
<!-- Arena Container with Center Start Overlay -->
<div style="position: relative; width: 100%;">
<!-- Start & Restart Overlay (Directly over the arena!) -->
<div id="startOverlay" class="arena-start-overlay">
<div style="font-size: 2.8rem; margin-bottom: 0.4rem;">🧚⚡❄️</div>
<h3 style="color: var(--gold); margin: 0 0 0.4rem 0; font-size: 1.4rem;">Bist du bereit?</h3>
<p style="color: var(--text-light); font-size: 0.95rem; margin: 0 0 1.25rem 0; text-align: center; max-width: 280px;">
Tippe schnell auf die blauen Wichtel (🧚), um sie einzufrieren! Verschone Neville (👦)!
</p>
<button type="button" id="startPixieBtn" class="btn-magical" style="padding: 0.9rem 2.2rem; font-size: 1.25rem;" onclick="startPixieHunt()">
⚡ Jagd starten!
</button>
</div>
<!-- 3x3 Magical Grid Arena -->
<div class="pixies-arena" id="pixieArena">
<!-- Hole 0 -->
@@ -71,12 +84,6 @@
<div class="pixie-target" id="target8">🧚</div>
</div>
</div>
<!-- Start & Status Overlay -->
<div id="startOverlay" style="text-align: center; margin-top: 0.5rem; width: 100%;">
<button type="button" id="startPixieBtn" class="btn-magical" style="padding: 0.9rem 2.2rem; font-size: 1.25rem;" onclick="startPixieHunt()">
⚡ Jagd beginnen!
</button>
</div>
<!-- Success Panel -->
@@ -99,5 +106,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/pixies.js?v=1.0"></script>
<script src="/static/js/pixies.js?v=2.0"></script>
{% endblock %}