Spaces:
Running
Running
| <html lang="en" class="overflow-hidden"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Fake Insects Game</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body, button, .cursor-pointer { | |
| cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='53' height='64' viewport='0 0 100 100' style='fill:black;font-size:32px;'><text y='50%'>π</text></svg>"), auto !important; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <audio id="coin-flip-sound" src="coin_flip.mp3"></audio> | |
| <audio id="wiggle-sound" src="wiggle.mp3"></audio> | |
| <audio id="countdown-sound" src="countdown.mp3"></audio> | |
| <div | |
| class="container mx-auto grid h-dvh max-w-4xl grid-rows-[40px,1fr,1fr] gap-1 overflow-hidden border-x bg-white pb-[90px] sm:pb-2 md:grid-cols-2 md:grid-rows-[40px,1fr]" | |
| > | |
| <div | |
| class="flex items-center bg-gray-100 px-3 font-bold md:col-span-2" | |
| > | |
| <h1 class="sm:text-xl"> | |
| <span class="sm:text-2xl">π</span> Fake Insects <span class="bg-gray-200 text-sm text-gray-500 px-1 py-0.5 rounded ml-1 align-text-bottom">v0.0</span> | |
| </h1> | |
| <div class="flex items-center gap-1 max-sm:text-sm sm:gap-1.5 ml-auto sm:ml-12"> | |
| <p class="rounded bg-black px-1.5 text-white"> | |
| <span class="font-normal">TIME LEFT:</span> <span id="time-left">25</span>s | |
| </p> | |
| <p class="rounded bg-black px-1.5 text-white"> | |
| <span class="font-normal">SCORE:</span> <span id="score">0</span> | |
| </p> | |
| </div> | |
| </div> | |
| <div class="overflow-hidden cursor-pointer" id="image1"> | |
| <img | |
| class="size-full object-contain hidden-if-no-src" | |
| src="" | |
| alt="Insect 1" | |
| /> | |
| </div> | |
| <div class="overflow-hidden cursor-pointer" id="image2"> | |
| <img | |
| class="size-full object-contain hidden-if-no-src" | |
| src="" | |
| alt="Insect 2" | |
| /> | |
| </div> | |
| </div> | |
| <!-- only show at the start --> | |
| <div | |
| id="start-overlay" | |
| class="absolute inset-0 grid place-items-center bg-black/50 p-6" | |
| > | |
| <div | |
| class="flex max-w-xl flex-col gap-6 text-balance rounded-xl bg-white p-12 text-center shadow-2xl" | |
| > | |
| <div class="text-5xl">π</div> | |
| <div> | |
| <p class="mb-1 text-xl"> | |
| Find the | |
| <b class="font-semibold" | |
| >fake insect (AI generated) and click on it</b | |
| > | |
| to win a point! | |
| </p> | |
| <p class="text-lg text-gray-400"> | |
| You have 25 seconds to make as many points as possible. | |
| </p> | |
| </div> | |
| <button | |
| id="start-button" | |
| class="flex h-12 items-center justify-center rounded-xl bg-green-500 px-6 font-semibold text-white hover:bg-green-600" | |
| > | |
| Start playing | |
| </button> | |
| </div> | |
| </div> | |
| <!-- only show after the player loses --> | |
| <div | |
| id="game-over-overlay" | |
| class="absolute inset-0 grid hidden place-items-center bg-black/50 p-6" | |
| > | |
| <div | |
| class="flex flex-col gap-6 rounded-xl bg-white p-12 text-center shadow-2xl" | |
| > | |
| <p id="game-over-reason" class="font-semibold text-lg"></p> | |
| <div class="text-5xl">π</div> | |
| <div> | |
| <p class="mb-2 text-4xl font-bold"> | |
| Your score: | |
| <span id="final-score" class="rounded-xl bg-black px-2 text-white">0</span> | |
| </p> | |
| <p id="game-over-message" class="text-lg text-gray-500"> | |
| You found 0 fake insects in 25 seconds. | |
| </p> | |
| <p id="performance-percentage" class="text-md text-gray-500">You've outperformed 0% of players.</p> | |
| <p class="text-md text-gray-500 mt-1"> | |
| Your Highscore: | |
| <span id="highscore" class="rounded-md font-bold bg-black px-1.5 text-white">0</span> | |
| </p> | |
| </div> | |
| <button | |
| id="try-again-button" | |
| class="flex h-12 items-center justify-center rounded-xl bg-black px-6 font-semibold text-white hover:bg-gray-700" | |
| > | |
| Try Again | |
| </button> | |
| </div> | |
| </div> | |
| </body> | |
| <script> | |
| const startOverlay = document.getElementById('start-overlay'); | |
| const gameOverOverlay = document.getElementById('game-over-overlay'); | |
| const startButton = document.getElementById('start-button'); | |
| const tryAgainButton = document.getElementById('try-again-button'); | |
| const timeLeftElement = document.getElementById('time-left'); | |
| const scoreElement = document.getElementById('score'); | |
| const finalScoreElement = document.getElementById('final-score'); | |
| const gameOverMessageElement = document.getElementById('game-over-message'); | |
| const image1 = document.getElementById('image1'); | |
| const image2 = document.getElementById('image2'); | |
| const highscoreElement = document.getElementById('highscore'); | |
| let score = 0; | |
| let timeLeft = 25; | |
| let timer; | |
| let currentRound; | |
| let preloadedImages = { real: [], fake: [] }; | |
| const totalImages = 100; | |
| // Start preloading images immediately | |
| preloadImages(); | |
| // Load highscore from localStorage | |
| let highscore = localStorage.getItem('highscore') || 0; | |
| highscoreElement.textContent = highscore; | |
| function preloadImages(count = 20) { | |
| const uniqueIndexes = new Set(); | |
| for (let i = 1; i <= count; i++) { | |
| let index = Math.floor(Math.random() * totalImages) + 1; | |
| while (uniqueIndexes.has(index)) { | |
| index = Math.floor(Math.random() * totalImages) + 1; | |
| } | |
| uniqueIndexes.add(index); | |
| const paddedIndex = index.toString().padStart(2, '0'); | |
| const realImg = new Image(); | |
| realImg.src = `insects/r/${paddedIndex}.png`; | |
| preloadedImages.real.push(realImg); | |
| const fakeImg = new Image(); | |
| fakeImg.src = `insects/f/${paddedIndex}.png`; | |
| preloadedImages.fake.push(fakeImg); | |
| } | |
| } | |
| function preloadAdditionalImages(count = 5) { | |
| const realCount = Math.max(0, count - preloadedImages.real.length); | |
| const fakeCount = Math.max(0, count - preloadedImages.fake.length); | |
| if (realCount > 0 || fakeCount > 0) { | |
| preloadImages(Math.max(realCount, fakeCount)); | |
| } | |
| } | |
| function getRandomImage(array) { | |
| if (array.length <= 5) { | |
| preloadAdditionalImages(); | |
| } | |
| const randomIndex = Math.floor(Math.random() * array.length); | |
| return array.splice(randomIndex, 1)[0]; | |
| } | |
| function startGame() { | |
| score = 0; | |
| timeLeft = 25; | |
| updateUI(); | |
| startOverlay.classList.add('hidden'); | |
| preloadAdditionalImages(); // Ensure we have enough images | |
| startRound(); | |
| startTimer(); | |
| } | |
| function startRound() { | |
| preloadAdditionalImages(); | |
| const realImage = getRandomImage(preloadedImages.real); | |
| const fakeImage = getRandomImage(preloadedImages.fake); | |
| if (Math.random() < 0.5) { | |
| image1.querySelector('img').src = realImage.src; | |
| image2.querySelector('img').src = fakeImage.src; | |
| currentRound = 2; | |
| } else { | |
| image1.querySelector('img').src = fakeImage.src; | |
| image2.querySelector('img').src = realImage.src; | |
| currentRound = 1; | |
| } | |
| } | |
| function startTimer() { | |
| timer = setInterval(() => { | |
| timeLeft--; | |
| updateUI(); | |
| if (timeLeft === 3) { | |
| document.getElementById('countdown-sound').play(); | |
| } | |
| if (timeLeft <= 0) { | |
| endGame('timeout'); | |
| } | |
| }, 1000); | |
| } | |
| function updateUI() { | |
| timeLeftElement.textContent = timeLeft; | |
| scoreElement.textContent = score; | |
| } | |
| function calculatePerformancePercentage(score) { | |
| const topScore = 30; | |
| const percentage = Math.min(100, Math.max(0, (score / topScore) * 100)); | |
| return Math.round(percentage); | |
| } | |
| function endGame(reason) { | |
| clearInterval(timer); | |
| document.getElementById('countdown-sound').pause(); | |
| document.getElementById('countdown-sound').currentTime = 0; | |
| const elapsedTime = 25 - timeLeft; | |
| finalScoreElement.textContent = score; | |
| gameOverMessageElement.textContent = `You found ${score} fake insect${score !== 1 ? 's' : ''} in ${elapsedTime} second${elapsedTime !== 1 ? 's' : ''}.`; | |
| const gameOverReasonElement = document.getElementById('game-over-reason'); | |
| if (reason === 'timeout') { | |
| gameOverReasonElement.textContent = "Time's up!"; | |
| } else { | |
| gameOverReasonElement.textContent = "You clicked a real insect!"; | |
| } | |
| const performancePercentage = calculatePerformancePercentage(score); | |
| document.getElementById('performance-percentage').textContent = `You've outperformed ${performancePercentage}% of players.`; | |
| // Update highscore if needed | |
| if (score > highscore) { | |
| highscore = score; | |
| localStorage.setItem('highscore', highscore); | |
| highscoreElement.textContent = highscore; | |
| } | |
| gameOverOverlay.classList.remove('hidden'); | |
| } | |
| function handleImageClick(imageNumber) { | |
| if (imageNumber === currentRound) { | |
| const coinFlipSound = document.getElementById('coin-flip-sound').cloneNode(); | |
| coinFlipSound.play(); | |
| score++; | |
| updateUI(); | |
| startRound(); | |
| } else { | |
| const wiggleSound = document.getElementById('wiggle-sound').cloneNode(); | |
| wiggleSound.play(); | |
| endGame('wrong-click'); | |
| } | |
| } | |
| startButton.addEventListener('click', startGame); | |
| tryAgainButton.addEventListener('click', () => { | |
| gameOverOverlay.classList.add('hidden'); | |
| preloadAdditionalImages(); // Preload images before restarting | |
| startGame(); | |
| }); | |
| image1.addEventListener('click', () => handleImageClick(1)); | |
| image2.addEventListener('click', () => handleImageClick(2)); | |
| // Function to hide images with no src | |
| function hideImagesWithNoSrc() { | |
| const images = document.querySelectorAll('.hidden-if-no-src'); | |
| images.forEach(img => { | |
| if (!img.src || img.src === window.location.href) { | |
| img.style.display = 'none'; | |
| } else { | |
| img.style.display = 'block'; | |
| } | |
| }); | |
| } | |
| // Call the function when images are loaded or when their src changes | |
| const observer = new MutationObserver(hideImagesWithNoSrc); | |
| document.querySelectorAll('.hidden-if-no-src').forEach(img => { | |
| observer.observe(img, { attributes: true, attributeFilter: ['src'] }); | |
| img.addEventListener('load', hideImagesWithNoSrc); | |
| img.addEventListener('error', hideImagesWithNoSrc); | |
| }); | |
| // Initial call to hide images with no src | |
| hideImagesWithNoSrc(); | |
| </script> | |
| </html> |