Penguin Dash: Overdrive
by WildGecko803533 lines61.8 KB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#06111f">
<title>Penguin Dash: Overdrive</title>
<style>
:root{
--cyan:#22d3ee;
--blue:#3b82f6;
--purple:#8b5cf6;
--green:#34d399;
--yellow:#facc15;
--red:#fb7185;
--bg:#020617;
--panel:rgba(8,18,35,.78);
--border:rgba(148,163,184,.16);
--text:#f8fafc;
--muted:#94a3b8;
}
*{
box-sizing:border-box;
-webkit-tap-highlight-color:transparent;
}
html,body{
width:100%;
height:100%;
margin:0;
overflow:hidden;
background:#020617;
color:var(--text);
font-family:
Inter,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
user-select:none;
}
button{
font:inherit;
}
.hidden{
opacity:0!important;
pointer-events:none!important;
visibility:hidden;
}
.screen{
position:fixed;
inset:0;
z-index:100;
display:flex;
justify-content:center;
align-items:center;
overflow:auto;
padding:20px;
background:
radial-gradient(circle at 20% 20%,rgba(34,211,238,.12),transparent 28%),
radial-gradient(circle at 80% 70%,rgba(139,92,246,.14),transparent 30%),
rgba(2,6,23,.88);
backdrop-filter:blur(18px);
transition:.35s ease;
}
.screen::before{
content:"";
position:absolute;
inset:0;
pointer-events:none;
opacity:.28;
background-image:
linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),
linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);
background-size:34px 34px;
}
.panel{
position:relative;
z-index:2;
width:min(94vw,560px);
max-height:92vh;
overflow:auto;
padding:26px;
border:1px solid var(--border);
border-radius:30px;
background:
linear-gradient(145deg,rgba(15,30,52,.92),rgba(4,11,24,.86));
box-shadow:
0 30px 90px rgba(0,0,0,.55),
inset 0 1px rgba(255,255,255,.06),
0 0 60px rgba(34,211,238,.05);
}
.logo{
margin:0;
text-align:center;
font-size:clamp(2.4rem,9vw,4.8rem);
line-height:.9;
font-weight:1000;
letter-spacing:-3px;
text-transform:uppercase;
text-shadow:
0 0 20px rgba(34,211,238,.4),
0 0 55px rgba(34,211,238,.15);
}
.logo span{
color:var(--cyan);
}
.subtitle{
text-align:center;
color:var(--muted);
margin:12px 0 24px;
letter-spacing:3px;
text-transform:uppercase;
font-size:.72rem;
font-weight:900;
}
.stats{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:9px;
margin-bottom:16px;
}
.stat{
padding:12px 8px;
text-align:center;
border:1px solid var(--border);
border-radius:16px;
background:rgba(255,255,255,.035);
}
.stat b{
display:block;
color:var(--cyan);
font-size:1.2rem;
}
.stat small{
color:var(--muted);
font-size:.62rem;
text-transform:uppercase;
letter-spacing:1px;
}
.btn{
width:100%;
border:0;
border-radius:16px;
padding:14px 16px;
margin-top:9px;
color:#fff;
font-weight:900;
text-transform:uppercase;
letter-spacing:.8px;
cursor:pointer;
transition:.16s ease;
box-shadow:0 8px 22px rgba(0,0,0,.2);
}
.btn:hover{
transform:translateY(-2px);
filter:brightness(1.08);
}
.btn:active{
transform:scale(.97);
}
.primary{
background:linear-gradient(135deg,#06b6d4,#2563eb);
}
.secondary{
background:rgba(255,255,255,.07);
border:1px solid var(--border);
}
.gold{
background:linear-gradient(135deg,#eab308,#f97316);
}
.danger{
background:linear-gradient(135deg,#ef4444,#be123c);
}
.green{
background:linear-gradient(135deg,#10b981,#059669);
}
.menu-grid{
display:grid;
grid-template-columns:1fr 1fr;
gap:9px;
margin-top:9px;
}
.menu-grid .btn{
margin:0;
}
.section-title{
display:flex;
align-items:center;
justify-content:space-between;
margin-bottom:15px;
}
.section-title h2{
margin:0;
font-size:1.45rem;
}
.close{
width:40px;
height:40px;
border-radius:12px;
border:1px solid var(--border);
color:white;
background:rgba(255,255,255,.06);
cursor:pointer;
font-size:1.2rem;
}
/* LOADING */
#loadingScreen{
z-index:999;
overflow:hidden;
background:
radial-gradient(circle at 50% 42%,rgba(34,211,238,.18),transparent 25%),
radial-gradient(circle at 15% 80%,rgba(59,130,246,.13),transparent 28%),
#020617;
}
.loading{
position:relative;
z-index:2;
width:min(88vw,720px);
text-align:center;
}
.loading-badge{
display:inline-flex;
align-items:center;
gap:8px;
padding:7px 14px;
border-radius:999px;
border:1px solid rgba(34,211,238,.25);
color:#a5f3fc;
background:rgba(8,47,73,.5);
font-size:.68rem;
letter-spacing:2px;
font-weight:900;
}
.loading-badge i{
width:7px;
height:7px;
border-radius:50%;
background:var(--cyan);
box-shadow:0 0 14px var(--cyan);
animation:pulse 1s infinite;
}
.loading h1{
margin:25px 0 8px;
font-size:clamp(3rem,13vw,7.5rem);
line-height:.82;
letter-spacing:-5px;
font-weight:1000;
}
.loading h1 span{
color:var(--cyan);
}
.loading p{
color:var(--muted);
text-transform:uppercase;
letter-spacing:4px;
font-size:.78rem;
font-weight:800;
}
.loader-box{
margin-top:40px;
}
.loader{
height:30px;
padding:4px;
border-radius:999px;
background:#020617;
border:1px solid rgba(148,163,184,.22);
box-shadow:inset 0 0 15px #000;
overflow:hidden;
}
#loaderBar{
height:100%;
width:0%;
border-radius:999px;
background:linear-gradient(
90deg,
#0891b2,
#22d3ee,
#a5f3fc,
#fff
);
box-shadow:
0 0 15px rgba(34,211,238,.9),
0 0 35px rgba(34,211,238,.45);
position:relative;
}
#loaderBar::after{
content:"";
position:absolute;
top:0;
right:-100px;
width:100px;
height:100%;
background:linear-gradient(
90deg,
transparent,
rgba(255,255,255,.9),
transparent
);
animation:shine .7s linear infinite;
}
.loader-info{
display:flex;
justify-content:space-between;
align-items:center;
margin-top:12px;
}
#loaderStatus{
color:var(--cyan);
font-size:.75rem;
font-weight:900;
text-transform:uppercase;
letter-spacing:1px;
}
#loaderPercent{
font-size:1.8rem;
font-weight:1000;
}
.loading-penguin{
font-size:4rem;
margin-top:22px;
animation:float 1.5s ease-in-out infinite;
filter:drop-shadow(0 0 20px rgba(34,211,238,.5));
}
/* GAME */
#game{
position:fixed;
inset:0;
overflow:hidden;
background:
linear-gradient(
to bottom,
#06152b 0%,
#102a4c 52%,
#13283b 100%
);
}
#gameCanvas{
position:absolute;
inset:0;
width:100%;
height:100%;
touch-action:none;
}
#hud{
position:absolute;
z-index:20;
top:max(12px,env(safe-area-inset-top));
left:12px;
right:12px;
display:flex;
justify-content:space-between;
align-items:flex-start;
pointer-events:none;
}
.hud-left,
.hud-right{
display:flex;
gap:7px;
}
.hud-card{
min-width:75px;
padding:8px 11px;
border-radius:13px;
border:1px solid rgba(255,255,255,.1);
background:rgba(2,6,23,.62);
backdrop-filter:blur(10px);
text-align:center;
box-shadow:0 7px 20px rgba(0,0,0,.18);
}
.hud-card small{
display:block;
color:#94a3b8;
font-size:.58rem;
font-weight:900;
text-transform:uppercase;
}
.hud-card b{
display:block;
margin-top:2px;
color:#fff;
font-size:1rem;
}
#comboBox{
position:absolute;
z-index:21;
top:95px;
left:50%;
transform:translateX(-50%) scale(.8);
opacity:0;
pointer-events:none;
text-align:center;
transition:.2s ease;
}
#comboBox.show{
opacity:1;
transform:translateX(-50%) scale(1);
}
#comboNumber{
font-size:2.7rem;
font-weight:1000;
color:#facc15;
text-shadow:0 0 25px rgba(250,204,21,.55);
}
#comboText{
color:#fff;
font-size:.65rem;
letter-spacing:3px;
font-weight:900;
}
#powerupStatus{
position:absolute;
z-index:21;
left:50%;
bottom:28px;
transform:translateX(-50%);
display:flex;
gap:8px;
pointer-events:none;
}
.power-indicator{
display:none;
padding:8px 12px;
border-radius:999px;
background:rgba(2,6,23,.72);
border:1px solid rgba(255,255,255,.12);
font-weight:900;
font-size:.72rem;
backdrop-filter:blur(10px);
}
#pauseButton{
position:absolute;
z-index:30;
right:13px;
top:90px;
width:42px;
height:42px;
border-radius:13px;
border:1px solid rgba(255,255,255,.12);
color:#fff;
background:rgba(2,6,23,.6);
cursor:pointer;
}
/* SHOP */
.shop-tabs{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:6px;
margin-bottom:13px;
}
.tab{
padding:10px 5px;
border:0;
border-radius:11px;
background:rgba(255,255,255,.05);
color:var(--muted);
font-weight:900;
cursor:pointer;
}
.tab.active{
background:var(--cyan);
color:#042f3a;
}
.shop-grid{
display:grid;
grid-template-columns:repeat(2,1fr);
gap:10px;
max-height:52vh;
overflow:auto;
padding-right:3px;
}
.item{
position:relative;
min-height:150px;
padding:13px;
border-radius:18px;
border:1px solid var(--border);
background:rgba(255,255,255,.035);
display:flex;
flex-direction:column;
justify-content:space-between;
text-align:center;
}
.item-icon{
font-size:2.8rem;
filter:drop-shadow(0 5px 10px rgba(0,0,0,.3));
}
.item-name{
font-weight:900;
font-size:.78rem;
}
.item-rarity{
font-size:.55rem;
color:var(--muted);
text-transform:uppercase;
letter-spacing:1px;
}
.item button{
border:0;
border-radius:9px;
padding:8px;
color:white;
background:linear-gradient(135deg,#06b6d4,#2563eb);
font-size:.68rem;
font-weight:900;
cursor:pointer;
}
/* QUESTS */
.quest-list{
display:flex;
flex-direction:column;
gap:8px;
max-height:58vh;
overflow:auto;
}
.quest{
padding:12px;
border-radius:15px;
border:1px solid var(--border);
background:rgba(255,255,255,.035);
}
.quest-top{
display:flex;
justify-content:space-between;
gap:10px;
}
.quest-title{
font-size:.78rem;
font-weight:900;
}
.quest-reward{
color:#facc15;
font-weight:1000;
white-space:nowrap;
}
.quest-bar{
height:6px;
margin-top:8px;
border-radius:99px;
background:#020617;
overflow:hidden;
}
.quest-progress{
height:100%;
background:linear-gradient(90deg,#06b6d4,#8b5cf6);
}
.quest.ready{
border-color:rgba(250,204,21,.5);
}
/* DAILY */
.daily-grid{
display:grid;
grid-template-columns:repeat(4,1fr);
gap:8px;
margin:15px 0;
}
.daily{
padding:10px 4px;
border-radius:12px;
border:1px solid var(--border);
background:rgba(255,255,255,.035);
text-align:center;
font-size:.7rem;
}
.daily.active{
border-color:var(--cyan);
box-shadow:0 0 18px rgba(34,211,238,.15);
}
.daily.claimed{
opacity:.45;
}
.reward-big{
text-align:center;
font-size:4rem;
margin:10px 0;
}
/* GAME OVER */
.result-score{
text-align:center;
font-size:4rem;
line-height:1;
font-weight:1000;
color:var(--cyan);
text-shadow:0 0 30px rgba(34,211,238,.3);
}
.result-label{
text-align:center;
color:var(--muted);
text-transform:uppercase;
letter-spacing:3px;
font-size:.65rem;
font-weight:900;
}
.result-grid{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:8px;
margin:20px 0;
}
/* TOAST */
#toast{
position:fixed;
z-index:1000;
left:50%;
bottom:25px;
transform:translate(-50%,80px);
opacity:0;
padding:11px 17px;
border-radius:13px;
background:rgba(2,6,23,.92);
border:1px solid rgba(34,211,238,.3);
box-shadow:0 12px 35px rgba(0,0,0,.4);
font-size:.78rem;
font-weight:900;
transition:.3s ease;
pointer-events:none;
}
#toast.show{
transform:translate(-50%,0);
opacity:1;
}
/* PAUSE */
.pause-content{
text-align:center;
}
.pause-icon{
font-size:4rem;
margin-bottom:8px;
}
/* ANIMATIONS */
@keyframes pulse{
50%{opacity:.4;transform:scale(.75)}
}
@keyframes shine{
from{transform:translateX(-20px)}
to{transform:translateX(120px)}
}
@keyframes float{
0%,100%{transform:translateY(0) rotate(-2deg)}
50%{transform:translateY(-10px) rotate(2deg)}
}
@keyframes pop{
0%{transform:scale(.5);opacity:0}
70%{transform:scale(1.12);opacity:1}
100%{transform:scale(1);opacity:1}
}
.pop{
animation:pop .25s ease;
}
@media(max-width:500px){
.panel{
padding:19px;
border-radius:24px;
}
.menu-grid{
grid-template-columns:1fr;
}
.stats{
grid-template-columns:repeat(3,1fr);
}
.hud-card{
min-width:64px;
padding:7px 8px;
}
.hud-card b{
font-size:.85rem;
}
.shop-grid{
grid-template-columns:repeat(2,1fr);
}
.loading h1{
letter-spacing:-3px;
}
}
</style>
</head>
<body>
<!-- =====================================================
LOADING
===================================================== -->
<div id="loadingScreen" class="screen">
<div class="loading">
<div class="loading-badge">
<i></i>
OVERDRIVE SYSTEM ONLINE
</div>
<h1>
PENGUIN <span>DASH</span>
</h1>
<p>
Overdrive Edition
</p>
<div class="loader-box">
<div class="loader">
<div id="loaderBar"></div>
</div>
<div class="loader-info">
<span id="loaderStatus">
Wird geladen...
</span>
<span id="loaderPercent">
0%
</span>
</div>
</div>
<div class="loading-penguin">
๐ง
</div>
</div>
</div>
<!-- =====================================================
MENU
===================================================== -->
<div id="menuScreen" class="screen hidden">
<div class="panel">
<h1 class="logo">
PENGUIN <span>DASH</span>
</h1>
<div class="subtitle">
Overdrive Edition
</div>
<div class="stats">
<div class="stat">
<b id="menuScore">0</b>
<small>Highscore</small>
</div>
<div class="stat">
<b id="menuCoins">0</b>
<small>Coins</small>
</div>
<div class="stat">
<b id="menuLevel">1</b>
<small>Level</small>
</div>
</div>
<button class="btn primary" onclick="startGame()">
๐ง Spiel starten
</button>
<div class="menu-grid">
<button class="btn secondary" onclick="openPanel('shopScreen')">
๐๏ธ Shop
</button>
<button class="btn secondary" onclick="openQuests()">
๐ Quests
</button>
<button class="btn secondary" onclick="openDaily()">
๐ Daily
</button>
<button class="btn secondary" onclick="showControls()">
๐ฎ Steuerung
</button>
</div>
<div style="
margin-top:18px;
padding:12px;
border-radius:15px;
background:rgba(34,211,238,.05);
border:1px solid rgba(34,211,238,.1);
text-align:center;
color:#94a3b8;
font-size:.68rem;
">
SPACE / โ / TIPpen = Springen<br>
Zweimal springen = Doppelsprung
</div>
</div>
</div>
<!-- =====================================================
SHOP
===================================================== -->
<div id="shopScreen" class="screen hidden">
<div class="panel">
<div class="section-title">
<h2>๐๏ธ Shop</h2>
<button class="close" onclick="closePanel('shopScreen')">
ร
</button>
</div>
<div style="
text-align:center;
margin-bottom:14px;
color:#facc15;
font-weight:1000;
">
๐ช <span id="shopCoins">0</span>
</div>
<div class="shop-tabs">
<button
id="tabSkins"
class="tab active"
onclick="shopTab('skins')">
Skins
</button>
<button
id="tabThemes"
class="tab"
onclick="shopTab('themes')">
Welten
</button>
<button
id="tabUpgrades"
class="tab"
onclick="shopTab('upgrades')">
Upgrades
</button>
</div>
<div id="shopGrid" class="shop-grid"></div>
</div>
</div>
<!-- =====================================================
QUESTS
===================================================== -->
<div id="questScreen" class="screen hidden">
<div class="panel">
<div class="section-title">
<h2>๐ Tages-Quests</h2>
<button class="close" onclick="closePanel('questScreen')">
ร
</button>
</div>
<div id="questList" class="quest-list"></div>
</div>
</div>
<!-- =====================================================
DAILY
===================================================== -->
<div id="dailyScreen" class="screen hidden">
<div class="panel">
<div class="section-title">
<h2>๐ Daily Reward</h2>
<button class="close" onclick="closePanel('dailyScreen')">
ร
</button>
</div>
<div class="reward-big">
๐
</div>
<div style="
text-align:center;
color:#94a3b8;
font-size:.78rem;
">
Komm jeden Tag zurรผck und erhรถhe deine Belohnung!
</div>
<div id="dailyGrid" class="daily-grid"></div>
<button
id="dailyButton"
class="btn gold"
onclick="claimDaily()">
Belohnung abholen
</button>
</div>
</div>
<!-- =====================================================
GAME
===================================================== -->
<div id="game">
<canvas id="gameCanvas"></canvas>
<div id="hud">
<div class="hud-left">
<div class="hud-card">
<small>Score</small>
<b id="hudScore">0</b>
</div>
<div class="hud-card">
<small>Coins</small>
<b id="hudCoins">0</b>
</div>
</div>
<div class="hud-right">
<div class="hud-card">
<small>Level</small>
<b id="hudLevel">1</b>
</div>
</div>
</div>
<div id="comboBox">
<div id="comboNumber">
x2
</div>
<div id="comboText">
COMBO
</div>
</div>
<div id="powerupStatus">
<div id="shieldPower" class="power-indicator">
๐ก๏ธ Schild
</div>
<div id="magnetPower" class="power-indicator">
๐งฒ Magnet
</div>
<div id="slowPower" class="power-indicator">
โ๏ธ Zeitlupe
</div>
</div>
<button id="pauseButton" onclick="togglePause()">
โธ
</button>
</div>
<!-- =====================================================
PAUSE
===================================================== -->
<div id="pauseScreen" class="screen hidden">
<div class="panel pause-content">
<div class="pause-icon">
โธ๏ธ
</div>
<h2>Pause</h2>
<p style="color:#94a3b8">
Dein Run wartet auf dich.
</p>
<button class="btn primary" onclick="togglePause()">
โถ Weiter
</button>
<button class="btn secondary" onclick="quitGame()">
Menรผ
</button>
</div>
</div>
<!-- =====================================================
GAME OVER
===================================================== -->
<div id="gameOverScreen" class="screen hidden">
<div class="panel">
<div class="result-label">
RUN BEENDET
</div>
<div id="finalScore" class="result-score">
0
</div>
<div class="result-label">
Punkte
</div>
<div class="result-grid">
<div class="stat">
<b id="finalCoins">0</b>
<small>Coins</small>
</div>
<div class="stat">
<b id="finalCombo">0</b>
<small>Combo</small>
</div>
<div class="stat">
<b id="finalLevel">1</b>
<small>Level</small>
</div>
</div>
<div id="newRecord" style="
display:none;
text-align:center;
color:#facc15;
font-weight:1000;
margin:12px;
">
๐ NEUER HIGHSCORE!
</div>
<button class="btn primary" onclick="startGame()">
๐ Nochmal
</button>
<button class="btn secondary" onclick="goMenu()">
๐ Hauptmenรผ
</button>
</div>
</div>
<!-- =====================================================
TOAST
===================================================== -->
<div id="toast"></div>
<script>
"use strict";
/* =====================================================
DATA
===================================================== */
const SKINS = [
{id:"๐ง",name:"Classic",cost:0,rarity:"Common"},
{id:"๐ถ๏ธ",name:"Cool Penguin",cost:60,rarity:"Rare"},
{id:"๐ฅท",name:"Ninja",cost:120,rarity:"Rare"},
{id:"๐",name:"Penguin King",cost:250,rarity:"Epic"},
{id:"๐",name:"Astronaut",cost:400,rarity:"Epic"},
{id:"๐ค",name:"Cyber Penguin",cost:650,rarity:"Legendary"},
{id:"๐ฅ",name:"Inferno",cost:900,rarity:"Legendary"},
{id:"๐",name:"Diamond",cost:1400,rarity:"Legendary"},
{id:"๐ป",name:"Ghost",cost:1800,rarity:"Mythic"},
{id:"๐",name:"Dragon",cost:2500,rarity:"Mythic"}
];
const THEMES = [
{
id:"arctic",
name:"Arctic",
cost:0,
sky1:"#06152b",
sky2:"#17456a",
ground:"#173044"
},
{
id:"neon",
name:"Neon City",
cost:500,
sky1:"#16062b",
sky2:"#43115f",
ground:"#180e2c"
},
{
id:"sunset",
name:"Sunset",
cost:800,
sky1:"#43111b",
sky2:"#b84d24",
ground:"#35191c"
},
{
id:"void",
name:"Void",
cost:1500,
sky1:"#02020c",
sky2:"#20134d",
ground:"#0c0a1d"
}
];
const QUESTS = [
{
text:"Erreiche 100 Punkte",
type:"score",
goal:100,
reward:50
},
{
text:"Sammle 15 Coins",
type:"coins",
goal:15,
reward:70
},
{
text:"Erreiche einen x5 Combo",
type:"combo",
goal:5,
reward:80
},
{
text:"Springe 25 Mal",
type:"jumps",
goal:25,
reward:60
},
{
text:"Erreiche 250 Punkte",
type:"score",
goal:250,
reward:150
},
{
text:"Sammle 35 Coins",
type:"coins",
goal:35,
reward:180
}
];
const DAILY_REWARDS=[
25,40,60,90,130,180,300
];
/* =====================================================
SAVE DATA
===================================================== */
let save = JSON.parse(
localStorage.getItem("penguinDashSave") || "null"
);
if(!save){
save={
coins:0,
highscore:0,
level:1,
xp:0,
skin:"๐ง",
ownedSkins:["๐ง"],
theme:"arctic",
ownedThemes:["arctic"],
coinUpgrade:1,
jumpUpgrade:1,
dailyDay:0,
lastDaily:"",
questDay:"",
quests:[]
};
}
function persist(){
localStorage.setItem(
"penguinDashSave",
JSON.stringify(save)
);
}
/* =====================================================
DOM
===================================================== */
const loadingScreen=
document.getElementById("loadingScreen");
const menuScreen=
document.getElementById("menuScreen");
const gameOverScreen=
document.getElementById("gameOverScreen");
const canvas=
document.getElementById("gameCanvas");
const ctx=
canvas.getContext("2d");
const game=
document.getElementById("game");
/* =====================================================
LOADING
0 -> 99 in 2.5 sec
99 hold 3 sec
100 hold 1.5 sec
===================================================== */
const loaderBar=
document.getElementById("loaderBar");
const loaderPercent=
document.getElementById("loaderPercent");
const loaderStatus=
document.getElementById("loaderStatus");
const loaderStart=
performance.now();
const FILL_TIME=2500;
const HOLD_99=3000;
const HOLD_100=1500;
function loadingLoop(now){
const elapsed=
now-loaderStart;
if(elapsed<FILL_TIME){
const p=
elapsed/FILL_TIME;
const percent=
Math.floor(p*99);
loaderBar.style.width=
percent+"%";
loaderPercent.textContent=
percent+"%";
loaderStatus.textContent=
"Wird geladen...";
requestAnimationFrame(loadingLoop);
return;
}
if(elapsed<FILL_TIME+HOLD_99){
loaderBar.style.width="99%";
loaderPercent.textContent="99%";
loaderStatus.textContent="Overdrive wird initialisiert...";
requestAnimationFrame(loadingLoop);
return;
}
loaderBar.style.width="100%";
loaderPercent.textContent="100%";
loaderStatus.textContent="Fertig!";
if(elapsed<FILL_TIME+HOLD_99+HOLD_100){
requestAnimationFrame(loadingLoop);
return;
}
loadingScreen.classList.add("hidden");
menuScreen.classList.remove("hidden");
updateMenu();
prepareQuests();
applyTheme();
}
requestAnimationFrame(loadingLoop);
/* =====================================================
MENU
===================================================== */
function updateMenu(){
document.getElementById("menuScore").textContent=
Math.floor(save.highscore);
document.getElementById("menuCoins").textContent=
save.coins;
document.getElementById("menuLevel").textContent=
save.level;
}
function openPanel(id){
document.getElementById(id).classList.remove("hidden");
if(id==="shopScreen"){
shopTab("skins");
document.getElementById("shopCoins").textContent=
save.coins;
}
if(id==="questScreen"){
prepareQuests();
renderQuests();
}
if(id==="dailyScreen"){
renderDaily();
}
}
function closePanel(id){
document.getElementById(id).classList.add("hidden");
}
function goMenu(){
gameRunning=false;
paused=false;
gameOverScreen.classList.add("hidden");
document.getElementById("pauseScreen").classList.add("hidden");
menuScreen.classList.remove("hidden");
updateMenu();
}
/* =====================================================
CONTROLS
===================================================== */
function showControls(){
showToast(
"Tippen / SPACE / โ = Sprung โข zweimal = Doppelsprung"
);
}
/* =====================================================
GAME VARIABLES
===================================================== */
let W=innerWidth;
let H=innerHeight;
let gameRunning=false;
let paused=false;
let animationId=0;
let player;
let obstacles=[];
let coins=[];
let powerups=[];
let particles=[];
let snow=[];
let mountains=[];
let score=0;
let runCoins=0;
let jumps=0;
let combo=0;
let maxCombo=0;
let comboTimer=0;
let speed=5;
let level=1;
let spawnTimer=0;
let coinTimer=0;
let powerTimer=0;
let shake=0;
let powerState={
shield:0,
magnet:0,
slow:0
};
/* =====================================================
RESIZE
===================================================== */
function resize(){
W=innerWidth;
H=innerHeight;
const dpr=Math.min(devicePixelRatio||1,2);
canvas.width=W*dpr;
canvas.height=H*dpr;
canvas.style.width=W+"px";
canvas.style.height=H+"px";
ctx.setTransform(
dpr,0,0,dpr,0,0
);
createBackground();
}
window.addEventListener("resize",resize);
/* =====================================================
BACKGROUND
===================================================== */
function createBackground(){
snow=[];
for(let i=0;i<90;i++){
snow.push({
x:Math.random()*W,
y:Math.random()*H,
size:Math.random()*2.5+.5,
speed:Math.random()*1.4+.3,
drift:Math.random()*1.2-.6
});
}
mountains=[];
for(let i=0;i<8;i++){
mountains.push({
x:i*(W/5),
width:W/3+Math.random()*W/5,
height:80+Math.random()*150
});
}
}
/* =====================================================
THEME
===================================================== */
function getTheme(){
return THEMES.find(
t=>t.id===save.theme
)||THEMES[0];
}
function applyTheme(){
const t=getTheme();
game.style.background=
`linear-gradient(to bottom,${t.sky1},${t.sky2} 60%,${t.ground})`;
}
/* =====================================================
START GAME
===================================================== */
function startGame(){
menuScreen.classList.add("hidden");
gameOverScreen.classList.add("hidden");
score=0;
runCoins=0;
jumps=0;
combo=0;
maxCombo=0;
comboTimer=0;
level=1;
speed=5;
spawnTimer=0;
coinTimer=0;
powerTimer=0;
shake=0;
obstacles=[];
coins=[];
powerups=[];
particles=[];
powerState={
shield:0,
magnet:0,
slow:0
};
player={
x:W*.18,
y:H*.68,
width:48,
height:48,
vy:0,
gravity:.75,
jumpPower:-13.5,
jumps:0,
rotation:0
};
gameRunning=true;
paused=false;
applyTheme();
updateHUD();
cancelAnimationFrame(animationId);
animationId=
requestAnimationFrame(gameLoop);
}
/* =====================================================
PLAYER JUMP
===================================================== */
function jump(){
if(!gameRunning||paused)return;
if(player.jumps<2){
player.vy=
player.jumpPower -
(save.jumpUpgrade-1)*.7;
player.jumps++;
jumps++;
burst(
player.x+20,
player.y+42,
"#67e8f9",
8
);
shake=2;
}
}
window.addEventListener("keydown",e=>{
if(
e.code==="Space"||
e.code==="ArrowUp"||
e.code==="KeyW"
){
e.preventDefault();
jump();
}
if(e.code==="Escape"){
togglePause();
}
});
canvas.addEventListener(
"pointerdown",
e=>{
if(gameRunning&&!paused){
e.preventDefault();
jump();
}
}
);
/* =====================================================
PAUSE
===================================================== */
function togglePause(){
if(!gameRunning)return;
paused=!paused;
document
.getElementById("pauseScreen")
.classList.toggle(
"hidden",
!paused
);
if(!paused){
animationId=
requestAnimationFrame(gameLoop);
}
}
/* =====================================================
QUIT
===================================================== */
function quitGame(){
gameRunning=false;
paused=false;
document
.getElementById("pauseScreen")
.classList.add("hidden");
menuScreen.classList.remove("hidden");
updateMenu();
}
/* =====================================================
GAME LOOP
===================================================== */
let lastTime=performance.now();
function gameLoop(now){
if(!gameRunning||paused)return;
const dt=
Math.min(
(now-lastTime)/16.67,
2
);
lastTime=now;
update(dt);
draw();
animationId=
requestAnimationFrame(gameLoop);
}
/* =====================================================
UPDATE
===================================================== */
function update(dt){
const slowFactor=
powerState.slow>0?.58:1;
/* SCORE */
score+=.11*dt*speed*slowFactor;
/* LEVEL */
const newLevel=
Math.floor(score/100)+1;
if(newLevel>level){
level=newLevel;
burst(
player.x,
player.y,
"#a78bfa",
25
);
showToast(
"LEVEL "+level+"! โก"
);
}
speed=
Math.min(
13,
5+
score*.006
);
/* PLAYER */
player.vy+=player.gravity*dt;
player.y+=player.vy*dt;
player.rotation=
Math.max(
-.25,
Math.min(
.25,
player.vy*.025
)
);
const ground=
H*.80;
if(
player.y+player.height>=ground
){
player.y=
ground-player.height;
player.vy=0;
player.jumps=0;
}
/* SPAWN */
spawnTimer+=dt;
coinTimer+=dt;
powerTimer+=dt;
const spawnRate=
Math.max(
27,
70-speed*3.2
);
if(spawnTimer>spawnRate){
spawnTimer=0;
spawnObstacle();
}
if(coinTimer>34){
coinTimer=0;
spawnCoinPattern();
}
if(powerTimer>600){
powerTimer=0;
if(Math.random()<.7){
spawnPowerup();
}
}
/* OBJECTS */
updateObstacles(dt,slowFactor);
updateCoins(dt,slowFactor);
updatePowerups(dt,slowFactor);
updateParticles(dt);
/* POWERS */
for(const key in powerState){
if(powerState[key]>0){
powerState[key]-=dt;
}
if(powerState[key]<0){
powerState[key]=0;
}
}
updatePowerUI();
/* COMBO */
if(combo>0){
comboTimer-=dt;
if(comboTimer<=0){
combo=0;
updateCombo();
}
}
/* SHAKE */
shake*=.88;
updateHUD();
checkQuestProgress();
}
/* =====================================================
OBSTACLES
===================================================== */
function spawnObstacle(){
const types=[
"ice",
"rock",
"spike",
"crate"
];
let type=
types[
Math.floor(
Math.random()*types.length
)
];
let h=
type==="spike"
?45
:38+Math.random()*25;
obstacles.push({
x:W+50,
y:H*.80-h,
width:
type==="spike"
?42
:42+Math.random()*18,
height:h,
type,
passed:false
});
/* sometimes double obstacle */
if(
score>180 &&
Math.random()<.16
){
obstacles.push({
x:W+150,
y:H*.80-42,
width:42,
height:42,
type:"rock",
passed:false
});
}
}
function updateObstacles(dt,slow){
for(
let i=obstacles.length-1;
i>=0;
i--
){
const o=obstacles[i];
o.x-=speed*slow*dt;
if(!o.passed&&o.x+o.width<player.x){
o.passed=true;
combo++;
maxCombo=
Math.max(
maxCombo,
combo
);
comboTimer=110;
updateCombo();
score+=
1+
Math.min(
10,
combo*.15
);
if(combo%5===0){
burst(
player.x+20,
player.y,
"#facc15",
15
);
showToast(
"COMBO x"+combo+"! ๐ฅ"
);
}
}
if(collides(player,o)){
if(powerState.shield>0){
powerState.shield=0;
burst(
player.x+20,
player.y+20,
"#38bdf8",
30
);
shake=9;
obstacles.splice(i,1);
showToast(
"Schild gerettet! ๐ก๏ธ"
);
continue;
}
gameOver();
return;
}
if(o.x+o.width<-80){
obstacles.splice(i,1);
}
}
}
/* =====================================================
DRAW OBSTACLE
===================================================== */
function drawObstacle(o){
ctx.save();
if(o.type==="spike"){
ctx.fillStyle="#fb7185";
ctx.beginPath();
ctx.moveTo(
o.x,
o.y+o.height
);
ctx.lineTo(
o.x+o.width/2,
o.y
);
ctx.lineTo(
o.x+o.width,
o.y+o.height
);
ctx.closePath();
ctx.fill();
ctx.shadowColor="#fb7185";
ctx.shadowBlur=18;
ctx.fill();
}else{
const colors={
ice:"#67e8f9",
rock:"#64748b",
crate:"#d97706"
};
ctx.fillStyle=
colors[o.type]||"#64748b";
roundRect(
ctx,
o.x,
o.y,
o.width,
o.height,
8
);
ctx.fill();
if(o.type==="ice"){
ctx.strokeStyle=
"rgba(255,255,255,.5)";
ctx.lineWidth=2;
ctx.beginPath();
ctx.moveTo(
o.x+8,
o.y+8
);
ctx.lineTo(
o.x+o.width-8,
o.y+o.height-8
);
ctx.stroke();
}
}
ctx.restore();
}
/* =====================================================
COINS
===================================================== */
function spawnCoinPattern(){
const count=
3+
Math.floor(
Math.random()*4
);
const baseY=
H*.80-
80-
Math.random()*110;
for(let i=0;i<count;i++){
coins.push({
x:W+40+i*38,
y:baseY+
Math.sin(i*.8)*18,
size:24,
rotation:Math.random()*6
});
}
}
function updateCoins(dt,slow){
for(
let i=coins.length-1;
i>=0;
i--
){
const c=coins[i];
if(powerState.magnet>0){
const dx=
player.x+24-c.x;
const dy=
player.y+24-c.y;
const dist=
Math.hypot(dx,dy);
if(dist<220){
c.x+=
dx*.07*dt;
c.y+=
dy*.07*dt;
}
}
c.x-=speed*slow*dt;
c.rotation+=.08*dt;
if(collidesCircle(player,c)){
collectCoin(c);
coins.splice(i,1);
continue;
}
if(c.x<-50){
coins.splice(i,1);
}
}
}
function collectCoin(c){
const multiplier=
1+
Math.floor(
(save.coinUpgrade-1)*.5
);
runCoins+=multiplier;
combo=
Math.max(
combo+1,
1
);
maxCombo=
Math.max(
maxCombo,
combo
);
comboTimer=100;
burst(
c.x,
c.y,
"#facc15",
12
);
updateCombo();
}
/* =====================================================
POWERUPS
===================================================== */
function spawnPowerup(){
const types=[
"shield",
"magnet",
"slow"
];
const type=
types[
Math.floor(
Math.random()*types.length
)
];
powerups.push({
x:W+40,
y:H*.55+
Math.random()*100,
size:31,
type,
rotation:0
});
}
function updatePowerups(dt,slow){
for(
let i=powerups.length-1;
i>=0;
i--
){
const p=powerups[i];
p.x-=speed*slow*dt;
p.rotation+=.05*dt;
if(collidesCircle(player,p)){
powerState[p.type]=
p.type==="shield"
? 480
: 360;
burst(
p.x,
p.y,
p.type==="shield"
? "#38bdf8"
: p.type==="magnet"
? "#f472b6"
: "#a78bfa",
20
);
showToast(
p.type==="shield"
? "๐ก๏ธ Schild aktiviert!"
: p.type==="magnet"
? "๐งฒ Magnet aktiviert!"
: "โ๏ธ Zeitlupe aktiviert!"
);
powerups.splice(i,1);
continue;
}
if(p.x<-60){
powerups.splice(i,1);
}
}
}
/* =====================================================
PARTICLES
===================================================== */
function burst(x,y,color,count){
for(let i=0;i<count;i++){
particles.push({
x,
y,
vx:
(Math.random()-.5)*7,
vy:
(Math.random()-.5)*7,
life:1,
size:
2+
Math.random()*4,
color
});
}
}
function updateParticles(dt){
for(
let i=particles.length-1;
i>=0;
i--
){
const p=particles[i];
p.x+=p.vx*dt;
p.y+=p.vy*dt;
p.vy+=.12*dt;
p.life-=.035*dt;
if(p.life<=0){
particles.splice(i,1);
}
}
}
/* =====================================================
DRAW
===================================================== */
function draw(){
const t=getTheme();
ctx.save();
if(shake>0){
ctx.translate(
(Math.random()-.5)*shake,
(Math.random()-.5)*shake
);
}
/* SKY */
const gradient=
ctx.createLinearGradient(
0,0,0,H
);
gradient.addColorStop(0,t.sky1);
gradient.addColorStop(.65,t.sky2);
gradient.addColorStop(1,t.ground);
ctx.fillStyle=gradient;
ctx.fillRect(
0,0,W,H
);
drawMoon();
drawMountains();
drawSnow();
const ground=
H*.80;
/* GROUND */
ctx.fillStyle=t.ground;
ctx.fillRect(
0,
ground,
W,
H-ground
);
ctx.strokeStyle=
"rgba(103,232,249,.8)";
ctx.lineWidth=3;
ctx.beginPath();
ctx.moveTo(
0,
ground
);
ctx.lineTo(
W,
ground
);
ctx.stroke();
/* OBJECTS */
for(const o of obstacles){
drawObstacle(o);
}
for(const c of coins){
drawCoin(c);
}
for(const p of powerups){
drawPowerup(p);
}
drawPlayer();
drawParticles();
ctx.restore();
}
/* =====================================================
BACKGROUND DRAW
===================================================== */
function drawMoon(){
ctx.save();
ctx.fillStyle="rgba(186,230,253,.14)";
ctx.shadowColor="rgba(186,230,253,.3)";
ctx.shadowBlur=35;
ctx.beginPath();
ctx.arc(
W*.78,
H*.19,
Math.min(W,H)*.055,
0,
Math.PI*2
);
ctx.fill();
ctx.restore();
}
function drawMountains(){
ctx.save();
ctx.fillStyle="rgba(2,6,23,.25)";
for(const m of mountains){
ctx.beginPath();
ctx.moveTo(
m.x,
H*.80
);
ctx.lineTo(
m.x+m.width*.5,
H*.80-m.height
);
ctx.lineTo(
m.x+m.width,
H*.80
);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
function drawSnow(){
ctx.fillStyle="rgba(255,255,255,.55)";
for(const s of snow){
s.x+=s.drift*.15;
s.y+=s.speed;
if(s.y>H){
s.y=-5;
s.x=Math.random()*W;
}
if(s.x>W)s.x=0;
if(s.x<0)s.x=W;
ctx.beginPath();
ctx.arc(
s.x,
s.y,
s.size,
0,
Math.PI*2
);
ctx.fill();
}
}
/* =====================================================
PLAYER DRAW
===================================================== */
function drawPlayer(){
ctx.save();
ctx.translate(
player.x+24,
player.y+24
);
ctx.rotate(
player.rotation
);
if(powerState.shield>0){
ctx.beginPath();
ctx.arc(
0,
0,
37+
Math.sin(performance.now()*.008)*3,
0,
Math.PI*2
);
ctx.strokeStyle=
"rgba(56,189,248,.85)";
ctx.lineWidth=3;
ctx.shadowColor="#38bdf8";
ctx.shadowBlur=20;
ctx.stroke();
}
ctx.font="46px sans-serif";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillText(
save.skin,
0,
1
);
ctx.restore();
}
function drawCoin(c){
ctx.save();
ctx.translate(
c.x,
c.y
);
ctx.rotate(
Math.sin(c.rotation)
);
ctx.font=
`${c.size}px sans-serif`;
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.shadowColor="#facc15";
ctx.shadowBlur=14;
ctx.fillText(
"๐ช",
0,
0
);
ctx.restore();
}
function drawPowerup(p){
const icons={
shield:"๐ก๏ธ",
magnet:"๐งฒ",
slow:"โ๏ธ"
};
ctx.save();
ctx.translate(
p.x,
p.y
);
ctx.rotate(
Math.sin(p.rotation)*.12
);
ctx.font=
`${p.size}px sans-serif`;
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.shadowColor=
p.type==="shield"
? "#38bdf8"
: p.type==="magnet"
? "#f472b6"
: "#a78bfa";
ctx.shadowBlur=18;
ctx.fillText(
icons[p.type],
0,
0
);
ctx.restore();
}
function drawParticles(){
for(const p of particles){
ctx.save();
ctx.globalAlpha=
Math.max(0,p.life);
ctx.fillStyle=p.color;
ctx.beginPath();
ctx.arc(
p.x,
p.y,
p.size,
0,
Math.PI*2
);
ctx.fill();
ctx.restore();
}
}
/* =====================================================
COLLISION
===================================================== */
function collides(a,b){
const pad=9;
return (
a.x+pad <
b.x+b.width &&
a.x+a.width-pad >
b.x &&
a.y+pad <
b.y+b.height &&
a.y+a.height-pad >
b.y
);
}
function collidesCircle(a,b){
const ax=
a.x+a.width/2;
const ay=
a.y+a.height/2;
const dx=
ax-b.x;
const dy=
ay-b.y;
const r=
b.size*.75;
return (
dx*dx+
dy*dy
<
r*r+
22*22
);
}
/* =====================================================
HUD
===================================================== */
function updateHUD(){
document.getElementById("hudScore").textContent=
Math.floor(score);
document.getElementById("hudCoins").textContent=
runCoins;
document.getElementById("hudLevel").textContent=
level;
}
function updateCombo(){
const box=
document.getElementById("comboBox");
if(combo>=2){
document.getElementById(
"comboNumber"
).textContent=
"x"+combo;
box.classList.add("show");
box.classList.remove("pop");
void box.offsetWidth;
box.classList.add("pop");
}else{
box.classList.remove("show");
}
}
function updatePowerUI(){
const powers={
shield:
document.getElementById("shieldPower"),
magnet:
document.getElementById("magnetPower"),
slow:
document.getElementById("slowPower")
};
for(const key in powers){
powers[key].style.display=
powerState[key]>0
? "block"
: "none";
}
}
/* =====================================================
GAME OVER
===================================================== */
function gameOver(){
if(!gameRunning)return;
gameRunning=false;
cancelAnimationFrame(animationId);
const finalScore=
Math.floor(score);
const oldHigh=
save.highscore;
const earnedXP=
Math.max(
5,
Math.floor(
finalScore/4+
runCoins+
maxCombo*2
)
);
save.coins+=runCoins;
save.xp+=earnedXP;
while(save.xp>=levelXP(save.level)){
save.xp-=
levelXP(save.level);
save.level++;
save.coins+=
50+
save.level*10;
}
if(finalScore>save.highscore){
save.highscore=
finalScore;
document.getElementById(
"newRecord"
).style.display="block";
}else{
document.getElementById(
"newRecord"
).style.display="none";
}
persist();
document.getElementById(
"finalScore"
).textContent=
finalScore;
document.getElementById(
"finalCoins"
).textContent=
runCoins;
document.getElementById(
"finalCombo"
).textContent=
maxCombo;
document.getElementById(
"finalLevel"
).textContent=
level;
gameOverScreen.classList.remove("hidden");
updateMenu();
}
/* =====================================================
XP
===================================================== */
function levelXP(lvl){
return 100+
(lvl-1)*55;
}
/* =====================================================
SHOP
===================================================== */
let activeShopTab="skins";
function shopTab(tab){
activeShopTab=tab;
document
.getElementById("tabSkins")
.classList.toggle(
"active",
tab==="skins"
);
document
.getElementById("tabThemes")
.classList.toggle(
"active",
tab==="themes"
);
document
.getElementById("tabUpgrades")
.classList.toggle(
"active",
tab==="upgrades"
);
const grid=
document.getElementById(
"shopGrid"
);
grid.innerHTML="";
document.getElementById(
"shopCoins"
).textContent=save.coins;
if(tab==="skins"){
SKINS.forEach(s=>{
const owned=
save.ownedSkins.includes(s.id);
const active=
save.skin===s.id;
grid.innerHTML+=`
<div class="item">
<div class="item-icon">${s.id}</div>
<div class="item-name">${s.name}</div>
<div class="item-rarity">${s.rarity}</div>
<button onclick="buySkin('${s.id}',${s.cost})">
${
active
?"AKTIV"
:owned
?"BENUTZEN"
:s.cost+" ๐ช"
}
</button>
</div>
`;
});
}else if(tab==="themes"){
THEMES.forEach(t=>{
const owned=
save.ownedThemes.includes(t.id);
const active=
save.theme===t.id;
grid.innerHTML+=`
<div class="item">
<div class="item-icon">๐</div>
<div class="item-name">${t.name}</div>
<div class="item-rarity">
${t.cost===0?"FREE":"SPECIAL"}
</div>
<button onclick="buyTheme('${t.id}',${t.cost})">
${
active
?"AKTIV"
:owned
?"BENUTZEN"
:t.cost+" ๐ช"
}
</button>
</div>
`;
});
}else{
const upgrades=[
{
key:"coinUpgrade",
icon:"๐ช",
name:"Coin Multiplier",
max:6,
base:80
},
{
key:"jumpUpgrade",
icon:"๐ฆ",
name:"Jump Power",
max:6,
base:100
}
];
upgrades.forEach(u=>{
const lvl=save[u.key];
const cost=
lvl*u.base;
grid.innerHTML+=`
<div class="item">
<div class="item-icon">${u.icon}</div>
<div class="item-name">${u.name}</div>
<div class="item-rarity">
LEVEL ${lvl}/${u.max}
</div>
<button
onclick="buyUpgrade('${u.key}',${cost},${u.max})"
${lvl>=u.max?"disabled":""}>
${
lvl>=u.max
?"MAX"
:cost+" ๐ช"
}
</button>
</div>
`;
});
}
}
function buySkin(id,cost){
if(save.ownedSkins.includes(id)){
save.skin=id;
}else{
if(save.coins<cost){
showToast("Nicht genug Coins!");
return;
}
save.coins-=cost;
save.ownedSkins.push(id);
save.skin=id;
}
persist();
shopTab("skins");
updateMenu();
showToast("Skin ausgerรผstet! ๐ง");
}
function buyTheme(id,cost){
if(save.ownedThemes.includes(id)){
save.theme=id;
}else{
if(save.coins<cost){
showToast("Nicht genug Coins!");
return;
}
save.coins-=cost;
save.ownedThemes.push(id);
save.theme=id;
}
persist();
applyTheme();
shopTab("themes");
updateMenu();
showToast("Welt ausgerรผstet! ๐");
}
function buyUpgrade(key,cost,max){
if(save[key]>=max){
return;
}
if(save.coins<cost){
showToast("Nicht genug Coins!");
return;
}
save.coins-=cost;
save[key]++;
persist();
shopTab("upgrades");
updateMenu();
showToast("Upgrade verbessert! โก");
}
/* =====================================================
QUESTS
===================================================== */
function todayKey(){
const d=new Date();
return d.toISOString().slice(0,10);
}
function prepareQuests(){
if(save.questDay===todayKey()&&save.quests.length){
return;
}
save.questDay=todayKey();
const shuffled=
[...QUESTS]
.sort(
()=>Math.random()-.5
);
save.quests=
shuffled
.slice(0,4)
.map(q=>({
...q,
current:0,
claimed:false
}));
persist();
}
function openQuests(){
prepareQuests();
openPanel("questScreen");
renderQuests();
}
function questValue(q){
if(q.type==="score"){
return Math.floor(score);
}
if(q.type==="coins"){
return runCoins;
}
if(q.type==="combo"){
return maxCombo;
}
if(q.type==="jumps"){
return jumps;
}
return 0;
}
function checkQuestProgress(){
if(!save.quests.length)return;
for(const q of save.quests){
if(q.claimed)continue;
q.current=
Math.min(
q.goal,
questValue(q)
);
}
}
function renderQuests(){
const list=
document.getElementById(
"questList"
);
list.innerHTML="";
save.quests.forEach((q,i)=>{
const current=
Math.min(
q.goal,
q.current||0
);
const percent=
Math.min(
100,
current/q.goal*100
);
const ready=
current>=q.goal;
list.innerHTML+=`
<div class="quest ${ready&&!q.claimed?"ready":""}">
<div class="quest-top">
<div class="quest-title">
${q.text}
</div>
<div class="quest-reward">
+${q.reward} ๐ช
</div>
</div>
<div style="
margin-top:6px;
color:#94a3b8;
font-size:.65rem;
">
${current} / ${q.goal}
</div>
<div class="quest-bar">
<div
class="quest-progress"
style="width:${percent}%">
</div>
</div>
${
q.claimed
?`
<div style="
margin-top:8px;
color:#34d399;
font-size:.65rem;
font-weight:900;
">
โ ABGEHOLT
</div>
`
:ready
?`
<button
class="btn gold"
style="margin-top:8px;padding:9px"
onclick="claimQuest(${i})">
Belohnung abholen
</button>
`
:""
}
</div>
`;
});
}
function claimQuest(index){
const q=save.quests[index];
if(
!q||
q.claimed||
q.current<q.goal
){
return;
}
q.claimed=true;
save.coins+=q.reward;
persist();
renderQuests();
updateMenu();
showToast(
"+"+q.reward+" Coins! ๐"
);
}
/* =====================================================
DAILY
===================================================== */
function renderDaily(){
const grid=
document.getElementById(
"dailyGrid"
);
const today=todayKey();
const claimed=
save.lastDaily===today;
grid.innerHTML="";
DAILY_REWARDS.forEach((reward,i)=>{
const active=
!claimed &&
i===save.dailyDay;
const done=
i<save.dailyDay ||
(claimed&&i===save.dailyDay);
grid.innerHTML+=`
<div class="daily
${active?"active":""}
${done?"claimed":""}">
<strong>
Tag ${i+1}
</strong>
<br>
${
done
?"โ"
:reward+" ๐ช"
}
</div>
`;
});
const button=
document.getElementById(
"dailyButton"
);
button.disabled=claimed;
button.textContent=
claimed
?"Heute bereits abgeholt"
:"Belohnung abholen";
}
function claimDaily(){
const today=todayKey();
if(save.lastDaily===today){
return;
}
const reward=
DAILY_REWARDS[
save.dailyDay
];
save.coins+=reward;
save.lastDaily=today;
save.dailyDay=
(save.dailyDay+1)%
DAILY_REWARDS.length;
persist();
renderDaily();
updateMenu();
showToast(
"+"+reward+" Daily Coins! ๐"
);
}
function openDaily(){
openPanel("dailyScreen");
renderDaily();
}
/* =====================================================
UTILITY
===================================================== */
function roundRect(
context,
x,
y,
width,
height,
radius
){
context.beginPath();
context.roundRect(
x,
y,
width,
height,
radius
);
}
let toastTimer=0;
function showToast(message){
const toast=
document.getElementById("toast");
toast.textContent=message;
toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer=
setTimeout(
()=>{
toast.classList.remove("show");
},
1800
);
}
/* =====================================================
INITIALIZE
===================================================== */
resize();
prepareQuests();
updateMenu();
applyTheme();
</script>
</body>
</html>Game Source: Penguin Dash: Overdrive
Creator: WildGecko80
Libraries: none
Complexity: complex (3533 lines, 61.8 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-wildgecko80" to link back to the original. Then publish at arcadelab.ai/publish.