Penguin Dash: Overdrive
by PixelCoder166879 lines98.4 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(4,1fr);
gap:8px;
margin-bottom:16px;
}
.stat{
padding:12px 6px;
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.15rem;
}
.stat small{
color:var(--muted);
font-size:.57rem;
text-transform:uppercase;
letter-spacing:.7px;
}
.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);
}
.btn:disabled{
opacity:.45;
cursor:not-allowed;
transform:none;
}
.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;
}
.item button:disabled{
opacity:.45;
cursor:not-allowed;
}
/* =====================================================
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;
}
/* =====================================================
LOOT BOX
===================================================== */
.box-inventory{
position:relative;
text-align:center;
padding:4px 0 8px;
}
.box-inventory::before{
content:"";
position:absolute;
top:20px;
left:50%;
width:230px;
height:230px;
transform:translateX(-50%);
border-radius:50%;
background:
radial-gradient(
circle,
rgba(34,211,238,.20),
rgba(139,92,246,.08) 45%,
transparent 70%
);
filter:blur(8px);
pointer-events:none;
}
/* PREMIUM BOX */
.loot-box-big{
position:relative;
z-index:2;
width:190px;
height:170px;
margin:22px auto 16px;
display:flex;
align-items:center;
justify-content:center;
font-size:6.2rem;
border-radius:28px;
background:
linear-gradient(
145deg,
rgba(34,211,238,.18),
rgba(139,92,246,.12) 50%,
rgba(250,204,21,.12)
);
border:1px solid rgba(103,232,249,.45);
box-shadow:
0 0 20px rgba(34,211,238,.25),
0 0 60px rgba(139,92,246,.15),
inset 0 1px 0 rgba(255,255,255,.16),
inset 0 -20px 40px rgba(0,0,0,.25);
animation:
premiumBoxFloat 2.8s ease-in-out infinite;
transition:
transform .25s ease,
filter .25s ease;
}
.loot-box-big::before{
content:"";
position:absolute;
inset:-2px;
border-radius:30px;
padding:2px;
background:
linear-gradient(
120deg,
transparent,
rgba(34,211,238,.9),
rgba(139,92,246,.9),
transparent
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite:xor;
mask-composite:exclude;
animation:
boxBorderSpin 3s linear infinite;
}
.loot-box-big::after{
content:"✦ ✧ ✦";
position:absolute;
inset:-35px;
display:flex;
align-items:center;
justify-content:center;
color:#a5f3fc;
font-size:1.1rem;
letter-spacing:28px;
text-shadow:
0 0 10px #22d3ee,
0 0 25px #8b5cf6;
opacity:.7;
pointer-events:none;
animation:
boxSparkles 1.8s ease-in-out infinite;
}
.loot-box-big.opening{
animation:
boxOpening .75s ease-in-out infinite;
filter:
brightness(1.4)
drop-shadow(
0 0 30px
rgba(250,204,21,.8)
);
}
.loot-box-big.reward{
animation:
boxReward .7s ease;
}
/* Box-Anzahl */
.box-count{
position:relative;
z-index:3;
display:inline-flex;
align-items:center;
gap:8px;
padding:8px 17px;
border-radius:999px;
color:#fff;
font-size:.78rem;
font-weight:1000;
text-transform:uppercase;
letter-spacing:1px;
background:rgba(2,6,23,.72);
border:1px solid rgba(255,255,255,.1);
box-shadow:
0 8px 25px rgba(0,0,0,.3),
inset 0 1px rgba(255,255,255,.06);
backdrop-filter:blur(12px);
}
.box-count span{
color:#facc15;
font-size:1.4rem;
text-shadow:
0 0 12px rgba(250,204,21,.6);
}
/* Box Beschreibung */
.box-description{
position:relative;
z-index:2;
max-width:380px;
margin:15px auto 20px;
color:#94a3b8;
font-size:.76rem;
line-height:1.6;
}
/* mögliche Belohnungen */
.loot-chances{
position:relative;
z-index:2;
display:grid;
grid-template-columns:repeat(3,1fr);
gap:8px;
margin:18px 0;
}
.loot-chances div{
min-height:80px;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
padding:10px 5px;
border-radius:16px;
background:
linear-gradient(
145deg,
rgba(255,255,255,.07),
rgba(255,255,255,.025)
);
border:1px solid rgba(148,163,184,.14);
color:#94a3b8;
font-size:.57rem;
font-weight:900;
text-transform:uppercase;
letter-spacing:.7px;
transition:.2s ease;
}
.loot-chances div:hover{
transform:translateY(-3px);
border-color:
rgba(34,211,238,.35);
background:
rgba(34,211,238,.07);
box-shadow:
0 8px 25px rgba(34,211,238,.08);
}
.loot-chances b{
display:block;
margin-bottom:6px;
font-size:1.7rem;
filter:
drop-shadow(
0 0 8px
rgba(255,255,255,.2)
);
}
/* Öffnen Button */
#openBoxButton{
position:relative;
z-index:3;
overflow:hidden;
font-size:.85rem;
box-shadow:
0 10px 30px rgba(234,179,8,.18),
0 0 25px rgba(250,204,21,.08);
}
#openBoxButton::before{
content:"";
position:absolute;
top:0;
left:-100%;
width:60%;
height:100%;
background:
linear-gradient(
90deg,
transparent,
rgba(255,255,255,.35),
transparent
);
transform:skewX(-20deg);
animation:
buttonShine 2.4s infinite;
}
/* Reward Ergebnis */
.box-result{
position:relative;
z-index:5;
display:none;
margin-top:18px;
padding:18px;
border-radius:20px;
text-align:center;
background:
linear-gradient(
145deg,
rgba(255,255,255,.08),
rgba(255,255,255,.025)
);
border:1px solid rgba(250,204,21,.28);
box-shadow:
0 0 30px rgba(250,204,21,.08);
animation:
rewardAppear .35s ease;
}
.box-result.show{
display:block;
}
.box-result-icon{
font-size:3.5rem;
margin-bottom:5px;
filter:
drop-shadow(
0 0 18px
rgba(255,255,255,.35)
);
}
.box-result-rarity{
font-size:.62rem;
font-weight:1000;
letter-spacing:3px;
text-transform:uppercase;
color:#facc15;
}
.box-result-name{
margin-top:4px;
font-size:1.25rem;
font-weight:1000;
}
.box-result-description{
margin-top:5px;
color:#94a3b8;
font-size:.7rem;
}
/* =====================================================
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;
}
}
@keyframes premiumBoxFloat{
0%,100%{
transform:
translateY(0)
rotate(-1deg);
}
50%{
transform:
translateY(-9px)
rotate(1deg);
}
}
@keyframes boxBorderSpin{
from{
transform:rotate(0deg);
}
to{
transform:rotate(360deg);
}
}
@keyframes boxSparkles{
0%,100%{
opacity:.3;
transform:scale(.85);
}
50%{
opacity:1;
transform:scale(1.08);
}
}
@keyframes boxOpening{
0%{
transform:
translateY(0)
rotate(0)
scale(1);
}
20%{
transform:
translateY(-5px)
rotate(-4deg)
scale(1.03);
}
40%{
transform:
translateY(3px)
rotate(4deg)
scale(1.06);
}
60%{
transform:
translateY(-7px)
rotate(-3deg)
scale(1.1);
}
80%{
transform:
translateY(2px)
rotate(3deg)
scale(1.06);
}
100%{
transform:
translateY(0)
rotate(0)
scale(1);
}
}
@keyframes boxReward{
0%{
transform:scale(1);
filter:brightness(1);
}
40%{
transform:scale(1.18);
filter:brightness(1.8);
}
70%{
transform:scale(.94);
}
100%{
transform:scale(1);
filter:brightness(1);
}
}
@keyframes rewardAppear{
0%{
opacity:0;
transform:
translateY(15px)
scale(.85);
}
100%{
opacity:1;
transform:
translateY(0)
scale(1);
}
}
@keyframes buttonShine{
0%{
left:-100%;
}
45%,100%{
left:150%;
}
}
.pop{
animation:pop .25s ease;
}
/* =====================================================
MOBILE
===================================================== */
@media(max-width:500px){
.panel{
padding:19px;
border-radius:24px;
}
.menu-grid{
grid-template-columns:1fr;
}
.stats{
grid-template-columns:repeat(4,1fr);
gap:5px;
}
.stat{
padding:10px 3px;
}
.stat b{
font-size:1rem;
}
.stat small{
font-size:.48rem;
letter-spacing:.3px;
}
.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;
}
.loot-box-big{
width:165px;
height:150px;
font-size:5.3rem;
}
.loot-chances{
gap:5px;
}
.loot-chances div{
min-height:70px;
font-size:.52rem;
}
.loot-chances b{
font-size:1.45rem;
}
}
</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 class="stat">
<b id="menuBoxes">0</b>
<small>Boxen</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="openPanel('boxScreen')">
📦 Boxen
</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>
<!-- =====================================================
LOOT BOX SCREEN
===================================================== -->
<div id="boxScreen" class="screen hidden">
<div class="panel">
<div class="section-title">
<h2>📦 Lootboxen</h2>
<button
class="close"
onclick="closePanel('boxScreen')">
×
</button>
</div>
<div class="box-inventory">
<div
id="lootBoxVisual"
class="loot-box-big">
🎁
</div>
<div class="box-count">
📦
<span id="boxCount">0</span>
Boxen
</div>
<div class="box-description">
Öffne deine Box und erhalte eine
zufällige Belohnung. Je seltener
der Fund, desto wertvoller ist er!
</div>
<div class="loot-chances">
<div>
<b>🪙</b>
Coins
</div>
<div>
<b>🐧</b>
Skins
</div>
<div>
<b>⭐</b>
Selten
</div>
</div>
<button
id="openBoxButton"
class="btn gold"
onclick="openLootBox()">
📦 Box öffnen
</button>
<div
id="boxResult"
class="box-result">
<div
id="boxResultIcon"
class="box-result-icon">
🎁
</div>
<div
id="boxResultRarity"
class="box-result-rarity">
COMMON
</div>
<div
id="boxResultName"
class="box-result-name">
Belohnung
</div>
<div
id="boxResultDescription"
class="box-result-description">
Deine Belohnung
</div>
</div>
</div>
</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>
<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
];
/* =====================================================
LOOT BOX DATA
===================================================== */
const LOOT_BOXES=[
{
id:"basic",
name:"Mystery Box",
icon:"🎁",
rewards:[
{
type:"coins",
min:25,
max:60,
weight:50,
rarity:"Common"
},
{
type:"coins",
min:70,
max:120,
weight:25,
rarity:"Rare"
},
{
type:"skin",
id:"🕶️",
weight:10,
rarity:"Rare"
},
{
type:"skin",
id:"🥷",
weight:7,
rarity:"Rare"
},
{
type:"coins",
min:150,
max:250,
weight:5,
rarity:"Epic"
},
{
type:"skin",
id:"👑",
weight:2,
rarity:"Epic"
},
{
type:"skin",
id:"🚀",
weight:1,
rarity:"Epic"
}
]
}
];
/* =====================================================
SAVE SYSTEM
===================================================== */
const SAVE_KEY="penguinDashSave";
const DEFAULT_SAVE={
version:3,
coins:0,
boxes:0,
highscore:0,
level:1,
xp:0,
skin:"🐧",
ownedSkins:[
"🐧"
],
theme:"arctic",
ownedThemes:[
"arctic"
],
coinUpgrade:1,
jumpUpgrade:1,
dailyDay:0,
lastDaily:"",
questDay:"",
quests:[]
};
function cloneDefaultSave(){
return JSON.parse(
JSON.stringify(
DEFAULT_SAVE
)
);
}
function loadSave(){
try{
const raw=
localStorage.getItem(
SAVE_KEY
);
if(!raw){
return cloneDefaultSave();
}
const parsed=
JSON.parse(raw);
if(
!parsed ||
typeof parsed!=="object"
){
return cloneDefaultSave();
}
const result={
...cloneDefaultSave(),
...parsed
};
if(
!Array.isArray(
result.ownedSkins
)
){
result.ownedSkins=[
"🐧"
];
}
if(
!result.ownedSkins.includes(
"🐧"
)
){
result.ownedSkins.unshift(
"🐧"
);
}
if(
!Array.isArray(
result.ownedThemes
)
){
result.ownedThemes=[
"arctic"
];
}
if(
!result.ownedThemes.includes(
"arctic"
)
){
result.ownedThemes.unshift(
"arctic"
);
}
if(
!Array.isArray(
result.quests
)
){
result.quests=[];
}
if(
!Number.isFinite(
result.coins
) ||
result.coins<0
){
result.coins=0;
}
if(
!Number.isFinite(
result.boxes
) ||
result.boxes<0
){
result.boxes=0;
}
if(
!Number.isFinite(
result.highscore
) ||
result.highscore<0
){
result.highscore=0;
}
if(
!Number.isFinite(
result.level
) ||
result.level<1
){
result.level=1;
}
if(
!Number.isFinite(
result.xp
) ||
result.xp<0
){
result.xp=0;
}
if(
!Number.isFinite(
result.coinUpgrade
) ||
result.coinUpgrade<1
){
result.coinUpgrade=1;
}
if(
!Number.isFinite(
result.jumpUpgrade
) ||
result.jumpUpgrade<1
){
result.jumpUpgrade=1;
}
if(
!Number.isFinite(
result.dailyDay
) ||
result.dailyDay<0
){
result.dailyDay=0;
}
if(
!result.skin ||
!result.ownedSkins.includes(
result.skin
)
){
result.skin="🐧";
}
if(
!result.theme ||
!result.ownedThemes.includes(
result.theme
)
){
result.theme="arctic";
}
result.version=3;
return result;
}catch(error){
console.error(
"Fehler beim Laden des Spielstands:",
error
);
return cloneDefaultSave();
}
}
let save=loadSave();
function persist(){
try{
localStorage.setItem(
SAVE_KEY,
JSON.stringify(save)
);
return true;
}catch(error){
console.error(
"Speichern fehlgeschlagen:",
error
);
showToast(
"⚠️ Speichern fehlgeschlagen!"
);
return false;
}
}
persist();
/* =====================================================
AUTO SAVE
===================================================== */
let lastAutoSave=
performance.now();
const AUTO_SAVE_INTERVAL=2000;
function autoSave(){
const now=
performance.now();
if(
now-lastAutoSave>=
AUTO_SAVE_INTERVAL
){
persist();
lastAutoSave=now;
}
}
function saveBeforeLeaving(){
if(
gameRunning &&
runCoins>0
){
save.coins+=
runCoins;
runCoins=0;
}
persist();
}
window.addEventListener(
"beforeunload",
saveBeforeLeaving
);
document.addEventListener(
"visibilitychange",
()=>{
if(
document.visibilityState===
"hidden"
){
saveBeforeLeaving();
}
}
);
/* =====================================================
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
===================================================== */
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;
document.getElementById(
"menuBoxes"
).textContent=
save.boxes;
}
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();
}
if(
id==="boxScreen"
){
renderBoxes();
}
}
function closePanel(id){
document
.getElementById(id)
.classList.add(
"hidden"
);
}
function goMenu(){
saveBeforeLeaving();
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(){
if(
runCoins>0
){
save.coins+=
runCoins;
runCoins=0;
persist();
}
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;
lastTime=
performance.now();
lastAutoSave=
performance.now();
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
);
persist();
if(!paused){
lastTime=
performance.now();
animationId=
requestAnimationFrame(
gameLoop
);
}
}
/* =====================================================
QUIT
===================================================== */
function quitGame(){
saveBeforeLeaving();
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();
autoSave();
animationId=
requestAnimationFrame(
gameLoop
);
}
/* =====================================================
UPDATE
===================================================== */
function update(dt){
const slowFactor=
powerState.slow>0
? .58
: 1;
score+=
.11*
dt*
speed*
slowFactor;
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.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;
}
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();
}
}
updateObstacles(
dt,
slowFactor
);
updateCoins(
dt,
slowFactor
);
updatePowerups(
dt,
slowFactor
);
updateParticles(dt);
for(
const key in powerState
){
if(
powerState[key]>0
){
powerState[key]-=dt;
}
if(
powerState[key]<0
){
powerState[key]=0;
}
}
updatePowerUI();
if(
combo>0
){
comboTimer-=dt;
if(
comboTimer<=0
){
combo=0;
updateCombo();
}
}
shake*=.88;
updateHUD();
checkQuestProgress();
if(
save.quests.length &&
Math.floor(
nowSafe()
)%10===0
){
persist();
}
}
function nowSafe(){
return performance.now()/1000;
}
/* =====================================================
OBSTACLES
===================================================== */
function spawnObstacle(){
const types=[
"ice",
"rock",
"spike",
"crate"
];
const type=
types[
Math.floor(
Math.random()*
types.length
)
];
const 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
});
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
);
}
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;
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();
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 earnedXP=
Math.max(
5,
Math.floor(
finalScore/4+
runCoins+
maxCombo*2
)
);
save.coins+=
runCoins;
runCoins=0;
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";
}
checkQuestProgress();
persist();
document.getElementById(
"finalScore"
).textContent=
finalScore;
document.getElementById(
"finalCoins"
).textContent=
Math.floor(
save.coins
);
document.getElementById(
"finalCombo"
).textContent=
maxCombo;
document.getElementById(
"finalLevel"
).textContent=
save.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();
const year=
d.getFullYear();
const month=
String(
d.getMonth()+1
).padStart(
2,
"0"
);
const day=
String(
d.getDate()
).padStart(
2,
"0"
);
return (
year+
"-"+
month+
"-"+
day
);
}
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;
}
let changed=false;
for(
const q of save.quests
){
if(
q.claimed
){
continue;
}
const next=
Math.min(
q.goal,
questValue(q)
);
if(
q.current!==next
){
q.current=next;
changed=true;
}
}
if(
changed
){
persist();
}
}
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} 🪙
<br>
+1 📦
</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>
`;
}
);
}
/* =====================================================
QUEST REWARD
===================================================== */
function claimQuest(index){
const q=
save.quests[index];
if(
!q||
q.claimed||
q.current<q.goal
){
return;
}
q.claimed=true;
save.coins+=
q.reward;
/* NEU:
Jede abgeschlossene Quest
gibt zusätzlich eine Box.
*/
save.boxes+=1;
persist();
renderQuests();
updateMenu();
showToast(
`+${q.reward} Coins + 1 Box! 🏆📦`
);
}
/* =====================================================
LOOT BOX SYSTEM
===================================================== */
function rollBoxReward(){
const box=
LOOT_BOXES[0];
const totalWeight=
box.rewards.reduce(
(
sum,
reward
)=>
sum+
reward.weight,
0
);
let roll=
Math.random()*
totalWeight;
for(
const reward of box.rewards
){
roll-=
reward.weight;
if(
roll<=0
){
return reward;
}
}
return box.rewards[0];
}
function renderBoxes(){
const count=
document.getElementById(
"boxCount"
);
const button=
document.getElementById(
"openBoxButton"
);
if(count){
count.textContent=
save.boxes;
}
if(button){
button.disabled=
save.boxes<=0;
button.textContent=
save.boxes>0
?"📦 Box öffnen"
:"Keine Boxen vorhanden";
}
}
function openLootBox(){
if(
save.boxes<=0
){
showToast(
"Keine Boxen vorhanden! 📦"
);
return;
}
const boxVisual=
document.getElementById(
"lootBoxVisual"
);
const resultBox=
document.getElementById(
"boxResult"
);
const button=
document.getElementById(
"openBoxButton"
);
resultBox.classList.remove(
"show"
);
save.boxes--;
button.disabled=true;
boxVisual.classList.remove(
"reward"
);
boxVisual.classList.add(
"opening"
);
/* Kleine Spannungspause */
setTimeout(
()=>{
const reward=
rollBoxReward();
let icon="🎁";
let name="";
let description="";
let rarity=
reward.rarity||
"Common";
if(
reward.type==="coins"
){
const amount=
Math.floor(
reward.min+
Math.random()*
(
reward.max-
reward.min+
1
)
);
save.coins+=
amount;
icon="🪙";
name=
`+${amount} Coins`;
description=
"Eine Coin-Belohnung aus der Mystery Box.";
}
if(
reward.type==="skin"
){
const skin=
SKINS.find(
s=>
s.id===
reward.id
);
if(
skin &&
!save.ownedSkins.includes(
skin.id
)
){
save.ownedSkins.push(
skin.id
);
icon=
skin.id;
name=
skin.name;
description=
"Neuer Skin freigeschaltet!";
}else{
const bonus=100;
save.coins+=
bonus;
icon="🔁";
name=
`Doppelter Skin`;
description=
`Nicht mehr benötigter Skin → +${bonus} Coins`;
rarity="Rare";
}
}
persist();
updateMenu();
renderBoxes();
document.getElementById(
"boxResultIcon"
).textContent=
icon;
document.getElementById(
"boxResultRarity"
).textContent=
rarity;
document.getElementById(
"boxResultName"
).textContent=
name;
document.getElementById(
"boxResultDescription"
).textContent=
description;
boxVisual.classList.remove(
"opening"
);
boxVisual.classList.add(
"reward"
);
resultBox.classList.add(
"show"
);
showToast(
`📦 ${name}!`
);
burstBoxReward();
},
900
);
}
function burstBoxReward(){
const box=
document.getElementById(
"lootBoxVisual"
);
const rect=
box.getBoundingClientRect();
const colors=[
"#22d3ee",
"#8b5cf6",
"#facc15",
"#f472b6"
];
for(
let i=0;
i<28;
i++
){
const particle=
document.createElement(
"span"
);
particle.textContent=
i%3===0
?"✦"
:"•";
particle.style.position=
"fixed";
particle.style.left=
(
rect.left+
rect.width/2
)+"px";
particle.style.top=
(
rect.top+
rect.height/2
)+"px";
particle.style.zIndex=
"9999";
particle.style.pointerEvents=
"none";
particle.style.color=
colors[
Math.floor(
Math.random()*
colors.length
)
];
particle.style.fontSize=
(
12+
Math.random()*14
)+"px";
particle.style.transition=
"all .7s cubic-bezier(.2,.8,.2,1)";
document.body.appendChild(
particle
);
const angle=
Math.random()*
Math.PI*
2;
const distance=
60+
Math.random()*110;
requestAnimationFrame(
()=>{
particle.style.transform=
`translate(
${Math.cos(angle)*distance}px,
${Math.sin(angle)*distance}px
)
scale(.2)`;
particle.style.opacity=
"0";
}
);
setTimeout(
()=>{
particle.remove();
},
800
);
}
}
/* =====================================================
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: PixelCoder16
Libraries: none
Complexity: complex (6879 lines, 98.4 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-pixelcoder16" to link back to the original. Then publish at arcadelab.ai/publish.