Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Bubble Trouble - Fun Physics Game</title> | |
| <link rel="icon" type="image/x-icon" href="/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://unpkg.com/[email protected]/dist/aos.css" rel="stylesheet"> | |
| <script src="https://unpkg.com/[email protected]/dist/aos.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> | |
| <style> | |
| #hero { | |
| height: 100vh; | |
| position: relative; | |
| overflow: hidden; | |
| touch-action: manipulation; | |
| } | |
| .game-area { | |
| width: 100%; | |
| height: 60vh; | |
| min-height: 300px; | |
| max-height: 600px; | |
| } | |
| .bubble { | |
| position: absolute; | |
| pointer-events: none; | |
| animation: float 6s infinite ease-in-out; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| text-shadow: 0 0 10px rgba(255,255,255,0.5); | |
| transition: transform 0.2s ease; | |
| } | |
| .bubble:hover { | |
| transform: scale(1.2); | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| #gameModal { | |
| backdrop-filter: blur(5px); | |
| } | |
| @keyframes float { | |
| 0%, 100% { | |
| transform: translateY(0) translateX(0); | |
| } | |
| 50% { | |
| transform: translateY(-50px) translateX(20px); | |
| } | |
| } | |
| .game-container { | |
| perspective: 1000px; | |
| } | |
| .game-card { | |
| transition: transform 0.5s ease; | |
| } | |
| .game-card:hover { | |
| transform: rotateY(10deg) scale(1.03); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-blue-900 to-purple-900 text-white"> | |
| <div id="hero" class="flex flex-col items-center justify-center"> | |
| <div class="absolute inset-0 hovered-element" id="vanta-bg"></div> | |
| <div class="relative z-10 text-center px-4"> | |
| <h1 class="text-5xl md:text-7xl font-bold mb-6" data-aos="fade-down">Bubble Trouble</h1> | |
| <p class="text-xl md:text-2xl mb-8 max-w-2xl mx-auto" data-aos="fade-up" data-aos-delay="200"> | |
| Pop as many smiley bubbles as you can in 30 seconds! | |
| </p> | |
| <div class="flex flex-col sm:flex-row gap-4 justify-center" data-aos="zoom-in" data-aos-delay="400"> | |
| <a href="#play" class="bg-pink-600 hover:bg-pink-700 text-white font-bold py-3 px-6 rounded-full transition-all transform hover:scale-105 shadow-lg"> | |
| Play Now | |
| </a> | |
| <a href="#features" class="bg-transparent border-2 border-white hover:bg-white hover:text-purple-900 font-bold py-3 px-6 rounded-full transition-all transform hover:scale-105"> | |
| Learn More | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <section id="play" class="py-20 px-4"> | |
| <div class="max-w-6xl mx-auto"> | |
| <h2 class="text-3xl md:text-4xl font-bold text-center mb-12" data-aos="fade-up">Play Bubble Trouble</h2> | |
| <div class="game-container grid grid-cols-1 gap-8 items-center justify-center"> | |
| <div data-aos="fade-right"> | |
| </div> | |
| <div data-aos="fade-up" data-aos-delay="200" class="mx-auto"> | |
| <div class="bg-white/10 backdrop-blur-md rounded-2xl p-8 shadow-xl w-full max-w-md"> | |
| <h3 class="text-2xl font-bold mb-6 text-center">Start Playing</h3> | |
| <div class="space-y-4 mb-6"> | |
| <div> | |
| <label class="block mb-2">Your Name</label> | |
| <input type="text" class="w-full bg-white/10 border border-white/20 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-pink-500"> | |
| </div> | |
| <div> | |
| <label class="block mb-2">Institution</label> | |
| <input type="text" class="w-full bg-white/10 border border-white/20 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-pink-500"> | |
| </div> | |
| <div> | |
| <label class="block mb-2">City</label> | |
| <input type="text" class="w-full bg-white/10 border border-white/20 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-pink-500"> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="agb" class="mr-3 h-5 w-5 rounded border-white/30 bg-white/10 focus:ring-pink-500"> | |
| <label for="agb">I accept the AGB</label> | |
| </div> | |
| </div> | |
| <button class="w-full bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700 text-white font-bold py-4 px-6 rounded-full transition-all transform hover:scale-105 mb-6 disabled:opacity-50" disabled> | |
| Start Game | |
| </button> | |
| <p class="text-sm opacity-80 text-center">Works on any device - PC, Mac, phone or tablet!</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <footer class="bg-black/30 py-12 px-4"> | |
| <div class="max-w-6xl mx-auto"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-6 md:mb-0"> | |
| <h2 class="text-2xl font-bold">Bubble Trouble</h2> | |
| <p class="opacity-80 mt-2">Β© 2023 All rights reserved</p> | |
| </div> | |
| <div class="flex space-x-6"> | |
| <a href="#" class="hover:text-pink-400 transition-colors"> | |
| <i data-feather="twitter"></i> | |
| </a> | |
| <a href="#" class="hover:text-pink-400 transition-colors"> | |
| <i data-feather="instagram"></i> | |
| </a> | |
| <a href="#" class="hover:text-pink-400 transition-colors"> | |
| <i data-feather="facebook"></i> | |
| </a> | |
| <a href="#" class="hover:text-pink-400 transition-colors"> | |
| <i data-feather="github"></i> | |
| </a> | |
| </div> | |
| </div> | |
| <div class="border-t border-white/10 mt-8 pt-8 text-center md:text-left"> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> | |
| <div> | |
| <h4 class="font-bold mb-4">Game</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Play</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Features</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Leaderboard</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-bold mb-4">Company</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">About</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Careers</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Press</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-bold mb-4">Support</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Help Center</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Community</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Contact</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-bold mb-4">Legal</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Privacy</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Terms</a></li> | |
| <li><a href="#" class="hover:text-pink-400 transition-colors">Cookies</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| // Game variables | |
| let gameStartTime; | |
| let score = 0; | |
| let gameActive = false; | |
| let gameTimeout; | |
| // Vanta.js background | |
| VANTA.GLOBE({ | |
| el: "#vanta-bg", | |
| mouseControls: true, | |
| touchControls: true, | |
| gyroControls: false, | |
| minHeight: 200.00, | |
| minWidth: 200.00, | |
| scale: 1.00, | |
| scaleMobile: 1.00, | |
| color: 0x7d3aff, | |
| backgroundColor: 0x0, | |
| size: 0.8 | |
| }); | |
| // Create floating bubbles | |
| document.addEventListener('DOMContentLoaded', () => { | |
| AOS.init({ | |
| duration: 800, | |
| easing: 'ease-in-out', | |
| once: true | |
| }); | |
| feather.replace(); | |
| // Create smiley bubbles | |
| const bubbleContainer = document.getElementById('hero'); | |
| const smileys = ['π', 'π', 'π€©', 'π', 'π₯³', 'π', 'π€ͺ', 'π', 'π', 'π', 'π€', 'π₯°', 'π', 'π€', 'πΊ']; | |
| for (let i = 0; i < 15; i++) { | |
| const bubble = document.createElement('div'); | |
| bubble.classList.add('bubble'); | |
| bubble.innerHTML = smileys[Math.floor(Math.random() * smileys.length)]; | |
| bubble.style.fontSize = `${Math.random() * 30 + 20}px`; | |
| bubble.style.left = `${Math.random() * 100}%`; | |
| bubble.style.top = `${Math.random() * 100}%`; | |
| bubble.style.animationDelay = `${Math.random() * 5}s`; | |
| bubble.style.transform = `rotate(${Math.random() * 360}deg)`; | |
| bubbleContainer.appendChild(bubble); | |
| } | |
| // Enable start button when form is complete | |
| const formInputs = document.querySelectorAll('input'); | |
| const startButton = document.querySelector('.bg-gradient-to-r.from-pink-500'); | |
| formInputs.forEach(input => { | |
| input.addEventListener('input', checkForm); | |
| }); | |
| function checkForm() { | |
| const name = formInputs[0].value.trim(); | |
| const institution = formInputs[1].value.trim(); | |
| const city = formInputs[2].value.trim(); | |
| const agbChecked = formInputs[3].checked; | |
| if (name && institution && city && agbChecked) { | |
| startButton.disabled = false; | |
| } else { | |
| startButton.disabled = true; | |
| } | |
| } | |
| // Start game function | |
| startButton.addEventListener('click', startGame); | |
| function startGame() { | |
| gameActive = true; | |
| score = 0; | |
| gameStartTime = Date.now(); | |
| // Hide form and show game area | |
| document.querySelector('.bg-white\\/10').classList.add('hidden'); | |
| // Create game container | |
| const gameContainer = document.createElement('div'); | |
| gameContainer.id = 'gameArea'; | |
| gameContainer.className = 'game-area bg-black/20 rounded-xl relative overflow-hidden'; | |
| gameContainer.style.touchAction = 'manipulation'; | |
| gameContainer.innerHTML = ` | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <p class="text-2xl">Pop the smileys! Score: <span id="scoreDisplay">0</span></p> | |
| </div> | |
| `; | |
| document.querySelector('#play .max-w-6xl').appendChild(gameContainer); | |
| // Start smiley spawning | |
| spawnSmiley(); | |
| // Set game timer (30 seconds) | |
| gameTimeout = setTimeout(endGame, 30000); | |
| } | |
| function spawnSmiley() { | |
| if (!gameActive) return; | |
| const gameArea = document.getElementById('gameArea'); | |
| if (!gameArea) return; | |
| const smiley = document.createElement('div'); | |
| smiley.className = 'absolute cursor-pointer text-4xl animate-bounce select-none'; | |
| smiley.style.userSelect = 'none'; | |
| smiley.style.webkitUserSelect = 'none'; | |
| smiley.innerHTML = ['π', 'π', 'π€©', 'π', 'π₯³'][Math.floor(Math.random() * 5)]; | |
| smiley.style.left = `${Math.random() * 80 + 10}%`; | |
| smiley.style.top = `${Math.random() * 80 + 10}%`; | |
| smiley.addEventListener('click', () => { | |
| score++; | |
| document.getElementById('scoreDisplay').textContent = score; | |
| smiley.remove(); | |
| }); | |
| gameArea.appendChild(smiley); | |
| // Remove smiley after random time (0.5-2s) | |
| // Auto-remove smiley if not clicked | |
| const removeTimeout = setTimeout(() => { | |
| if (smiley.parentNode) smiley.remove(); | |
| }, 500 + Math.random() * 1500); | |
| // Touch/click handler | |
| const handleInteraction = () => { | |
| clearTimeout(removeTimeout); | |
| score++; | |
| document.getElementById('scoreDisplay').textContent = score; | |
| smiley.remove(); | |
| }; | |
| smiley.addEventListener('click', handleInteraction); | |
| smiley.addEventListener('touchend', handleInteraction); | |
| // Spawn next smiley after random delay (0.2-0.8s) | |
| // Use requestAnimationFrame for better performance | |
| const spawnDelay = 200 + Math.random() * 600; | |
| let lastTime = performance.now(); | |
| const checkSpawn = (currentTime) => { | |
| if (currentTime - lastTime >= spawnDelay) { | |
| spawnSmiley(); | |
| } else if (gameActive) { | |
| requestAnimationFrame(checkSpawn); | |
| } | |
| }; | |
| if (gameActive) { | |
| requestAnimationFrame(checkSpawn); | |
| } | |
| } | |
| function endGame() { | |
| gameActive = false; | |
| clearTimeout(gameTimeout); | |
| cancelAnimationFrame(spawnSmiley); | |
| const gameArea = document.getElementById('gameArea'); | |
| gameArea.innerHTML = ` | |
| <div class="absolute inset-0 flex items-center justify-center flex-col bg-gradient-to-br from-pink-500 via-purple-500 to-blue-500"> | |
| <h3 class="text-6xl font-bold mb-6 text-white animate-bounce">π Congratulations! π</h3> | |
| <p class="text-4xl text-yellow-300 font-bold animate-pulse">Your score: ${score}</p> | |
| <div class="mt-8 flex space-x-4"> | |
| <div class="text-4xl animate-spin">β¨</div> | |
| <div class="text-4xl animate-ping">π</div> | |
| <div class="text-4xl animate-bounce">β</div> | |
| </div> | |
| <button id="newGameBtn" class="mt-8 bg-white text-purple-900 hover:bg-purple-100 font-bold py-3 px-8 rounded-full transition-all transform hover:scale-105 shadow-lg text-xl"> | |
| New Game | |
| </button> | |
| </div> | |
| `; | |
| document.getElementById('newGameBtn').addEventListener('click', () => { | |
| gameArea.remove(); | |
| document.querySelector('.bg-white\\/10').classList.remove('hidden'); | |
| formInputs[0].value = ''; | |
| formInputs[1].value = ''; | |
| formInputs[2].value = ''; | |
| formInputs[3].checked = false; | |
| startButton.disabled = true; | |
| }); | |
| // Save data to Excel (using SheetJS) | |
| const name = formInputs[0].value.trim(); | |
| const institution = formInputs[1].value.trim(); | |
| const city = formInputs[2].value.trim(); | |
| saveToExcel(name, institution, city, score); | |
| } | |
| function saveToExcel(name, institution, city, score) { | |
| // Using SheetJS to save data | |
| const script = document.createElement('script'); | |
| script.src = 'https://cdn.sheetjs.com/xlsx-0.19.3/package/dist/xlsx.full.min.js'; | |
| script.onload = function() { | |
| let data; | |
| try { | |
| // Try to load existing data | |
| const existing = localStorage.getItem('bubbleGameData'); | |
| data = existing ? JSON.parse(existing) : []; | |
| } catch { | |
| data = []; | |
| } | |
| // Add new entry | |
| data.push({ | |
| name, | |
| institution, | |
| city, | |
| score, | |
| date: new Date().toISOString() | |
| }); | |
| // Save updated data | |
| localStorage.setItem('bubbleGameData', JSON.stringify(data)); | |
| // Convert to Excel | |
| const ws = XLSX.utils.json_to_sheet(data); | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, "Scores"); | |
| XLSX.writeFile(wb, "BubbleTroubleScores.xlsx"); | |
| }; | |
| document.head.appendChild(script); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |