🎮ArcadeLab

Bounce 100

by NovaWizard65
1174 lines41.8 KB
▶ Play
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Bounce 100</title>
    <!-- ========== POKI SDK ========== -->
    <script src="//game-cdn.poki.com/scripts/v2/poki-sdk.js"></script>
    <script>
        // ========== INITIALISATION POKI SDK ==========
        let pokiReady = false;

        function initPokiSDK() {
            if (typeof PokiSDK !== 'undefined') {
                PokiSDK.init().then(() => {
                    console.log("✅ Poki SDK initialized successfully");
                    pokiReady = true;
                    // Démarrage du jeu après initialisation
                    startGame();
                }).catch(() => {
                    console.log("⚠️ Poki SDK init failed (adblock?)");
                    pokiReady = false;
                    // Lancement du jeu même sans SDK
                    startGame();
                });
            } else {
                console.log("⚠️ Poki SDK not loaded");
                // Lancement du jeu sans SDK (pour test local)
                startGame();
            }
        }

        // Attendre le chargement de la page
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initPokiSDK);
        } else {
            initPokiSDK();
        }
    </script>
    <style>
        /* ========== STYLES ========== */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }
        html, body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: #0f172a;
            font-family: 'Segoe UI', Arial, sans-serif;
        }
        #gameWrapper {
            position: relative;
            width: 100%;
            height: 100%;
            max-width: 900px;
            max-height: 550px;
            margin: 0 auto;
            background: #000;
            border: 3px solid #ffd700;
            border-radius: 16px;
            overflow: hidden;
            box-shadow: 0 20px 40px rgba(0,0,0,0.8);
            aspect-ratio: 900 / 550;
        }
        #gameWrapper canvas {
            display: block;
            width: 100%;
            height: 100%;
            image-rendering: pixelated;
        }
        /* Overlays */
        .overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: none;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background: rgba(0,0,0,0.88);
            backdrop-filter: blur(4px);
            z-index: 20;
            padding: 20px;
            text-align: center;
            border-radius: 16px;
        }
        .overlay.active {
            display: flex;
        }
        .overlay h1 {
            font-size: clamp(2rem, 6vw, 3.2rem);
            font-weight: 900;
            color: #ffd700;
            text-shadow: 0 0 30px #ffaa00, 0 0 60px #ff6600;
            letter-spacing: 4px;
            margin-bottom: 8px;
        }
        .overlay p {
            color: #ccc;
            max-width: 400px;
            font-size: clamp(0.85rem, 2vw, 1rem);
            margin: 6px 0;
        }
        .overlay .keys {
            color: #aaa;
            font-size: 0.8rem;
            background: rgba(255,255,255,0.06);
            padding: 6px 16px;
            border-radius: 40px;
            border: 1px solid rgba(255,255,255,0.08);
            margin: 6px 0;
        }
        .overlay .keys kbd {
            background: #1a1a2e;
            color: #ffd700;
            padding: 2px 10px;
            border-radius: 4px;
            border: 1px solid #444;
            font-family: monospace;
            font-size: 0.8rem;
        }
        .btn {
            background: linear-gradient(135deg, #ff416c, #ff4b2b);
            border: none;
            padding: 12px 32px;
            border-radius: 40px;
            font-weight: bold;
            font-size: clamp(0.9rem, 2vw, 1.1rem);
            color: #fff;
            cursor: pointer;
            margin: 6px;
            transition: transform 0.15s;
            box-shadow: 0 4px 20px rgba(255,65,108,0.4);
            touch-action: manipulation;
            min-height: 48px;
            min-width: 120px;
        }
        .btn:hover {
            transform: scale(1.04);
        }
        .btn:active {
            transform: scale(0.96);
        }
        .btn.secondary {
            background: rgba(255,255,255,0.12);
            border: 1px solid rgba(255,255,255,0.2);
            box-shadow: none;
        }
        .btn.gold {
            background: linear-gradient(135deg, #f7971e, #ffd200);
            color: #1a1a2e;
            box-shadow: 0 4px 20px rgba(255,215,0,0.4);
        }
        /* Grille des niveaux */
        .grid {
            display: grid;
            grid-template-columns: repeat(10, 1fr);
            gap: 4px;
            max-width: 380px;
            max-height: 140px;
            overflow-y: auto;
            margin: 6px 0;
            width: 100%;
            padding: 4px;
        }
        .grid button {
            background: rgba(255,215,0,0.12);
            border: 1px solid rgba(255,215,0,0.2);
            color: #ffd700;
            padding: 4px 0;
            border-radius: 6px;
            font-size: 0.65rem;
            cursor: pointer;
            transition: 0.1s;
            touch-action: manipulation;
        }
        .grid button:hover:not(.locked) {
            background: rgba(255,215,0,0.3);
        }
        .grid button.locked {
            opacity: 0.35;
            cursor: not-allowed;
            border-color: #444;
            color: #666;
        }
        .grid button.completed {
            border-color: #4caf50;
            color: #4caf50;
        }
        .grid button.current {
            border-color: #ff9800;
            color: #ff9800;
            background: rgba(255,152,0,0.2);
        }
        /* Header en jeu */
        .header {
            position: absolute;
            top: 8px;
            left: 8px;
            right: 8px;
            display: flex;
            justify-content: space-between;
            z-index: 10;
            pointer-events: none;
            gap: 6px;
            flex-wrap: wrap;
        }
        .header .stats {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
            pointer-events: none;
        }
        .header .stats span {
            background: rgba(0,0,0,0.75);
            padding: 3px 10px;
            border-radius: 20px;
            font-size: clamp(0.6rem, 1.2vw, 0.75rem);
            font-weight: bold;
            color: #ffd700;
            pointer-events: none;
            white-space: nowrap;
        }
        .header .menu-btn {
            background: rgba(0,0,0,0.75);
            border: 1px solid #ffd700;
            color: #ffd700;
            padding: 3px 12px;
            border-radius: 20px;
            font-size: clamp(0.6rem, 1.2vw, 0.75rem);
            cursor: pointer;
            pointer-events: auto;
            transition: 0.15s;
            touch-action: manipulation;
            min-height: 28px;
        }
        .header .menu-btn:hover {
            background: #ffd700;
            color: #000;
        }
        .header .menu-btn:active {
            transform: scale(0.95);
        }
        /* Titre en jeu */
        .title-in-game {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255,215,0,0.10);
            font-size: clamp(1rem, 3vw, 1.8rem);
            font-weight: 900;
            letter-spacing: 6px;
            pointer-events: none;
            z-index: 5;
            text-transform: uppercase;
            white-space: nowrap;
        }
        /* Scrollbar personnalisée pour la grille */
        .grid::-webkit-scrollbar {
            width: 4px;
        }
        .grid::-webkit-scrollbar-track {
            background: rgba(255,255,255,0.05);
            border-radius: 10px;
        }
        .grid::-webkit-scrollbar-thumb {
            background: #ffd700;
            border-radius: 10px;
        }
        /* Responsive */
        @media (max-width: 600px) {
            #gameWrapper {
                border-radius: 0;
                border-width: 2px;
                max-width: 100%;
                max-height: 100%;
                aspect-ratio: 900/550;
            }
            .header .stats span {
                font-size: 0.55rem;
                padding: 2px 6px;
            }
            .header .menu-btn {
                font-size: 0.55rem;
                padding: 2px 8px;
                min-height: 24px;
            }
            .btn {
                padding: 10px 20px;
                font-size: 0.85rem;
                min-height: 40px;
                min-width: 80px;
            }
            .grid {
                max-width: 100%;
                gap: 3px;
            }
            .grid button {
                font-size: 0.55rem;
                padding: 3px 0;
            }
            .overlay h1 {
                font-size: 1.8rem;
            }
        }
        @media (max-width: 400px) {
            .header .stats span {
                font-size: 0.45rem;
                padding: 1px 4px;
            }
            .header .menu-btn {
                font-size: 0.45rem;
                padding: 1px 6px;
                min-height: 20px;
            }
            .btn {
                padding: 8px 14px;
                font-size: 0.75rem;
                min-height: 34px;
                min-width: 60px;
            }
            .overlay h1 {
                font-size: 1.4rem;
            }
            .grid button {
                font-size: 0.45rem;
                padding: 2px 0;
            }
        }
    </style>
</head>
<body>
<div id="gameWrapper">
    <canvas id="gameCanvas" width="900" height="550"></canvas>

    <!-- ========== HEADER ========== -->
    <div class="header">
        <div class="stats">
            <span>❤️ <span id="livesVal">3</span></span>
            <span>⭐ <span id="levelVal">1</span>/100</span>
            <span>🏆 <span id="scoreVal">0</span></span>
            <span>💎 <span id="starsVal">0</span></span>
        </div>
        <button class="menu-btn" id="inGameMenuBtn">🏠 Menu</button>
    </div>
    <div class="title-in-game">BOUNCE 100</div>

    <!-- ========== MENU PRINCIPAL ========== -->
    <div id="mainMenu" class="overlay active">
        <h1>⚡ BOUNCE 100 ⚡</h1>
        <p>100 niveaux de plateformes addictifs.</p>
        <p style="font-size:0.9rem;color:#aaa;">Collecte les étoiles, évite les ennemis, atteins le drapeau.</p>
        <div class="keys">
            <kbd>←</kbd> <kbd>→</kbd> ou <kbd>Q</kbd> <kbd>D</kbd> &nbsp;·&nbsp; <kbd>↑</kbd> <kbd>Espace</kbd> <kbd>W</kbd> sauter
        </div>
        <button class="btn" id="playBtn">🎮 Jouer</button>
        <button class="btn secondary" id="levelsBtn">📋 Niveaux</button>
        <div id="levelGridContainer" class="hidden" style="width:100%;"></div>
    </div>

    <!-- ========== GAME OVER ========== -->
    <div id="gameOverOverlay" class="overlay">
        <h1 style="color:#ff6b6b;">💀 GAME OVER</h1>
        <p>Score : <span id="finalScoreSpan">0</span></p>
        <button class="btn" id="retryBtn">🔄 Réessayer</button>
        <button class="btn secondary" id="menuBtnRetry">🏠 Menu</button>
    </div>

    <!-- ========== NIVEAU TERMINÉ ========== -->
    <div id="levelCompleteOverlay" class="overlay">
        <h1>🎉 Niveau terminé !</h1>
        <p id="completeMsg"></p>
        <button class="btn" id="nextLevelBtn">⏩ Suivant</button>
        <button class="btn secondary" id="menuBtnComplete">🏠 Menu</button>
    </div>

    <!-- ========== VICTOIRE FINALE ========== -->
    <div id="finalWinOverlay" class="overlay">
        <h1>🏆 LÉGENDE !</h1>
        <p>Vous avez terminé les 100 niveaux !</p>
        <p>Score final : <span id="finalWinScore">0</span></p>
        <button class="btn" id="winMenuBtn">🏠 Menu</button>
    </div>
</div>

<script>
    (function(){
        "use strict";

        // ========== POKI SDK FONCTIONS ==========
        function pokiGameplayStart() {
            try {
                if (window.PokiSDK && typeof window.PokiSDK.gameplayStart === 'function') {
                    window.PokiSDK.gameplayStart();
                    console.log("✅ Poki: gameplayStart");
                }
            } catch(e) {}
        }

        function pokiGameplayStop() {
            try {
                if (window.PokiSDK && typeof window.PokiSDK.gameplayStop === 'function') {
                    window.PokiSDK.gameplayStop();
                    console.log("✅ Poki: gameplayStop");
                }
            } catch(e) {}
        }

        function pokiCommercialBreak(callback) {
            try {
                if (window.PokiSDK && typeof window.PokiSDK.commercialBreak === 'function') {
                    window.PokiSDK.commercialBreak().then(() => {
                        if (callback) callback();
                    }).catch(() => {
                        if (callback) callback();
                    });
                } else {
                    if (callback) callback();
                }
            } catch(e) {
                if (callback) callback();
            }
        }

        function pokiRewardedBreak(callback) {
            try {
                if (window.PokiSDK && typeof window.PokiSDK.rewardedBreak === 'function') {
                    window.PokiSDK.rewardedBreak().then((success) => {
                        if (callback) callback(success);
                    }).catch(() => {
                        if (callback) callback(false);
                    });
                } else {
                    if (callback) callback(false);
                }
            } catch(e) {
                if (callback) callback(false);
            }
        }

        // ========== ÉLÉMENTS DOM ==========
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const livesSpan = document.getElementById('livesVal');
        const levelSpan = document.getElementById('levelVal');
        const scoreSpan = document.getElementById('scoreVal');
        const starsSpan = document.getElementById('starsVal');
        const finalScoreSpan = document.getElementById('finalScoreSpan');
        const finalWinScoreSpan = document.getElementById('finalWinScore');
        const completeMsgSpan = document.getElementById('completeMsg');

        const mainMenu = document.getElementById('mainMenu');
        const gameOverOverlay = document.getElementById('gameOverOverlay');
        const levelCompleteOverlay = document.getElementById('levelCompleteOverlay');
        const finalWinOverlay = document.getElementById('finalWinOverlay');
        const levelGridContainer = document.getElementById('levelGridContainer');

        const playBtn = document.getElementById('playBtn');
        const levelsBtn = document.getElementById('levelsBtn');
        const retryBtn = document.getElementById('retryBtn');
        const menuBtnRetry = document.getElementById('menuBtnRetry');
        const nextLevelBtn = document.getElementById('nextLevelBtn');
        const menuBtnComplete = document.getElementById('menuBtnComplete');
        const winMenuBtn = document.getElementById('winMenuBtn');
        const inGameMenuBtn = document.getElementById('inGameMenuBtn');

        // ========== CONSTANTES ==========
        const W = 900, H = 550;
        const GRAVITY = 0.45;
        const JUMP = -7.6;
        const GROUND = H - 55;

        // ========== ÉTAT JEU ==========
        let level = 1;
        let maxUnlocked = 1;
        let lives = 3;
        let score = 0;
        let starsGot = 0;
        let running = false;
        let levelDone = false;
        let lock = false;
        let highScore = 0;
        let completed = [];
        let isPaused = false;

        let p = { x:80, y:GROUND-22, vx:0, vy:0, w:20, h:20, onGround:false, facingRight:true };
        let left=false, right=false, jump=false;

        let platforms=[], stars=[], enemies=[], moving=[], tele=[], wind=[], particles=[];

        // ========== OVERLAYS ==========
        function hideAll() {
            document.querySelectorAll('.overlay').forEach(el => el.classList.remove('active'));
            levelGridContainer.classList.add('hidden');
        }

        function show(id) {
            hideAll();
            document.getElementById(id).classList.add('active');
        }

        // ========== SAUVEGARDE (avec try/catch pour incognito) ==========
        function save() {
            try {
                let data = { maxUnlocked, highScore: Math.max(highScore, score), completed, score };
                localStorage.setItem('bounce100', JSON.stringify(data));
            } catch(e) {
                console.log("⚠️ Sauvegarde impossible (incognito)");
            }
        }

        function load() {
            try {
                let raw = localStorage.getItem('bounce100');
                if(raw) {
                    let data = JSON.parse(raw);
                    maxUnlocked = data.maxUnlocked || 1;
                    highScore = data.highScore || 0;
                    completed = data.completed || [];
                    score = data.score || 0;
                    return;
                }
            } catch(e) {
                console.log("⚠️ Chargement impossible (incognito)");
            }
            maxUnlocked = 1;
            highScore = 0;
            completed = [];
            score = 0;
        }

        // ========== GÉNÉRATION NIVEAU ==========
        function genLevel(n) {
            let plat=[], starList=[], enemyList=[], movingList=[], teleList=[], windList=[];
            plat.push({ x:0, y:GROUND, w:W, h:25, type:'normal' });
            let diff = (n-1)/99;
            let pCount = 5 + Math.floor(n/8);
            let eCount = Math.min(6, 1 + Math.floor(n/12));
            let sCount = Math.min(8, 3 + Math.floor(n/10));
            let theme = Math.floor((n%5));
            for(let i=0; i<pCount; i++) {
                let x = 80 + (i*(W-160)/pCount) + (Math.sin(n*i)*35);
                x = Math.min(Math.max(x,60), W-110);
                let y = GROUND - 55 - (i*10) - (Math.sin(n+i)*18);
                y = Math.min(Math.max(y, GROUND-190), GROUND-45);
                let w = 55 + (Math.sin(n+i)*15);
                w = Math.min(Math.max(w,45), 90);
                let type = 'normal';
                if(n>=3 && i===2 && Math.random()>0.6) type='spring';
                if(n>=5 && i===3 && Math.random()>0.7) type='spike';
                if(i===pCount-1) type='goal';
                if(n>=10 && i===1 && Math.random()>0.7) {
                    movingList.push({ x, y, w, h:18, type:'normal', range:60, speed:1, dir:1, startX:x });
                    continue;
                }
                plat.push({ x, y, w, h:18, type });
            }
            for(let i=0; i<sCount; i++) {
                let idx = 1 + (i%(plat.length-1));
                let pl = plat[idx];
                if(pl && pl.type!=='spike') {
                    starList.push({ x: pl.x + pl.w/2 - 7, y: pl.y - 18, collected: false });
                }
            }
            for(let i=0; i<eCount; i++) {
                let idx = 1 + (i%(plat.length-2));
                let pl = plat[idx];
                if(pl && pl.type!=='spike' && pl.type!=='goal') {
                    enemyList.push({
                        x: pl.x+15, y: pl.y-25, w:22, h:22,
                        dir: i%2===0 ? 1 : -1,
                        speed: 1.2 + diff*2.5,
                        type: theme===1 ? 'fire' : 'slime'
                    });
                }
            }
            if(n>=15 && Math.random()>0.8) {
                teleList.push({ x:200, y:GROUND-100, w:30, h:30, tx:W-200, ty:GROUND-100 });
                teleList.push({ x:W-200, y:GROUND-100, w:30, h:30, tx:200, ty:GROUND-100 });
            }
            if(n>=20 && Math.random()>0.7) {
                windList.push({ x:400, y:0, w:150, h:H, force:2+diff*3, dir:1 });
            }
            return { platforms:plat, stars:starList, enemies:enemyList, moving: movingList, tele: teleList, wind: windList };
        }

        let cache = {};
        function getLevel(n) {
            if(!cache[n]) cache[n] = genLevel(n);
            return JSON.parse(JSON.stringify(cache[n]));
        }

        function loadLevel(n) {
            if(n>100) { showFinalWin(); return; }
            let lvl = getLevel(n);
            platforms = lvl.platforms;
            stars = lvl.stars;
            enemies = lvl.enemies;
            moving = lvl.moving || [];
            tele = lvl.tele || [];
            wind = lvl.wind || [];
            p.x = 80; p.y = GROUND - p.h;
            p.vx = 0; p.vy = 0; p.onGround = false;
            starsGot = 0;
            running = true;
            levelDone = false;
            isPaused = false;
            updateUI();
            pokiGameplayStart();
        }

        // ========== UI ==========
        function updateUI() {
            livesSpan.textContent = lives;
            levelSpan.textContent = level;
            scoreSpan.textContent = score;
            let total = stars.length || 0;
            starsSpan.textContent = starsGot + '/' + total;
        }

        function buildGrid() {
            levelGridContainer.innerHTML = '';
            let div = document.createElement('div');
            div.className = 'grid';
            for(let i=1; i<=100; i++) {
                let btn = document.createElement('button');
                btn.textContent = i;
                if(i <= maxUnlocked) {
                    if(completed.includes(i)) btn.classList.add('completed');
                    if(i === level) btn.classList.add('current');
                    btn.onclick = (function(lvl) {
                        return function() {
                            if(confirm('Changer de niveau ? La progression actuelle sera perdue.')) {
                                level = lvl;
                                lives = 3;
                                score = 0;
                                startGame();
                            }
                        };
                    })(i);
                } else {
                    btn.classList.add('locked');
                    btn.disabled = true;
                }
                div.appendChild(btn);
            }
            levelGridContainer.appendChild(div);
            levelGridContainer.classList.remove('hidden');
        }

        // ========== TRANSITIONS ==========
        function startGame() {
            load();
            if(maxUnlocked < 1) maxUnlocked = 1;
            if(level > maxUnlocked) level = maxUnlocked;
            lives = 3;
            score = 0;
            starsGot = 0;
            hideAll();
            loadLevel(level);
            running = true;
            updateUI();
        }

        function goMenu() {
            pokiGameplayStop();
            hideAll();
            mainMenu.classList.add('active');
            running = false;
            buildGrid();
            isPaused = true;
        }

        function showGameOver() {
            pokiGameplayStop();
            hideAll();
            finalScoreSpan.textContent = score;
            gameOverOverlay.classList.add('active');
            running = false;
            isPaused = true;
        }

        function showLevelComplete(msg) {
            pokiGameplayStop();
            hideAll();
            completeMsgSpan.innerHTML = msg;
            levelCompleteOverlay.classList.add('active');
            running = false;
            isPaused = true;
            // Publicité Poki après un niveau terminé
            pokiCommercialBreak(function() {});
        }

        function showFinalWin() {
            pokiGameplayStop();
            hideAll();
            finalWinScoreSpan.textContent = score;
            finalWinOverlay.classList.add('active');
            running = false;
            isPaused = true;
            save();
        }

        function nextLevel() {
            if(lock) return;
            lock = true;
            levelCompleteOverlay.classList.remove('active');
            if(level < 100) {
                level++;
                lives = Math.min(lives+1, 5);
                loadLevel(level);
                running = true;
                levelDone = false;
                isPaused = false;
                updateUI();
            } else {
                showFinalWin();
            }
            setTimeout(function(){ lock = false; }, 200);
        }

        function retry() {
            gameOverOverlay.classList.remove('active');
            startGame();
        }

        // ========== BOUTON MENU EN JEU ==========
        inGameMenuBtn.addEventListener('click', function() {
            if(confirm('Revenir au menu principal ? La progression actuelle sera perdue.')) {
                goMenu();
            }
        });

        // ========== PHYSIQUE ==========
        function applyPhysics() {
            for(let w of wind) {
                if(p.x + p.w > w.x && p.x < w.x + w.w) {
                    p.vx += w.force * w.dir * 0.1;
                }
            }
            p.vy += GRAVITY;
            if(left) {
                p.vx = Math.max(p.vx - 0.55, -5.8);
                p.facingRight = false;
            } else if(right) {
                p.vx = Math.min(p.vx + 0.55, 5.8);
                p.facingRight = true;
            } else {
                p.vx *= 0.94;
            }
            p.x += p.vx;
            p.y += p.vy;

            for(let m of moving) {
                m.x += m.speed * m.dir;
                if(m.x > m.startX + m.range || m.x < m.startX - m.range) m.dir *= -1;
                if(p.x + p.w > m.x && p.x < m.x + m.w && p.y + p.h > m.y && p.y < m.y + m.h) {
                    if(p.vy >= 0 && p.y + p.h - p.vy <= m.y + 12) {
                        p.y = m.y - p.h;
                        p.vy = 0;
                        p.onGround = true;
                        p.x += m.speed * m.dir;
                    }
                }
            }

            p.onGround = false;
            for(let plat of platforms) {
                if(p.x + p.w > plat.x && p.x < plat.x + plat.w && p.y + p.h > plat.y && p.y < plat.y + plat.h) {
                    if(p.vy >= 0 && p.y + p.h - p.vy <= plat.y + 12) {
                        p.y = plat.y - p.h;
                        p.vy = 0;
                        p.onGround = true;
                        if(plat.type === 'spring') {
                            p.vy = JUMP * 1.3;
                            addParticles(p.x+p.w/2, p.y+p.h, '#ffaa44', 12);
                            playSound('jump');
                        }
                        if(plat.type === 'spike') {
                            hitPlayer();
                        }
                        if(plat.type === 'goal' && !levelDone && running) {
                            completeLevel();
                        }
                    } else if(p.x + p.w - p.vx <= plat.x + 8) {
                        p.x = plat.x - p.w;
                    } else if(p.x - p.vx >= plat.x + plat.w - 8) {
                        p.x = plat.x + plat.w;
                    } else if(p.vy < 0) {
                        p.y = plat.y + plat.h;
                        p.vy = 0;
                    }
                }
            }

            for(let t of tele) {
                if(p.x + p.w > t.x && p.x < t.x + t.w && p.y + p.h > t.y && p.y < t.y + t.h) {
                    p.x = t.tx;
                    p.y = t.ty;
                    playSound('collect');
                    addParticles(p.x, p.y, '#00ffff', 15);
                }
            }

            if(jump && p.onGround && running) {
                p.vy = JUMP;
                p.onGround = false;
                jump = false;
                addParticles(p.x+p.w/2, p.y+p.h, '#aaaaff', 8);
                playSound('jump');
            }

            if(p.y > H && running) hitPlayer();
            if(p.x < 0) p.x = 0;
            if(p.x + p.w > W) p.x = W - p.w;
        }

        function collectStars() {
            for(let s of stars) {
                if(!s.collected && p.x + p.w > s.x && p.x < s.x+14 && p.y + p.h > s.y && p.y < s.y+14) {
                    s.collected = true;
                    score += 50;
                    starsGot++;
                    updateUI();
                    addParticles(s.x+7, s.y+7, '#ffd700', 10);
                    playSound('collect');
                }
            }
        }

        function updateEnemies() {
            for(let e of enemies) {
                e.x += e.speed * e.dir;
                if(e.x < 40 || e.x + e.w > W-40) e.dir *= -1;
                if(p.x + p.w > e.x && p.x < e.x + e.w && p.y + p.h > e.y && p.y < e.y + e.h && running) {
                    if(p.vy > 0 && p.y + p.h - p.vy <= e.y + 10) {
                        enemies.splice(enemies.indexOf(e), 1);
                        p.vy = JUMP * 0.6;
                        score += 100;
                        updateUI();
                        addParticles(e.x+e.w/2, e.y+e.h/2, '#ff6666', 15);
                        playSound('collect');
                    } else {
                        hitPlayer();
                    }
                }
            }
        }

        function hitPlayer() {
            lives--;
            updateUI();
            addParticles(p.x+p.w/2, p.y+p.h/2, '#ff0000', 20);
            playSound('hit');
            if(lives <= 0) {
                running = false;
                isPaused = true;
                save();
                showGameOver();
            } else {
                p.x = 80;
                p.y = GROUND - p.h;
                p.vx = 0;
                p.vy = 0;
            }
        }

        function completeLevel() {
            if(levelDone) return;
            levelDone = true;
            running = false;
            isPaused = true;
            let bonus = starsGot * 50 + 200;
            score += bonus;
            if(!completed.includes(level)) completed.push(level);
            if(level === maxUnlocked && level < 100) maxUnlocked = level + 1;
            if(score > highScore) highScore = score;
            save();
            playSound('levelComplete');
            let msg = '+' + bonus + ' pts ⭐ ' + starsGot + '/' + stars.length;
            showLevelComplete(msg);
        }

        // ========== SON ==========
        let actx = null;
        function playSound(type) {
            try {
                if(!actx) actx = new (window.AudioContext || window.webkitAudioContext)();
                let osc = actx.createOscillator();
                let gain = actx.createGain();
                osc.connect(gain);
                gain.connect(actx.destination);
                let f=440, d=0.2;
                switch(type) {
                    case 'jump': f=523.25; d=0.12; break;
                    case 'collect': f=659.25; d=0.1; break;
                    case 'hit': f=220; d=0.3; break;
                    case 'levelComplete': f=783.99; d=0.4; break;
                    case 'levelStart': f=392; d=0.2; break;
                }
                osc.frequency.value = f;
                gain.gain.value = 0.2;
                osc.start();
                gain.gain.exponentialRampToValueAtTime(0.00001, actx.currentTime + d);
                osc.stop(actx.currentTime + d);
            } catch(e) {}
        }

        // ========== PARTICULES ==========
        function addParticles(x,y,color,count) {
            if(!count) count = 8;
            for(let i=0; i<count; i++) {
                particles.push({
                    x, y,
                    vx: (Math.random()-0.5)*4,
                    vy: (Math.random()-0.5)*3-1,
                    life: 0.7,
                    color,
                    size: Math.random()*3+1.5
                });
            }
        }

        function updateParticles() {
            for(let i=0; i<particles.length; i++) {
                let pt = particles[i];
                pt.x += pt.vx;
                pt.y += pt.vy;
                pt.life -= 0.02;
                if(pt.life <= 0) { particles.splice(i,1); i--; }
            }
        }

        // ========== DESSIN ==========
        function drawBg() {
            let g = ctx.createLinearGradient(0,0,0,H);
            g.addColorStop(0,'#0a0f2a');
            g.addColorStop(1,'#1a1f3a');
            ctx.fillStyle = g;
            ctx.fillRect(0,0,W,H);
            ctx.fillStyle = 'rgba(255,255,200,0.2)';
            for(let i=0;i<80;i++) {
                ctx.beginPath();
                ctx.arc((i*131)%W, (i*57)%H, 1.2, 0, Math.PI*2);
                ctx.fill();
            }
        }

        function drawPlatforms() {
            for(let plat of platforms) {
                ctx.shadowBlur = 3;
                if(plat.type === 'spike') {
                    ctx.fillStyle = '#aa4444';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    for(let i=0; i<plat.w/12; i++) {
                        ctx.beginPath();
                        ctx.moveTo(plat.x+i*12, plat.y);
                        ctx.lineTo(plat.x+i*12+6, plat.y-10);
                        ctx.lineTo(plat.x+i*12-6, plat.y-10);
                        ctx.fillStyle = '#ff6666';
                        ctx.fill();
                    }
                } else if(plat.type === 'spring') {
                    ctx.fillStyle = '#f0a030';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#d08020';
                    for(let i=0;i<4;i++) ctx.fillRect(plat.x+8+i*14, plat.y-6, 8,6);
                } else if(plat.type === 'goal') {
                    ctx.fillStyle = '#44aa77';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#ffdd88';
                    ctx.font = '22px monospace';
                    ctx.fillText('🏁', plat.x+plat.w/2-8, plat.y+14);
                } else {
                    let g = ctx.createLinearGradient(plat.x, plat.y, plat.x, plat.y+plat.h);
                    g.addColorStop(0, '#5a7a5a');
                    g.addColorStop(1, '#3a5a3a');
                    ctx.fillStyle = g;
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#7a9a7a';
                    ctx.fillRect(plat.x, plat.y, plat.w, 4);
                }
            }
            for(let m of moving) {
                ctx.fillStyle = '#88aaff';
                ctx.fillRect(m.x, m.y, m.w, m.h);
                ctx.fillStyle = '#aaccff';
                ctx.fillRect(m.x, m.y, m.w, 4);
            }
            for(let t of tele) {
                ctx.fillStyle = '#44aaff';
                ctx.globalAlpha = 0.7;
                ctx.fillRect(t.x, t.y, t.w, t.h);
                ctx.fillStyle = '#fff';
                ctx.font = '18px monospace';
                ctx.fillText('🌀', t.x+8, t.y+22);
                ctx.globalAlpha = 1;
            }
            ctx.shadowBlur = 0;
        }

        function drawStars() {
            for(let s of stars) {
                if(!s.collected) {
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#ffaa00';
                    ctx.fillStyle = '#ffdd77';
                    ctx.beginPath();
                    ctx.arc(s.x+7, s.y+7, 7, 0, Math.PI*2);
                    ctx.fill();
                    ctx.fillStyle = '#ffaa33';
                    ctx.font = '16px monospace';
                    ctx.fillText('★', s.x+2, s.y+11);
                }
            }
            ctx.shadowBlur = 0;
        }

        function drawEnemies() {
            for(let e of enemies) {
                ctx.fillStyle = e.type === 'fire' ? '#ff6633' : '#8844cc';
                ctx.shadowBlur = 4;
                ctx.fillRect(e.x, e.y, e.w, e.h);
                ctx.fillStyle = '#fff';
                ctx.fillRect(e.x+5, e.y+5, 4,4);
                ctx.fillRect(e.x+13, e.y+5, 4,4);
                ctx.fillRect(e.x+4, e.y+12, 14,3);
            }
        }

        function drawPlayer() {
            ctx.shadowBlur = 15;
            ctx.shadowColor = '#ff6666';
            let g = ctx.createRadialGradient(p.x+7, p.y+7, 3, p.x+10, p.y+10, 14);
            g.addColorStop(0, '#ff7e7e');
            g.addColorStop(1, '#cc2222');
            ctx.fillStyle = g;
            ctx.beginPath();
            ctx.arc(p.x+p.w/2, p.y+p.h/2, p.w/2, 0, Math.PI*2);
            ctx.fill();
            ctx.shadowBlur = 0;
            ctx.fillStyle = 'rgba(255,255,255,0.5)';
            ctx.beginPath();
            ctx.ellipse(p.x+6, p.y+6, 4, 2, -0.3, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.arc(p.x+(p.facingRight?13:7), p.y+7, 3.5, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = '#222';
            ctx.beginPath();
            ctx.arc(p.x+(p.facingRight?13:7), p.y+6, 2, 0, Math.PI*2);
            ctx.fill();
            ctx.shadowBlur = 0;
        }

        function drawParticles() {
            for(let pt of particles) {
                ctx.globalAlpha = pt.life;
                ctx.fillStyle = pt.color;
                ctx.fillRect(pt.x, pt.y, pt.size, pt.size);
            }
            ctx.globalAlpha = 1;
        }

        function draw() {
            drawBg();
            drawPlatforms();
            drawStars();
            drawEnemies();
            drawPlayer();
            drawParticles();
            ctx.font = 'bold 12px monospace';
            ctx.fillStyle = 'rgba(255,255,255,0.3)';
            ctx.fillText('Niveau ' + level + '/100', 12, 28);
        }

        // ========== BOUCLE ==========
        function update() {
            if(!running || isPaused) return;
            applyPhysics();
            collectStars();
            updateEnemies();
            updateParticles();
        }

        function loop() {
            update();
            draw();
            requestAnimationFrame(loop);
        }

        // ========== CONTROLES ==========
        function keyDown(e) {
            let k = e.key;
            if(k==='ArrowLeft' || k==='q' || k==='Q' || k==='a' || k==='A') { left=true; e.preventDefault(); }
            if(k==='ArrowRight' || k==='d' || k==='D') { right=true; e.preventDefault(); }
            if(k==='ArrowUp' || k===' ' || k==='w' || k==='W' || k==='z' || k==='Z') { jump=true; e.preventDefault(); }
            if(k==='r' || k==='R') { startGame(); e.preventDefault(); }
        }
        function keyUp(e) {
            let k = e.key;
            if(k==='ArrowLeft' || k==='q' || k==='Q' || k==='a' || k==='A') { left=false; e.preventDefault(); }
            if(k==='ArrowRight' || k==='d' || k==='D') { right=false; e.preventDefault(); }
        }

        // ========== INIT ==========
        function initPokiAndStart() {
            // Cette fonction est appelée après l'init du SDK
            load();
            goMenu();
        }

        // Redéfinition de startGame pour l'appel externe
        window.startGame = function() {
            initPokiAndStart();
        };

        // Si le SDK n'est pas chargé, on lance quand même
        if (typeof PokiSDK === 'undefined') {
            console.log("⚠️ Poki SDK non chargé, lancement direct");
            initPokiAndStart();
        }

        // Événements
        playBtn.addEventListener('click', startGame);
        retryBtn.addEventListener('click', retry);
        menuBtnRetry.addEventListener('click', goMenu);
        nextLevelBtn.addEventListener('click', nextLevel);
        menuBtnComplete.addEventListener('click', goMenu);
        winMenuBtn.addEventListener('click', goMenu);
        levelsBtn.addEventListener('click', function() {
            if(levelGridContainer.classList.contains('hidden')) {
                buildGrid();
            } else {
                levelGridContainer.classList.add('hidden');
            }
        });

        window.addEventListener('keydown', keyDown);
        window.addEventListener('keyup', keyUp);

        // Lancer la boucle
        loop();

        // ========== GESTION DE LA VISIBILITÉ (POKI) ==========
        document.addEventListener('visibilitychange', function() {
            if (document.hidden) {
                // Le joueur a quitté l'onglet
                if (running && !isPaused) {
                    pokiGameplayStop();
                    isPaused = true;
                }
            } else {
                // Le joueur est revenu
                if (running && isPaused && !document.querySelector('.overlay.active')) {
                    pokiGameplayStart();
                    isPaused = false;
                }
            }
        });

    })();
</script>
</body>
</html>

Game Source: Bounce 100

Creator: NovaWizard65

Libraries: none

Complexity: complex (1174 lines, 41.8 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: bounce-100-novawizard65" to link back to the original. Then publish at arcadelab.ai/publish.