🎮ArcadeLab

Penguin Dash: Overdrive Edition

by NovaGalaxy88
809 lines33.3 KB
▶ Play
<!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.