🎮ArcadeLab

Penguin Dash: Overdrive

by PixelCoder16
6879 lines98.4 KB
▶ Play
<!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.