🎮ArcadeLab

Penguin Dash: Overdrive Edition

by BlazeKoala99
2548 lines59.5 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.12s ease, box-shadow 0.2s ease, filter 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    width: 100%;
    box-sizing: border-box;
}

.btn:hover {
    filter: brightness(1.08);
    box-shadow: 0 7px 22px rgba(6, 182, 212, 0.22);
}

.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);
}

.btn-gold {
    background: linear-gradient(135deg, #eab308, #ca8a04);
    box-shadow: 0 4px 14px rgba(234, 179, 8, 0.4);
    text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}

/* =========================
   LADEBALKEN
========================= */

#loadingScreen { background: radial-gradient(circle at 50% 38%, rgba(6,182,212,.18), transparent 30%), radial-gradient(circle at 15% 85%, rgba(59,130,246,.12), transparent 28%), radial-gradient(circle at 85% 20%, rgba(139,92,246,.10), transparent 25%), #020617; z-index:500; overflow:hidden; }
#loadingScreen::before { content:''; position:absolute; inset:0; background-image:linear-gradient(rgba(255,255,255,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.035) 1px,transparent 1px); background-size:34px 34px; mask-image:linear-gradient(to bottom,transparent,black 22%,black 78%,transparent); pointer-events:none; }
#loadingScreen::after { content:''; position:absolute; width:70vw; height:70vw; max-width:720px; max-height:720px; border:1px solid rgba(6,182,212,.10); border-radius:50%; box-shadow:0 0 80px rgba(6,182,212,.08),inset 0 0 80px rgba(6,182,212,.05); animation:loadingOrbit 9s linear infinite; pointer-events:none; }
.loading-content { position:relative; z-index:2; width:min(90vw,700px); display:flex; flex-direction:column; align-items:center; text-align:center; }
.loading-badge { display:inline-flex; align-items:center; gap:8px; padding:7px 14px; margin-bottom:18px; border:1px solid rgba(103,232,249,.25); border-radius:999px; background:rgba(8,47,73,.45); color:#a5f3fc; font-size:.72rem; font-weight:900; letter-spacing:2px; text-transform:uppercase; box-shadow:0 0 24px rgba(6,182,212,.10); }
.loading-badge::before { content:''; width:7px; height:7px; border-radius:50%; background:#22d3ee; box-shadow:0 0 10px #22d3ee; animation:statusPulse 1s ease-in-out infinite; }
.loading-title { margin:0; font-size:clamp(3.1rem,12vw,7.4rem); line-height:.88; font-weight:950; letter-spacing:clamp(1px,1vw,8px); text-transform:uppercase; color:#f8fafc; text-shadow:0 0 8px rgba(255,255,255,.35),0 0 25px rgba(6,182,212,.55),0 0 60px rgba(6,182,212,.22); animation:titleGlow 2.4s ease-in-out infinite; }
.loading-title span { color:var(--accent-color); }
.loading-subtitle { margin:18px 0 0; font-size:clamp(1rem,3.5vw,1.5rem); font-weight:800; letter-spacing:4px; text-transform:uppercase; color:#cbd5e1; }
.loader-wrap { width:100%; margin-top:38px; }
.loader-container { position:relative; width:100%; height:34px; padding:5px; box-sizing:border-box; background:rgba(2,6,23,.86); border:1px solid rgba(148,163,184,.28); border-radius:999px; overflow:hidden; box-shadow:inset 0 0 18px rgba(0,0,0,.75),0 0 28px rgba(6,182,212,.12); }
.loader-container::before { content:''; position:absolute; inset:5px; background:repeating-linear-gradient(90deg,rgba(255,255,255,.05) 0 1px,transparent 1px 12px); border-radius:inherit; pointer-events:none; }
.loader-bar { width:0%; height:100%; border-radius:999px; background:linear-gradient(90deg,#0891b2,#06b6d4 42%,#67e8f9 78%,#fff); box-shadow:0 0 12px rgba(6,182,212,.95),0 0 30px rgba(6,182,212,.62); position:relative; overflow:hidden; transition:width .08s linear; }
.loader-bar::before { content:''; position:absolute; inset:0; background:repeating-linear-gradient(-45deg,rgba(255,255,255,.18) 0 10px,transparent 10px 20px); animation:loaderStripes .65s linear infinite; }
.loader-bar::after { content:''; position:absolute; top:0; bottom:0; width:100px; right:-100px; background:linear-gradient(90deg,transparent,rgba(255,255,255,.85),transparent); animation:loaderShine .75s linear infinite; }
.loader-info { width:100%; display:flex; justify-content:space-between; align-items:center; margin-top:14px; color:#94a3b8; font-size:.82rem; font-weight:800; letter-spacing:1.5px; text-transform:uppercase; }
#loaderPercent { color:#f8fafc; font-size:clamp(1.6rem,5vw,2.4rem); font-weight:950; letter-spacing:1px; text-shadow:0 0 18px rgba(6,182,212,.7); }
.loader-status { color:var(--accent-color); }
.loader-penguin { margin-top:25px; font-size:clamp(2.5rem,7vw,4.2rem); filter:drop-shadow(0 0 18px rgba(6,182,212,.48)); animation:penguinFloat 1.5s ease-in-out infinite; }
.loading-tip { margin-top:12px; color:#64748b; font-size:.72rem; letter-spacing:1px; }
@keyframes loaderShine { from{transform:translateX(-120px)} to{transform:translateX(130px)} }
@keyframes loaderStripes { from{transform:translateX(0)} to{transform:translateX(20px)} }
@keyframes penguinFloat { 0%,100%{transform:translateY(0) rotate(-2deg)} 50%{transform:translateY(-8px) rotate(2deg)} }
@keyframes titleGlow { 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.08)} }
@keyframes statusPulse { 0%,100%{opacity:.45;transform:scale(.8)} 50%{opacity:1;transform:scale(1.15)} }
@keyframes loadingOrbit { from{transform:rotate(0deg) scale(1)} to{transform:rotate(360deg) scale(1.03)} }
@media (prefers-reduced-motion:reduce){#loadingScreen::after,.loading-title,.loading-badge::before,.loader-bar::before,.loader-bar::after,.loader-penguin{animation:none}}

/* =========================
   GAME
========================= */

#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 {
    touch-action: none;
    -webkit-tap-highlight-color: transparent;
    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
========================= */

.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;
    text-align: center;
}

/* =========================
   QUESTS
========================= */

.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
========================= */

#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
========================= */

#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
========================= */

.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>

<!-- =========================
     LADEBILDSCHIRM
========================= -->

<div id="loadingScreen" class="screen">

    <div class="loading-content">

        <div class="loading-badge">SYSTEM ONLINE</div>

        <div class="loading-title">PENGUIN <span>DASH</span></div>

        <div class="loading-subtitle">Overdrive Edition</div>

        <div class="loader-wrap">
            <div class="loader-container">
                <div id="loaderBar" class="loader-bar"></div>
            </div>

            <div class="loader-info">
                <span class="loader-status" id="loaderStatus">Wird geladen...</span>
                <span id="loaderPercent">0%</span>
            </div>
        </div>

        <div class="loader-penguin">🐧</div>

        <div class="loading-tip">Bereit für den nächsten Run? • Overdrive-System wird initialisiert</div>

    </div>

</div>

<!-- =========================
     HAUPTMENÜ
========================= -->

<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();">
                Tägliche Belohnung 🎁
            </button>

        </div>

    </div>

</div>

<!-- =========================
     QUESTS
========================= -->

<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;
        ">
            10 zufällige Aufgaben wechseln jede Nacht!
        </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
========================= -->

<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>

/* =========================
   STATE
========================= */

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') || ""
};

/* =========================
   SKINS
========================= */

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 }

];

/* =========================
   THEMES
========================= */

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'
    }

];

/* =========================
   UPGRADES
========================= */

const UPGRADES_CONFIG = {

    coinMultiplier: {
        name: 'Münz-Wert',
        max: 5,
        baseCost: 30
    },

    jumpForce: {
        name: 'Flugzeit',
        max: 5,
        baseCost: 40
    }

};

/* =========================
   QUESTS
========================= */

const QUEST_POOL = [

    { id: 'dq_1', tier: 'bronze', text: 'Aufwärmen: Score 40 knacken', reward: 30, condition: (s,c,j) => s >= 40 },
    { id: 'dq_2', tier: 'bronze', text: 'Taschengeld: Erbeute 12 Münzen im aktuellen Run', reward: 35, condition: (s,c,j) => c >= 12 },
    { id: 'dq_3', tier: 'bronze', text: 'Frühsport: Schaffe 15 Sprünge in einem Run', reward: 30, condition: (s,c,j) => j >= 15 },
    { id: 'dq_4', tier: 'bronze', text: 'Eis-Rutschen: Halte dich für Score 60 wacker', reward: 40, condition: (s,c,j) => s >= 60 },
    { id: 'dq_5', tier: 'bronze', text: 'Münz-Lehrling: Sammle 8 Münzen', reward: 25, condition: (s,c,j) => c >= 8 },
    { id: 'dq_6', tier: 'bronze', text: 'Hüpfer: Überlebe 30 Punkte und springe 5-mal', reward: 35, condition: (s,c,j) => s >= 30 && j >= 5 },
    { id: 'dq_7', tier: 'bronze', text: 'Ausdauer-Check: Erreiche Score 80', reward: 50, condition: (s,c,j) => s >= 80 },
    { id: 'dq_8', tier: 'bronze', text: 'Glücksritter: Sammle 5 Münzen in Rekordzeit', reward: 20, condition: (s,c,j) => c >= 5 },
    { id: 'dq_9', tier: 'bronze', text: 'Sicherer Tritt: 10 Sprünge und Score 50', reward: 40, condition: (s,c,j) => s >= 50 && j >= 10 },
    { id: 'dq_26', tier: 'bronze', text: 'Pinguin-Schritte: Erreiche Score 20', reward: 15, condition: (s,c,j) => s >= 20 },
    { id: 'dq_27', tier: 'bronze', text: 'Kleingeld: Sammle 5 Münzen', reward: 15, condition: (s,c,j) => c >= 5 },
    { id: 'dq_28', tier: 'bronze', text: 'Hopser: Mache 8 Sprünge', reward: 20, condition: (s,c,j) => j >= 8 },
    { id: 'dq_29', tier: 'bronze', text: 'Doppelsprung: 2 Sprünge in einem Run', reward: 10, condition: (s,c,j) => j >= 2 },
    { id: 'dq_30', tier: 'bronze', text: 'Schnelles Geld: 10 Coins einsammeln', reward: 25, condition: (s,c,j) => c >= 10 },
    { id: 'dq_31', tier: 'bronze', text: 'Glatteis: Erreiche Score 45', reward: 30, condition: (s,c,j) => s >= 45 },
    { id: 'dq_32', tier: 'bronze', text: 'Münz-Sammler: 15 Coins auf einen Schlag', reward: 40, condition: (s,c,j) => c >= 15 },
    { id: 'dq_33', tier: 'bronze', text: 'Luftikuss: 12 Sprünge im Run', reward: 25, condition: (s,c,j) => j >= 12 },
    { id: 'dq_34', tier: 'bronze', text: 'Halbzeit: Erreiche Score 75', reward: 45, condition: (s,c,j) => s >= 75 },
    { id: 'dq_35', tier: 'bronze', text: 'Kombinierer: Score 30 + 5 Coins', reward: 30, condition: (s,c,j) => s >= 30 && c >= 5 },

    { id: 'dq_10', tier: 'silber', text: 'Profi-Dash: Score 160 überleben', reward: 120, condition: (s,c,j) => s >= 160 },
    { id: 'dq_11', tier: 'silber', text: 'Münz-Magnet: Schnapp dir 30 Coins im Lauf', reward: 130, condition: (s,c,j) => c >= 30 },
    { id: 'dq_12', tier: 'silber', text: 'Hyperaktiv: Springe 40 Mal in einem Run', reward: 100, condition: (s,c,j) => j >= 40 },
    { id: 'dq_13', tier: 'silber', text: 'Fortgeschritten: Knacke die Marke von Score 220', reward: 160, condition: (s,c,j) => s >= 220 },
    { id: 'dq_14', tier: 'silber', text: 'Goldspeicher: Akkumuliere 35 Münzen in einem Sprint', reward: 140, condition: (s,c,j) => c >= 35 },
    { id: 'dq_15', tier: 'silber', text: 'Sprungfeder: Leiste ganze 30 Sprünge ab', reward: 95, condition: (s,c,j) => j >= 30 },
    { id: 'dq_16', tier: 'silber', text: 'Ausbalanciert: Score 120 und sammle 20 Münzen', reward: 150, condition: (s,c,j) => s >= 120 && c >= 20 },
    { id: 'dq_17', tier: 'silber', text: 'Silber-Glanz: Erreiche Score 180 ohne Probleme', reward: 130, condition: (s,c,j) => s >= 180 },
    { id: 'dq_18', tier: 'silber', text: 'Münz-Meisterschaft: Genau oder über 25 Münzen', reward: 110, condition: (s,c,j) => c >= 25 },
    { id: 'dq_36', tier: 'silber', text: 'Solider Run: Erreiche Score 150', reward: 115, condition: (s,c,j) => s >= 150 },
    { id: 'dq_37', tier: 'silber', text: 'Goldgräber: Sammle 40 Coins', reward: 150, condition: (s,c,j) => c >= 40 },
    { id: 'dq_38', tier: 'silber', text: 'Trampolin: Schaffe 35 Sprünge', reward: 110, condition: (s,c,j) => j >= 35 },
    { id: 'dq_39', tier: 'silber', text: 'Marathon-Vorlauf: Score 250 knacken', reward: 180, condition: (s,c,j) => s >= 250 },
    { id: 'dq_40', tier: 'silber', text: 'Schatzsucher: 50 Coins in einem Run', reward: 190, condition: (s,c,j) => c >= 50 },
    { id: 'dq_41', tier: 'silber', text: 'Akrobat: 45 Sprünge machen', reward: 130, condition: (s,c,j) => j >= 45 },
    { id: 'dq_42', tier: 'silber', text: 'Multitasking: Score 200 + 20 Sprünge', reward: 170, condition: (s,c,j) => s >= 200 && j >= 20 },
    { id: 'dq_43', tier: 'silber', text: 'Gute Ausbeute: 38 Münzen sammeln', reward: 145, condition: (s,c,j) => c >= 38 },

    { id: 'dq_19', tier: 'gold', text: 'Meisterleistung: Überlebe bis Score 500', reward: 400, condition: (s,c,j) => s >= 500 },
    { id: 'dq_20', tier: 'gold', text: 'Der Tresorraum: 65 Coins in einem Run einsammeln', reward: 420, condition: (s,c,j) => c >= 65 },
    { id: 'dq_21', tier: 'gold', text: 'Flug-Gott: Stolze 80 Sprünge absolvieren', reward: 350, condition: (s,c,j) => j >= 80 },
    { id: 'dq_22', tier: 'gold', text: 'Unbesiegbar: Setze die Messlatte auf Score 700', reward: 550, condition: (s,c,j) => s >= 700 },
    { id: 'dq_23', tier: 'gold', text: 'Pinguin-Legende: Überlebe gigantische 900 Punkte', reward: 750, condition: (s,c,j) => s >= 900 },
    { id: 'dq_24', tier: 'gold', text: 'Dauer-Jumper: Sensationelle 100 Sprünge hinlegen', reward: 450, condition: (s,c,j) => j >= 100 },
    { id: 'dq_25', tier: 'gold', text: 'Reichtum & Ruhm: Score 600 und 50 Münzen', reward: 650, condition: (s,c,j) => s >= 600 && c >= 50 },
    { id: 'dq_44', tier: 'gold', text: 'Eis-Gott: Knacke Score 800', reward: 650, condition: (s,c,j) => s >= 800 },
    { id: 'dq_45', tier: 'gold', text: 'Dagobert Duck: 75 Coins abstauben', reward: 500, condition: (s,c,j) => c >= 75 },
    { id: 'dq_46', tier: 'gold', text: 'Vogel-Ambitionen: 120 Sprünge in einem Run', reward: 600, condition: (s,c,j) => j >= 120 },
    { id: 'dq_47', tier: 'gold', text: 'Unendliche Weiten: Score 1000 erreichen', reward: 1000, condition: (s,c,j) => s >= 1000 },
    { id: 'dq_48', tier: 'gold', text: 'Jackpot: 100 Coins schnappen', reward: 800, condition: (s,c,j) => c >= 100 },
    { id: 'dq_49', tier: 'gold', text: 'Känguru-Pinguin: 150 Sprünge absolvieren', reward: 750, condition: (s,c,j) => j >= 150 },
    { id: 'dq_50', tier: 'gold', text: 'Perfekter Run: Score 750 + 60 Coins', reward: 850, condition: (s,c,j) => s >= 750 && c >= 60 }

];

/* =========================
   CACHE
========================= */

let cachedGroundColor = '#1e293b';
let cachedBorderColor = '#38bdf8';
let cachedActiveQuests = [];

/* =========================
   SAVE
========================= */

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);

}

/* =========================
   DAILY QUESTS
========================= */

function checkAndRotateDailyQuests() {

    const todayStr = new Date().toDateString();

    if (
        state.lastQuestDate !== todayStr ||
        state.activeQuestIds.length < 10
    ) {

        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');

        let shuffledBronze =
            bronzePool.sort(() => 0.5 - Math.random());

        let shuffledSilber =
            silberPool.sort(() => 0.5 - Math.random());

        let shuffledGold =
            goldPool.sort(() => 0.5 - Math.random());

        state.activeQuestIds = [

            shuffledBronze[0].id,
            shuffledBronze[1].id,
            shuffledBronze[2].id,
            shuffledBronze[3].id,

            shuffledSilber[0].id,
            shuffledSilber[1].id,
            shuffledSilber[2].id,
            shuffledSilber[3].id,

            shuffledGold[0].id,
            shuffledGold[1].id

        ];

        saveState();
    }

    cachedActiveQuests =
        QUEST_POOL.filter(q =>
            state.activeQuestIds.includes(q.id)
        );
}

/* =========================
   KEYBOARD
========================= */

window.addEventListener('keydown', (e) => {

    if (
        e.code === 'Space' ||
        e.code === 'ArrowUp' ||
        e.code === 'KeyW'
    ) {
        e.preventDefault();

        if (gameActive) {
            playerJump();
        }
    }

});

/* =========================
   QUEST MENU
========================= */

function openQuestsMenu() {

    checkAndRotateDailyQuests();

    togglePanel('questsPanel');

    renderQuests();
}

function renderQuests() {

    const container =
        document.getElementById('questListContainer');

    container.innerHTML = '';

    cachedActiveQuests.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>
        `;
    });
}

/* =========================
   QUEST REWARD
========================= */

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! 💰`
    );
}

/* =========================
   QUEST CHECK
========================= */

function checkQuests(
    currentScore,
    currentCoins,
    totalJumps
) {

    cachedActiveQuests.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();
            }
        }
    });
}

/* =========================
   QUEST NOTIFICATION
========================= */

function triggerQuestNotification() {

    const notifier =
        document.getElementById('quest-notifier');

    if (notifier) {

        notifier.classList.add('show');

        setTimeout(() => {

            notifier.classList.remove('show');

        }, 3500);
    }
}

/* =========================
   TOAST
========================= */

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);
}

/* =========================
   SCREEN
========================= */

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';
}

/* =========================
   PANEL
========================= */

function togglePanel(id) {

    const el =
        document.getElementById(id);

    el.classList.contains('hidden')
        ? el.classList.remove('hidden')
        : el.classList.add('hidden');
}

/* =========================
   MENU STATS
========================= */

function updateMenuStats() {

    document.getElementById('menuHighScore').innerText =
        state.highScore;

    document.getElementById('menuTotalCoins').innerText =
        state.totalCoins;
}

/* =========================
   CANVAS ENGINE
========================= */

let canvas,
    ctx,
    gameLoopId,
    gameActive = false;

let player,
    obstacles,
    items,
    particles,
    stars;

let score,
    coinsCollected,
    totalJumpsRun,
    gameSpeed,
    spawnTimer,
    groundY;

/* =========================
   INIT GAME
========================= */

function initGame() {

    canvas =
        document.getElementById('gameCanvas');

    ctx =
        canvas.getContext('2d');

    resizeCanvas();

    if (!window.__penguinDashResizeBound) {
        window.addEventListener('resize', resizeCanvas);
        window.__penguinDashResizeBound = true;
    }

    score = 0;
    coinsCollected = 0;
    totalJumpsRun = 0;
    gameSpeed = 6;
    spawnTimer = 0;

    obstacles = [];
    items = [];
    particles = [];
    stars = [];

    checkAndRotateDailyQuests();

    for (let i = 0; i < 40; i++) {

        stars.push({

            x: Math.random() * canvas.width,

            y: Math.random() * groundY,

            size: Math.random() * 2 + 1,

            speed: Math.random() * 0.5 + 0.1

        });
    }

    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);

    if (!canvas.__penguinDashInputBound) {
        canvas.addEventListener('pointerdown', (e) => {
            if (e.pointerType === 'mouse' && e.button !== 0) return;
            e.preventDefault();
            playerJump();
        });
        canvas.__penguinDashInputBound = true;
    }

    gameActive = true;

    gameLoop();
}

/* =========================
   RESIZE
========================= */

function resizeCanvas() {

    if (!canvas) return;

    canvas.width = window.innerWidth;

    canvas.height = window.innerHeight;

    groundY = canvas.height * 0.8;

    if (
        player &&
        player.y > groundY - player.h
    ) {

        player.y =
            groundY - player.h;
    }
}

/* =========================
   JUMP
========================= */

function playerJump() {

    if (
        gameActive &&
        (
            !player.isJumping ||
            player.y >= groundY - player.h
        )
    ) {

        player.vy = player.jumpForce;

        player.isJumping = true;

        totalJumpsRun++;

        createParticles(
            player.x + player.w / 2,
            groundY,
            '#38bdf8',
            12
        );
    }
}

/* =========================
   PARTICLES
========================= */

function createParticles(
    x,
    y,
    color,
    count
) {

    for (let i = 0; i < count; i++) {

        particles.push({

            x,
            y,

            vx:
                (Math.random() - 0.5) * 6,

            vy:
                (Math.random() - 0.5) * 6,

            size:
                Math.random() * 4 + 2,

            alpha: 1,

            color: color
        });
    }
}

/* =========================
   GAME LOOP
========================= */

function gameLoop() {

    if (!gameActive) return;

    ctx.clearRect(
        0,
        0,
        canvas.width,
        canvas.height
    );

    /* STARS */

    ctx.fillStyle = '#ffffff';

    stars.forEach(star => {

        star.x -= star.speed;

        if (star.x < 0) {
            star.x = canvas.width;
        }

        ctx.fillRect(
            star.x,
            star.y,
            star.size,
            star.size
        );
    });

    /* PLAYER */

    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;
    }

    /* GROUND */

    ctx.fillStyle = cachedGroundColor;

    ctx.fillRect(
        0,
        groundY,
        canvas.width,
        canvas.height - groundY
    );

    ctx.strokeStyle = cachedBorderColor;

    ctx.lineWidth = 4;

    ctx.beginPath();

    ctx.moveTo(
        0,
        groundY
    );

    ctx.lineTo(
        canvas.width,
        groundY
    );

    ctx.stroke();

    /* PENGUIN */

    ctx.font =
        `${player.h}px sans-serif`;

    ctx.textBaseline = 'top';

    ctx.fillText(
        state.activeSkin,
        player.x,
        player.y
    );

    /* SPAWN */

    spawnTimer++;

    if (
        spawnTimer >
        Math.max(
            30,
            80 - gameSpeed * 3
        )
    ) {

        spawnTimer = 0;

        if (Math.random() < 0.55) {

            items.push({

                x: canvas.width,

                y:
                    groundY -
                    50 -
                    Math.random() * 95,

                w: 25,

                h: 25
            });

        } else {

            obstacles.push({

                x: canvas.width,

                y: groundY,

                w: 35,

                h: 42
            });
        }
    }

    /* OBSTACLES */

    for (
        let i = obstacles.length - 1;
        i >= 0;
        i--
    ) {

        let o = obstacles[i];

        o.x -= gameSpeed;

        ctx.fillStyle =
            cachedBorderColor ||
            '#ef4444';

        ctx.beginPath();

        ctx.moveTo(
            o.x,
            o.y
        );

        ctx.lineTo(
            o.x + o.w / 2,
            o.y - o.h
        );

        ctx.lineTo(
            o.x + o.w,
            o.y
        );

        ctx.closePath();

        ctx.fill();

        if (
            player.x < o.x + o.w &&
            player.x + player.w > o.x &&
            player.y + player.h >
                o.y - o.h
        ) {

            gameOver();

            return;
        }

        if (o.x < -50) {
            obstacles.splice(i, 1);
        }
    }

    /* COINS */

    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.w / 2,
                c.y + c.h / 2,
                '#fbbf24',
                8
            );

            items.splice(i, 1);

            continue;
        }

        if (c.x < -50) {
            items.splice(i, 1);
        }
    }

    /* PARTICLES */

    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 = p.color;

        ctx.beginPath();

        ctx.arc(
            p.x,
            p.y,
            p.size,
            0,
            Math.PI * 2
        );

        ctx.fill();

        ctx.restore();
    }

    /* SCORE */

    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);
}

/* =========================
   GAME OVER
========================= */

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'
    );

    updateMenuStats();
}

/* =========================
   START GAME
========================= */

function launchDirectGame() {

    document
        .getElementById('mainMenu')
        .classList.add('hidden');

    document
        .getElementById('gameOverScreen')
        .classList.add('hidden');

    changeScreen(
        'mainMenu',
        'gameContainer'
    );

    initGame();
}

/* =========================
   SHOP
========================= */

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>
        `;
    }
}

/* =========================
   BUY SKIN
========================= */

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();

    switchTab('skins');

    updateMenuStats();
}

/* =========================
   BUY THEME
========================= */

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();

    switchTab('themes');

    updateMenuStats();
}

/* =========================
   BUY UPGRADE
========================= */

function buyUpgrade(type) {

    let conf =
        UPGRADES_CONFIG[type];

    let curLvl =
        type === 'coinMultiplier'
            ? state.coinMultiplierLevel
            : state.jumpForceLevel;

    let cost =
        curLvl * conf.baseCost;

    if (curLvl >= conf.max) {
        return;
    }

    if (state.totalCoins >= cost) {

        state.totalCoins -= cost;

        if (type === 'coinMultiplier') {

            state.coinMultiplierLevel++;

        } else {

            state.jumpForceLevel++;
        }

        saveState();

        switchTab('upgrades');

        updateMenuStats();

        showToast(
            'Upgrade erfolgreich! ⚡'
        );

    } else {

        showToast(
            'Nicht genug Coins! 🪙'
        );
    }
}

/* =========================
   APPLY THEME
========================= */

function applyTheme(id) {

    let t =
        ALL_THEMES.find(
            x => x.id === id
        );

    if (!t) return;

    let 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
    );

    cachedGroundColor =
        t.ground;

    cachedBorderColor =
        t.border;
}

/* =========================
   DAILY REWARD
========================= */

const DAILY_REWARDS =
    [15, 25, 40, 60, 80, 110, 150, 250];

function updateDailyUI() {

    const grid =
        document.getElementById('dailyGrid');

    grid.innerHTML = '';

    let lastClaim =
        state.lastDailyClaim
            ? new Date(state.lastDailyClaim)
            : null;

    let today = new Date();

    today.setHours(0,0,0,0);

    let streak =
        parseInt(
            localStorage.getItem(
                'pd_daily_streak'
            )
        ) || 0;

    if (lastClaim) {

        lastClaim.setHours(0,0,0,0);

        let diff =
            (today - lastClaim) /
            (1000 * 60 * 60 * 24);

        if (diff > 1) {
            streak = 0;
        }
    }

    let alreadyClaimedToday =
        lastClaim &&
        lastClaim.getTime() === today.getTime();

    let currentDayIndex =
        alreadyClaimedToday
            ? -1
            : (streak % 8);

    for (let i = 0; i < 8; i++) {

        let statusText =
            `Tag ${i+1}<br>${DAILY_REWARDS[i]}🪙`;

        let cls =
            'daily-day';

        if (
            i <
            (
                alreadyClaimedToday
                    ? (streak % 8 || 8)
                    : (streak % 8)
            )
        ) {

            cls += ' completed';

            statusText =
                `Tag ${i+1}<br>✔`;

        } else if (
            i === currentDayIndex
        ) {

            cls += ' current';
        }

        grid.innerHTML += `

            <div class="${cls}">
                ${statusText}
            </div>
        `;
    }

    document
        .getElementById('claimDailyBtn')
        .disabled =
            alreadyClaimedToday;
}

/* =========================
   CLAIM DAILY REWARD
========================= */

function claimDailyReward() {

    let today = new Date();

    today.setHours(0,0,0,0);

    let streak =
        parseInt(
            localStorage.getItem(
                'pd_daily_streak'
            )
        ) || 0;

    let lastClaim =
        state.lastDailyClaim
            ? new Date(state.lastDailyClaim)
            : null;

    if (lastClaim) {

        lastClaim.setHours(0,0,0,0);

        if (
            (today - lastClaim) /
            (1000 * 60 * 60 * 24) > 1
        ) {

            streak = 0;
        }
    }

    let amt =
        DAILY_REWARDS[streak % 8];

    state.totalCoins += amt;

    state.lastDailyClaim =
        new Date().toISOString();

    localStorage.setItem(
        'pd_daily_streak',
        streak + 1
    );

    saveState();

    updateDailyUI();

    updateMenuStats();

    showToast(
        `Tägliche Belohnung: +${amt} Coins! 🎁`
    );
}

/* =====================================================
   LADEANIMATION: 1,5 SEKUNDEN BIS 99% + 2,5 SEKUNDEN HALTEN
===================================================== */
const loaderBar=document.getElementById('loaderBar');
const loaderPercent=document.getElementById('loaderPercent');
const loaderStatus=document.getElementById('loaderStatus');
const loaderFillDuration = 1500;
const loaderHoldDuration = 2500;
const loaderTotalDuration = loaderFillDuration + loaderHoldDuration;
const loaderStart=performance.now();
function updateLoader(currentTime){
 const elapsed=currentTime-loaderStart;
 if(elapsed<loaderFillDuration){
  const progress = Math.max(0, Math.min(1, elapsed / loaderFillDuration));
  const percent = Math.min(99, Math.floor(progress * 99));
  loaderBar.style.width = percent + '%';
  loaderPercent.textContent = percent + '%';
  loaderStatus.textContent = 'Wird geladen...';
  requestAnimationFrame(updateLoader); return;
 }
 if(elapsed<loaderTotalDuration){
  loaderBar.style.width='99%'; loaderPercent.textContent='99%'; loaderStatus.textContent='Fast fertig...';
  requestAnimationFrame(updateLoader); return;
 }
 loaderBar.style.width='100%'; loaderPercent.textContent='100%'; loaderStatus.textContent='Fertig!';
 document.getElementById('loadingScreen').classList.add('hidden');
 document.getElementById('mainMenu').classList.remove('hidden');
 checkAndRotateDailyQuests(); updateMenuStats(); applyTheme(state.activeTheme);
}
requestAnimationFrame(updateLoader);
</script>

</body>
</html>

Game Source: Penguin Dash: Overdrive Edition

Creator: BlazeKoala99

Libraries: none

Complexity: complex (2548 lines, 59.5 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-blazekoala99" to link back to the original. Then publish at arcadelab.ai/publish.