Penguin Dash: Overdrive Edition
by NovaGalaxy88809 lines33.3 KB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Penguin Dash: Overdrive Edition</title>
<style>
:root {
--bg-grad-top: #0f172a;
--bg-grad-mid: #1e1b4b;
--bg-grad-bottom: #2e1065;
--ground-color: #1e293b;
--ground-border: #38bdf8;
--accent-color: #06b6d4;
--panel-bg: rgba(15, 23, 42, 0.65);
--text-color: #f8fafc;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
background: #020617;
user-select: none;
-webkit-user-select: none;
color: var(--text-color);
}
.screen {
position: absolute;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
transition: all 0.3s ease-in-out;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: rgba(2, 6, 23, 0.8);
}
.hidden {
opacity: 0;
pointer-events: none;
transform: scale(1.05);
}
.glass-panel {
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 28px;
padding: 25px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
text-align: center;
max-width: 480px;
width: 92%;
box-sizing: border-box;
}
h1 { font-size: 2.5rem; color: var(--accent-color); margin-bottom: 5px; text-transform: uppercase; letter-spacing: 1px; margin-top: 0;}
h2 { font-size: 1.8rem; margin-top: 0; margin-bottom: 10px;}
.btn {
padding: 14px 20px;
font-size: 15px;
font-weight: 700;
border: none;
border-radius: 14px;
color: white;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.2s;
text-transform: uppercase;
letter-spacing: 0.5px;
width: 100%;
box-sizing: border-box;
}
.btn:active { transform: scale(0.96); }
.btn:disabled { background: #475569 !important; color: #94a3b8; cursor: not-allowed; box-shadow: none; }
.btn-primary { background: linear-gradient(135deg, #06b6d4, #3b82f6); box-shadow: 0 4px 14px rgba(6, 182, 212, 0.4); }
.btn-secondary { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); }
.btn-danger { background: linear-gradient(135deg, #ef4444, #b91c1c); }
#loadingScreen { background: #020617; z-index: 500; }
.loader-container { width: 80%; max-width: 300px; background: rgba(255, 255, 255, 0.05); height: 8px; border-radius: 4px; overflow: hidden; margin-top: 20px; }
.loader-bar { width: 0%; height: 100%; background: var(--accent-color); transition: width 0.1s linear; }
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
display: none;
background: linear-gradient(to bottom, var(--bg-grad-top), var(--bg-grad-mid), var(--bg-grad-bottom));
}
#gameCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 5;
}
#hud {
position: absolute;
top: max(20px, env(safe-area-inset-top));
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
pointer-events: none;
z-index: 50;
}
.hud-card {
background: rgba(2, 6, 23, 0.6);
padding: 10px 18px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.05);
font-weight: 600;
font-size: 1rem;
backdrop-filter: blur(8px);
}
.stat-val { color: var(--accent-color); }
.shop-tabs { display: flex; gap: 6px; margin-bottom: 15px; }
.tab-btn { flex: 1; background: rgba(255, 255, 255, 0.05); border: none; color: #94a3b8; padding: 10px 5px; border-radius: 10px; cursor: pointer; font-weight: 600; font-size: 13px; }
.tab-btn.active { background: var(--accent-color); color: #020617; }
.shop-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; max-height: 280px; overflow-y: auto; padding-right: 4px; }
.shop-item { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 14px; padding: 10px; display: flex; flex-direction: column; align-items: center; justify-content: space-between; }
.shop-item span { font-size: 28px; margin-bottom: 2px; }
.shop-item small { font-size: 11px; margin-bottom: 6px; }
.quest-list { max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; text-align: left; padding-right: 4px; }
.quest-item { background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.08); padding: 10px 14px; border-radius: 14px; display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.quest-item.ready { border-color: #eab308; background: rgba(234, 179, 8, 0.08); box-shadow: 0 0 10px rgba(234, 179, 8, 0.2); }
.quest-item.claimed { border-color: #10b981; background: rgba(16, 185, 129, 0.03); opacity: 0.6; }
.quest-reward-tag { font-size: 12px; color: #fbbf24; font-weight: bold; white-space: nowrap; background: rgba(251, 191, 36, 0.1); padding: 2px 6px; border-radius: 6px;}
.btn-quest-claim { padding: 6px 12px; font-size: 11px; border-radius: 8px; font-weight: bold; width: auto; text-transform: none; box-shadow: 0 2px 5px rgba(234,179,8,0.3); }
.quest-badge { font-size: 9px; text-transform: uppercase; padding: 1px 5px; border-radius: 4px; font-weight: bold; margin-right: 5px; display: inline-block; vertical-align: middle;}
.badge-bronze { background: #b45309; color: #fef3c7; }
.badge-silber { background: #4b5563; color: #f9fafb; }
.badge-gold { background: #713f12; color: #fef08a; border: 1px solid #eab308; }
#quest-notifier {
position: absolute;
bottom: 25px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: rgba(15, 23, 42, 0.95);
border: 1px solid #eab308;
border-radius: 10px;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 600;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
z-index: 150;
pointer-events: none;
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
#quest-notifier.show { transform: translateX(-50%) translateY(0); }
#toast-container { position: absolute; top: 40px; left: 50%; transform: translateX(-50%); z-index: 200; width: 80%; max-width: 300px; pointer-events: none; }
.toast { background: rgba(16, 185, 129, 0.95); color: white; padding: 12px 20px; border-radius: 12px; text-align: center; font-weight: 600; box-shadow: 0 10px 25px -5px rgba(0,0,0,0.3); margin-bottom: 8px; }
.daily-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 15px 0; }
.daily-day { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; padding: 8px 4px; font-size: 12px; text-align: center; }
.daily-day.completed { background: rgba(16, 185, 129, 0.2); border-color: #10b981; color: #10b981; font-weight: bold; }
.daily-day.current { background: rgba(6, 182, 212, 0.2); border-color: var(--accent-color); font-weight: bold; }
</style>
</head>
<body>
<!-- LOADER -->
<div id="loadingScreen" class="screen">
<h2 style="color: var(--accent-color);">Lade Daten...</h2>
<div class="loader-container"><div id="loaderBar" class="loader-bar"></div></div>
</div>
<!-- MAIN MENU -->
<div id="mainMenu" class="screen hidden">
<div class="glass-panel">
<h1>Penguin Dash</h1>
<p style="color: #94a3b8; margin-bottom: 20px; font-size: 0.95rem;">Weiche aus & sammle Coins!</p>
<div style="margin-bottom: 20px; font-size: 1.1rem; display: flex; justify-content: space-around;">
<div>High: <span id="menuHighScore" class="stat-val">0</span></div>
<div>Coins: <span id="menuTotalCoins" class="stat-val">0</span> 🪙</div>
</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
<button class="btn btn-primary" onclick="launchDirectGame()">Spiel Starten</button>
<button class="btn btn-secondary" onclick="openShop()">Shop & Skins 🛍️</button>
<button class="btn btn-secondary" onclick="openQuestsMenu()">Tages-Quests 🏆</button>
<button class="btn btn-secondary" onclick="togglePanel('dailyPanel'); updateDailyUI();">Belohnung 🎁</button>
</div>
</div>
</div>
<!-- QUESTS MENU PANEL -->
<div id="questsPanel" class="screen hidden">
<div class="glass-panel">
<h2>📅 Quests von Heute</h2>
<p style="color: #94a3b8; font-size: 11px; margin-top: -5px; margin-bottom: 15px;">Wechseln jede Nacht automatisch!</p>
<div id="questListContainer" class="quest-list"></div>
<button class="btn btn-danger" style="margin-top: 15px;" onclick="togglePanel('questsPanel')">Zurück</button>
</div>
</div>
<!-- DAILY REWARD -->
<div id="dailyPanel" class="screen hidden">
<div class="glass-panel">
<h2>🎁 Tägliche Belohnung</h2>
<div id="dailyGrid" class="daily-grid"></div>
<button id="claimDailyBtn" class="btn btn-primary" onclick="claimDailyReward()">Abholen!</button>
<button class="btn btn-danger" style="margin-top: 10px;" onclick="togglePanel('dailyPanel')">Zurück</button>
</div>
</div>
<!-- GAME OVER -->
<div id="gameOverScreen" class="screen hidden">
<div class="glass-panel">
<h2 style="color: #ef4444;">Game Over</h2>
<div style="margin: 20px 0; font-size: 1.2rem; display: flex; justify-content: space-around;">
<div>Score: <span id="finalScore" class="stat-val">0</span></div>
<div>Coins: <span id="finalCoins" class="stat-val">0</span></div>
</div>
<div style="display: flex; gap: 10px;">
<button class="btn btn-primary" onclick="launchDirectGame()">Nochmal</button>
<button class="btn btn-secondary" onclick="changeScreen('gameOverScreen', 'mainMenu')">Menü</button>
</div>
</div>
</div>
<!-- SHOP -->
<div id="shopPanel" class="screen hidden">
<div class="glass-panel">
<h2>🛍️ Shop</h2>
<div style="font-size: 1.1rem; margin-bottom: 12px;">Coins: <span id="shopCoinCount" class="stat-val">0</span> 🪙</div>
<div class="shop-tabs">
<button id="tabBtn-skins" class="tab-btn active" onclick="switchTab('skins')">Skins 🐧</button>
<button id="tabBtn-themes" class="tab-btn" onclick="switchTab('themes')">Welten 🎨</button>
<button id="tabBtn-upgrades" class="tab-btn" onclick="switchTab('upgrades')">Upgrades ⚡</button>
</div>
<div id="shopGrid" class="shop-grid"></div>
<button class="btn btn-danger" style="margin-top: 15px;" onclick="closeShop()">Schließen</button>
</div>
</div>
<!-- GAME CONTAINER -->
<div id="gameContainer">
<div id="hud">
<div class="hud-card">Score: <span id="hudScore" class="stat-val">0</span></div>
<div class="hud-card"><span id="hudCoins" class="stat-val">0</span> 🪙</div>
</div>
<canvas id="gameCanvas"></canvas>
<div id="quest-notifier">
<span style="font-size: 18px;">✨</span>
<div>
<div style="color: #eab308; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px;">Tages-Quest erfüllt!</div>
<div style="color: #fff; font-size: 12px;">Hol dir die Coins im Menü!</div>
</div>
</div>
</div>
<div id="toast-container"></div>
<script>
let state = {
highScore: parseInt(localStorage.getItem('pd_highscore')) || 0,
totalCoins: parseInt(localStorage.getItem('pd_coins')) || 0,
ownedSkins: JSON.parse(localStorage.getItem('pd_skins')) || ['🐧'],
activeSkin: localStorage.getItem('pd_activeskin') || '🐧',
ownedThemes: JSON.parse(localStorage.getItem('pd_themes')) || ['Arktis'],
activeTheme: localStorage.getItem('pd_activetheme') || 'Arktis',
coinMultiplierLevel: parseInt(localStorage.getItem('pd_up_coinmult')) || 1,
jumpForceLevel: parseInt(localStorage.getItem('pd_up_jumpforce')) || 1,
activeQuestIds: JSON.parse(localStorage.getItem('pd_daily_active_ids')) || [],
readyQuests: JSON.parse(localStorage.getItem('pd_quests_ready')) || [],
claimedQuests: JSON.parse(localStorage.getItem('pd_quests_claimed')) || [],
lastQuestDate: localStorage.getItem('pd_quest_date') || "",
lastDailyClaim: localStorage.getItem('pd_lastdaily') || ""
};
const ALL_SKINS = [
{ id: '🐧', name: 'Original', cost: 0 },
{ id: '🕶️', name: 'Cooler Pingu', cost: 25 },
{ id: '🥷', name: 'Ninja', cost: 60 },
{ id: '👑', name: 'König', cost: 120 },
{ id: '🚀', name: 'Astronaut', cost: 200 },
{ id: '🤖', name: 'Cyborg', cost: 300 },
{ id: '🔥', name: 'Phönix', cost: 400 },
{ id: '💎', name: 'Diamant', cost: 600 },
{ id: '🤡', name: 'Clown', cost: 700 },
{ id: '🦄', name: 'Einhorn', cost: 850 },
{ id: '🎃', name: 'Grusel-Kürbis', cost: 1000 },
{ id: '👽', name: 'Alien', cost: 1250 },
{ id: '🍪', name: 'Lebkuchen', cost: 50 },
{ id: '🌵', name: 'Kaktus', cost: 90 },
{ id: '👻', name: 'Geist', cost: 150 },
{ id: '🦁', name: 'Löwen-Pingu', cost: 350 },
{ id: '🧟', name: 'Zombie', cost: 500 },
{ id: '🐉', name: 'Drache', cost: 800 },
{ id: '⚔️', name: 'Ritter', cost: 1100 },
{ id: '🔱', name: 'Meeresgott', cost: 2500 }
];
const ALL_THEMES = [
{ id: 'Arktis', name: 'Klassik Arktis', cost: 0, top: '#0f172a', mid: '#1e1b4b', bot: '#2e1065', ground: '#1e293b', border: '#38bdf8' },
{ id: 'Neon', name: 'Cyber Neon', cost: 50, top: '#020617', mid: '#311042', bot: '#0f172a', ground: '#111827', border: '#f43f5e' },
{ id: 'Wüste', name: 'Wüsten Hitze', cost: 120, top: '#7c2d12', mid: '#9a3412', bot: '#ea580c', ground: '#451a03', border: '#f97316' },
{ id: 'Vulkan', name: 'Lava Höhle', cost: 300, top: '#1c1917', mid: '#44403c', bot: '#78716c', ground: '#0c0a09', border: '#ef4444' }
];
const UPGRADES_CONFIG = {
coinMultiplier: { name: 'Münz-Wert', max: 5, baseCost: 30 },
jumpForce: { name: 'Flugzeit', max: 5, baseCost: 40 }
};
const QUEST_POOL = [
{ id: 'dq_b1', tier: 'bronze', text: 'Tages-Aufwärmen: Score 40 knacken', reward: 30, condition: (s, c, j) => s >= 40 },
{ id: 'dq_b2', tier: 'bronze', text: 'Schnelle Taschen: Erbeute 15 Münzen im Run', reward: 35, condition: (s, c, j) => c >= 15 },
{ id: 'dq_b3', tier: 'bronze', text: 'Beintraining: Schaffe heute 20 Sprünge in einem Run', reward: 30, condition: (s, c, j) => j >= 20 },
{ id: 'dq_b4', tier: 'bronze', text: 'Ausdauer-Check: Erreiche exakt oder über Score 80', reward: 50, condition: (s, c, j) => s >= 80 },
{ id: 'dq_s1', tier: 'silber', text: 'Profi-Dash: Score 160 überleben', reward: 120, condition: (s, c, j) => s >= 160 },
{ id: 'dq_s2', tier: 'silber', text: 'Münz-Sammler: Schnapp dir 35 Coins in einem Lauf', reward: 140, condition: (s, c, j) => c >= 35 },
{ id: 'dq_s3', tier: 'silber', text: 'Hyperaktiv: Springe 45 Mal in einem einzigen Run', reward: 100, condition: (s, c, j) => j >= 45 },
{ id: 'dq_s4', tier: 'silber', text: 'Speedrun: Erreiche rasant Score 260', reward: 200, condition: (s, c, j) => s >= 260 },
{ id: 'dq_g1', tier: 'gold', text: 'Meisterleistung: Überlebe bis Score 500', reward: 400, condition: (s, c, j) => s >= 500 },
{ id: 'dq_g2', tier: 'gold', text: 'Der Tresorraum: 75 Coins in einem Run einsammeln', reward: 450, condition: (s, c, j) => c >= 75 },
{ id: 'dq_g3', tier: 'gold', text: 'Flug-Gott: Stolze 90 Sprünge im selben Run absolvieren', reward: 350, condition: (s, c, j) => j >= 90 },
{ id: 'dq_g4', tier: 'gold', text: 'Unbesiegbar: Setze die Messlatte hoch auf Score 750', reward: 600, condition: (s, c, j) => s >= 750 }
];
function saveState() {
localStorage.setItem('pd_highscore', state.highScore);
localStorage.setItem('pd_coins', state.totalCoins);
localStorage.setItem('pd_skins', JSON.stringify(state.ownedSkins));
localStorage.setItem('pd_activeskin', state.activeSkin);
localStorage.setItem('pd_themes', JSON.stringify(state.ownedThemes));
localStorage.setItem('pd_activetheme', state.activeTheme);
localStorage.setItem('pd_up_coinmult', state.coinMultiplierLevel);
localStorage.setItem('pd_up_jumpforce', state.jumpForceLevel);
localStorage.setItem('pd_daily_active_ids', JSON.stringify(state.activeQuestIds));
localStorage.setItem('pd_quests_ready', JSON.stringify(state.readyQuests));
localStorage.setItem('pd_quests_claimed', JSON.stringify(state.claimedQuests));
localStorage.setItem('pd_quest_date', state.lastQuestDate);
localStorage.setItem('pd_lastdaily', state.lastDailyClaim);
}
function checkAndRotateDailyQuests() {
const todayStr = new Date().toDateString();
if (state.lastQuestDate !== todayStr || state.activeQuestIds.length === 0) {
state.lastQuestDate = todayStr;
state.readyQuests = [];
state.claimedQuests = [];
const bronzePool = QUEST_POOL.filter(q => q.tier === 'bronze');
const silberPool = QUEST_POOL.filter(q => q.tier === 'silber');
const goldPool = QUEST_POOL.filter(q => q.tier === 'gold');
const q1 = bronzePool[Math.floor(Math.random() * bronzePool.length)].id;
const q2 = silberPool[Math.floor(Math.random() * silberPool.length)].id;
const q3 = goldPool[Math.floor(Math.random() * goldPool.length)].id;
state.activeQuestIds = [q1, q2, q3];
saveState();
}
}
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
if (gameActive) playerJump();
}
});
function openQuestsMenu() {
checkAndRotateDailyQuests();
togglePanel('questsPanel');
renderQuests();
}
function renderQuests() {
const container = document.getElementById('questListContainer');
container.innerHTML = '';
const todaysQuests = QUEST_POOL.filter(q => state.activeQuestIds.includes(q.id));
todaysQuests.forEach(quest => {
const isReady = state.readyQuests.includes(quest.id);
const isClaimed = state.claimedQuests.includes(quest.id);
let statusClass = '';
let actionContent = `<span class="quest-reward-tag">+${quest.reward} 🪙</span>`;
if (isClaimed) {
statusClass = 'claimed';
actionContent = `<span style="color:#10b981; font-size:12px; font-weight:bold;">Abgeholt ✅</span>`;
} else if (isReady) {
statusClass = 'ready';
actionContent = `
<div style="display:flex; flex-direction:column; align-items:flex-end; gap:4px;">
<span class="quest-reward-tag">${quest.reward} 🪙</span>
<button class="btn btn-primary btn-quest-claim" onclick="claimQuestReward('${quest.id}', ${quest.reward})">Einlösen</button>
</div>`;
}
let badgeColorClass = 'badge-bronze';
if(quest.tier === 'silber') badgeColorClass = 'badge-silber';
if(quest.tier === 'gold') badgeColorClass = 'badge-gold';
container.innerHTML += `
<div class="quest-item ${statusClass}">
<div style="flex:1;">
<div style="margin-bottom: 2px;">
<span class="quest-badge ${badgeColorClass}">${quest.tier}</span>
<span style="font-weight:600; font-size:13px; color:#f8fafc">${quest.text}</span>
</div>
<div style="font-size:10px; color:#94a3b8; padding-left:2px;">
Status: ${isClaimed ? 'Erledigt' : (isReady ? 'BEREIT!' : 'Heute noch offen')}
</div>
</div>
<div>${actionContent}</div>
</div>
`;
});
}
function claimQuestReward(id, rewardAmount) {
state.readyQuests = state.readyQuests.filter(qId => qId !== id);
state.claimedQuests.push(id);
state.totalCoins += rewardAmount;
saveState();
renderQuests();
updateMenuStats();
showToast(`+${rewardAmount} Coins gesichert! 💰`);
}
function checkQuests(currentScore, currentCoins, totalJumps) {
const todaysQuests = QUEST_POOL.filter(q => state.activeQuestIds.includes(q.id));
todaysQuests.forEach(quest => {
if (!state.readyQuests.includes(quest.id) && !state.claimedQuests.includes(quest.id)) {
if (quest.condition(currentScore, currentCoins, totalJumps)) {
state.readyQuests.push(quest.id);
saveState();
triggerQuestNotification();
}
}
});
}
function triggerQuestNotification() {
const notifier = document.getElementById('quest-notifier');
notifier.classList.add('show');
setTimeout(() => { notifier.classList.remove('show'); }, 3500);
}
function showToast(text) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = 'toast'; toast.innerText = text;
container.appendChild(toast);
setTimeout(() => toast.remove(), 2500);
}
function changeScreen(fromId, toId) {
document.getElementById(fromId).classList.add('hidden');
const toScreen = document.getElementById(toId);
toScreen.classList.remove('hidden');
document.getElementById('gameContainer').style.display = (toId === 'gameContainer') ? 'block' : 'none';
}
function togglePanel(id) {
const el = document.getElementById(id);
el.classList.contains('hidden') ? el.classList.remove('hidden') : el.classList.add('hidden');
}
function updateMenuStats() {
document.getElementById('menuHighScore').innerText = state.highScore;
document.getElementById('menuTotalCoins').innerText = state.totalCoins;
}
// --- CANVAS GAME ENGINE ---
let canvas, ctx, gameLoopId, gameActive = false;
let player, obstacles, items, particles;
let score, coinsCollected, totalJumpsRun, gameSpeed, spawnTimer, groundY;
function initGame() {
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
resizeCanvas();
groundY = canvas.height * 0.8;
score = 0; coinsCollected = 0; totalJumpsRun = 0; gameSpeed = 6; spawnTimer = 0;
obstacles = []; items = []; particles = [];
let finalGravity = 0.7 - ((state.jumpForceLevel - 1) * 0.04);
player = {
x: canvas.width * 0.15,
y: groundY - 45,
w: 45, h: 45,
vy: 0,
gravity: finalGravity,
jumpForce: -14,
isJumping: false
};
applyTheme(state.activeTheme);
canvas.onmousedown = (e) => { e.preventDefault(); playerJump(); };
canvas.ontouchstart = (e) => { e.preventDefault(); playerJump(); };
gameActive = true;
gameLoop();
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function playerJump() {
if (!player.isJumping && gameActive) {
player.vy = player.jumpForce;
player.isJumping = true;
totalJumpsRun++;
createParticles(player.x + player.w/2, groundY, '#38bdf8', 10);
}
}
function createParticles(x, y, color, count) {
for(let i=0; i<count; i++) {
particles.push({
x, y,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
size: Math.random() * 4 + 2,
alpha: 1
});
}
}
function gameLoop() {
if (!gameActive) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
player.y += player.vy;
player.vy += player.gravity;
if (player.y >= groundY - player.h) {
player.y = groundY - player.h;
player.vy = 0;
player.isJumping = false;
}
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--ground-color');
ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--ground-border');
ctx.lineWidth = 4;
ctx.beginPath(); ctx.moveTo(0, groundY); ctx.lineTo(canvas.width, groundY); ctx.stroke();
ctx.font = `${player.h}px sans-serif`;
ctx.textBaseline = 'top';
ctx.fillText(state.activeSkin, player.x, player.y);
spawnTimer++;
if (spawnTimer > Math.max(30, 80 - gameSpeed * 3)) {
spawnTimer = 0;
if (Math.random() < 0.60) {
items.push({ x: canvas.width, y: groundY - 50 - Math.random() * 95, w: 25, h: 25 });
} else {
obstacles.push({ x: canvas.width, y: groundY - 40, w: 30, h: 40, color: '#ef4444' });
}
}
for (let i = obstacles.length - 1; i >= 0; i--) {
let o = obstacles[i]; o.x -= gameSpeed;
ctx.fillStyle = o.color;
ctx.fillRect(o.x, o.y, o.w, o.h);
if (player.x < o.x + o.w && player.x + player.w > o.x && player.y < o.y + o.h && player.y + player.h > o.y) {
gameOver(); return;
}
if (o.x < -50) obstacles.splice(i, 1);
}
for (let i = items.length - 1; i >= 0; i--) {
let c = items[i]; c.x -= gameSpeed;
ctx.font = `${c.w}px sans-serif`;
ctx.fillText('🪙', c.x, c.y);
if (player.x < c.x + c.w && player.x + player.w > c.x && player.y < c.y + c.h && player.y + player.h > c.y) {
coinsCollected += state.coinMultiplierLevel;
createParticles(c.x, c.y, '#fbbf24', 6);
items.splice(i, 1);
continue;
}
if (c.x < -50) items.splice(i, 1);
}
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i]; p.x += p.vx; p.y += p.vy; p.alpha -= 0.03;
if (p.alpha <= 0) { particles.splice(i, 1); continue; }
ctx.save(); ctx.globalAlpha = p.alpha; ctx.fillStyle = '#38bdf8';
ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill(); ctx.restore();
}
score += 0.1;
gameSpeed += 0.0025;
document.getElementById('hudScore').innerText = Math.floor(score);
document.getElementById('hudCoins').innerText = coinsCollected;
checkQuests(Math.floor(score), coinsCollected, totalJumpsRun);
gameLoopId = requestAnimationFrame(gameLoop);
}
function gameOver() {
gameActive = false;
cancelAnimationFrame(gameLoopId);
state.totalCoins += coinsCollected;
if (Math.floor(score) > state.highScore) state.highScore = Math.floor(score);
saveState();
document.getElementById('finalScore').innerText = Math.floor(score);
document.getElementById('finalCoins').innerText = coinsCollected;
changeScreen('gameContainer', 'gameOverScreen');
}
function launchDirectGame() {
document.getElementById('mainMenu').classList.add('hidden');
document.getElementById('gameOverScreen').classList.add('hidden');
changeScreen('mainMenu', 'gameContainer');
initGame();
}
// --- SHOP MECHANICS ---
let currentShopTab = 'skins';
function openShop() {
document.getElementById('shopCoinCount').innerText = state.totalCoins;
switchTab(currentShopTab);
changeScreen('mainMenu', 'shopPanel');
}
function closeShop() { changeScreen('shopPanel', 'mainMenu'); }
function switchTab(tab) {
currentShopTab = tab;
document.querySelectorAll('.shop-tabs .tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(`tabBtn-${tab}`).classList.add('active');
const grid = document.getElementById('shopGrid'); grid.innerHTML = '';
if (tab === 'skins') {
ALL_SKINS.forEach(item => {
const isOwned = state.ownedSkins.includes(item.id);
const isActive = state.activeSkin === item.id;
grid.innerHTML += `
<div class="shop-item">
<span>${item.id}</span>
<small style="color:#64748b">${item.name}</small>
<button class="btn btn-primary" style="padding:6px; font-size:11px; margin-top:2px;" onclick="buySkin('${item.id}', ${item.cost})">
${isActive ? 'Aktiv' : (isOwned ? 'Nutzen' : item.cost + ' 🪙')}
</button>
</div>`;
});
} else if (tab === 'themes') {
ALL_THEMES.forEach(item => {
const isOwned = state.ownedThemes.includes(item.id);
const isActive = state.activeTheme === item.id;
grid.innerHTML += `
<div class="shop-item">
<span style="font-size:22px; margin-top:4px;">🎨</span>
<small style="color:#64748b; font-weight:600;">${item.name}</small>
<button class="btn btn-primary" style="padding:6px; font-size:11px; margin-top:2px;" onclick="buyTheme('${item.id}', ${item.cost})">
${isActive ? 'Aktiv' : (isOwned ? 'Nutzen' : item.cost + ' 🪙')}
</button>
</div>`;
});
} else if (tab === 'upgrades') {
let cmLvl = state.coinMultiplierLevel;
let cmConf = UPGRADES_CONFIG.coinMultiplier;
let cmCost = cmLvl * cmConf.baseCost;
let cmMaxed = cmLvl >= cmConf.max;
grid.innerHTML += `
<div class="shop-item">
<span style="font-size:22px;">🪙</span>
<small style="color:#94a3b8; font-weight:bold;">${cmConf.name}<br>(Lvl ${cmLvl}/${cmConf.max})</small>
<button class="btn btn-primary" style="padding:6px; font-size:11px;" onclick="buyUpgrade('coinMultiplier')" ${cmMaxed ? 'disabled' : ''}>
${cmMaxed ? 'MAX' : cmCost + ' 🪙'}
</button>
</div>`;
let jfLvl = state.jumpForceLevel;
let jfConf = UPGRADES_CONFIG.jumpForce;
let jfCost = jfLvl * jfConf.baseCost;
let jfMaxed = jfLvl >= jfConf.max;
grid.innerHTML += `
<div class="shop-item">
<span style="font-size:22px;">⚡</span>
<small style="color:#94a3b8; font-weight:bold;">${jfConf.name}<br>(Lvl ${jfLvl}/${jfConf.max})</small>
<button class="btn btn-primary" style="padding:6px; font-size:11px;" onclick="buyUpgrade('jumpForce')" ${jfMaxed ? 'disabled' : ''}>
${jfMaxed ? 'MAX' : jfCost + ' 🪙'}
</button>
</div>`;
}
}
function buySkin(id, cost) {
if (state.ownedSkins.includes(id)) { state.activeSkin = id; }
else if (state.totalCoins >= cost) { state.totalCoins -= cost; state.ownedSkins.push(id); state.activeSkin = id; }
else { showToast('Nicht genug Coins! 🪙'); return; }
saveState(); openShop();
}
function buyUpgrade(type) {
let conf = UPGRADES_CONFIG[type];
if (type === 'coinMultiplier') {
let cost = state.coinMultiplierLevel * conf.baseCost;
if (state.coinMultiplierLevel < conf.max && state.totalCoins >= cost) {
state.totalCoins -= cost; state.coinMultiplierLevel++;
showToast('Münzwert erhöht! 🎉');
} else { showToast('Nicht genug Coins! 🪙'); return; }
} else if (type === 'jumpForce') {
let cost = state.jumpForceLevel * conf.baseCost;
if (state.jumpForceLevel < conf.max && state.totalCoins >= cost) {
state.totalCoins -= cost; state.jumpForceLevel++;
showToast('Schwerkraft gesenkt! ⚡');
} else { showToast('Nicht genug Coins! 🪙'); return; }
}
saveState(); openShop();
}
function buyTheme(id, cost) {
if (state.ownedThemes.includes(id)) { state.activeTheme = id; applyTheme(id); }
else if (state.totalCoins >= cost) { state.totalCoins -= cost; state.ownedThemes.push(id); state.activeTheme = id; applyTheme(id); }
else { showToast('Nicht genug Coins! 🪙'); return; }
saveState(); openShop();
}
function applyTheme(themeId) {
const t = ALL_THEMES.find(x => x.id === themeId) || ALL_THEMES[0];
const r = document.documentElement;
r.style.setProperty('--bg-grad-top', t.top);
r.style.setProperty('--bg-grad-mid', t.mid);
r.style.setProperty('--bg-grad-bottom', t.bot);
r.style.setProperty('--ground-color', t.ground);
r.style.setProperty('--ground-border', t.border);
}
// --- TÄGLICHE EINLOGG-BELOHNUNG ---
function updateDailyUI() {
const grid = document.getElementById('dailyGrid'); grid.innerHTML = '';
const todayStr = new Date().toDateString();
const hasClaimed = (state.lastDailyClaim === todayStr);
for(let i = 1; i <= 4; i++) {
let classes = "daily-day";
if (i === 1) {
if (hasClaimed) classes += " completed";
else classes += " current";
}
grid.innerHTML += `<div class="${classes}">Tag ${i}<br><strong>25 🪙</strong></div>`;
}
const claimBtn = document.getElementById('claimDailyBtn');
if (hasClaimed) {
claimBtn.disabled = true;
claimBtn.innerText = "Morgen wieder!";
} else {
claimBtn.disabled = false;
claimBtn.innerText = "Belohnung Abholen!";
}
}
function claimDailyReward() {
const todayStr = new Date().toDateString();
if (state.lastDailyClaim !== todayStr) {
state.totalCoins += 25;
state.lastDailyClaim = todayStr;
saveState();
updateDailyUI();
updateMenuStats();
showToast('+25 Coins erhalten! 🎁');
}
}
// --- INITIAL LOADING ---
window.onload = () => {
checkAndRotateDailyQuests();
let progress = 0;
const bar = document.getElementById('loaderBar');
const interval = setInterval(() => {
progress += Math.random() * 12 + 4;
if(progress >= 100) {
progress = 100; clearInterval(interval);
setTimeout(() => {
document.getElementById('loadingScreen').classList.add('hidden');
document.getElementById('mainMenu').classList.remove('hidden');
updateMenuStats();
}, 200);
}
bar.style.width = `${progress}%`;
}, 50);
};
window.onresize = () => { if(gameActive) resizeCanvas(); };
</script>
</body>
</html>Game Source: Penguin Dash: Overdrive Edition
Creator: NovaGalaxy88
Libraries: none
Complexity: complex (809 lines, 33.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: penguin-dash-overdrive-edition-novagalaxy88" to link back to the original. Then publish at arcadelab.ai/publish.