Penguin Dash: Overdrive Edition
by BlazeKoala992548 lines59.5 KB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Penguin Dash: Overdrive Edition</title>
<style>
:root {
--bg-grad-top: #0f172a;
--bg-grad-mid: #1e1b4b;
--bg-grad-bottom: #2e1065;
--ground-color: #1e293b;
--ground-border: #38bdf8;
--accent-color: #06b6d4;
--panel-bg: rgba(15, 23, 42, 0.65);
--text-color: #f8fafc;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
background: #020617;
user-select: none;
-webkit-user-select: none;
color: var(--text-color);
}
.screen {
position: absolute;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
transition: all 0.3s ease-in-out;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: rgba(2, 6, 23, 0.8);
}
.hidden {
opacity: 0;
pointer-events: none;
transform: scale(1.05);
}
.glass-panel {
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 28px;
padding: 25px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
text-align: center;
max-width: 480px;
width: 92%;
box-sizing: border-box;
}
h1 {
font-size: 2.5rem;
color: var(--accent-color);
margin-bottom: 5px;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 0;
}
h2 {
font-size: 1.8rem;
margin-top: 0;
margin-bottom: 10px;
}
.btn {
padding: 14px 20px;
font-size: 15px;
font-weight: 700;
border: none;
border-radius: 14px;
color: white;
cursor: pointer;
transition: transform 0.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.