Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Metal Detection Minigame</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap'); | |
| body { | |
| font-family: 'Orbitron', monospace; | |
| background: radial-gradient(ellipse at center, #0a0a0a 0%, #000000 100%); | |
| overflow: hidden; | |
| } | |
| .radar-container { | |
| position: relative; | |
| width: 320px; | |
| height: 320px; | |
| background: radial-gradient(circle at center, rgba(16, 185, 129, 0.1) 0%, rgba(0, 0, 0, 0.8) 70%), | |
| repeating-radial-gradient(circle at center, | |
| transparent 0, | |
| transparent 39px, | |
| rgba(16, 185, 129, 0.3) 40px, | |
| rgba(16, 185, 129, 0.3) 41px); | |
| border: 3px solid #10b981; | |
| box-shadow: 0 0 50px rgba(16, 185, 129, 0.5), | |
| inset 0 0 50px rgba(16, 185, 129, 0.2); | |
| animation: radarPulse 2s ease-in-out infinite; | |
| } | |
| @keyframes radarPulse { | |
| 0%, 100% { box-shadow: 0 0 50px rgba(16, 185, 129, 0.5), inset 0 0 50px rgba(16, 185, 129, 0.2); } | |
| 50% { box-shadow: 0 0 80px rgba(16, 185, 129, 0.8), inset 0 0 80px rgba(16, 185, 129, 0.4); } | |
| } | |
| .sweep-line { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 50%; | |
| height: 2px; | |
| background: linear-gradient(to right, #10b981, transparent); | |
| transform-origin: left center; | |
| box-shadow: 0 0 10px #10b981, 0 0 20px #10b981; | |
| animation: sweepRotate 4s linear infinite; | |
| } | |
| .sweep-line::before { | |
| content: ''; | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(to right, rgba(16, 185, 129, 0.8), transparent); | |
| filter: blur(3px); | |
| } | |
| .blip { | |
| position: absolute; | |
| width: 12px; | |
| height: 12px; | |
| background: radial-gradient(circle, #ef4444 0%, #dc2626 70%, transparent 100%); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| box-shadow: 0 0 20px #ef4444, 0 0 40px #ef4444; | |
| animation: blipGlow 1.5s ease-in-out infinite; | |
| } | |
| @keyframes blipGlow { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) scale(1); | |
| opacity: 1; | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.3); | |
| opacity: 0.7; | |
| } | |
| } | |
| .locked-blip { | |
| background: radial-gradient(circle, #10b981 0%, #059669 70%, transparent 100%); | |
| animation: lockedGlow 1s ease-in-out infinite; | |
| box-shadow: 0 0 30px #10b981, 0 0 60px #10b981; | |
| } | |
| @keyframes lockedGlow { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) scale(1) rotate(0deg); | |
| filter: brightness(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.2) rotate(180deg); | |
| filter: brightness(1.5); | |
| } | |
| } | |
| .hit-effect { | |
| position: absolute; | |
| width: 60px; | |
| height: 60px; | |
| border: 3px solid #10b981; | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| animation: hitRipple 0.6s ease-out forwards; | |
| } | |
| @keyframes hitRipple { | |
| 0% { | |
| transform: translate(-50%, -50%) scale(0.5); | |
| opacity: 1; | |
| } | |
| 100% { | |
| transform: translate(-50%, -50%) scale(3); | |
| opacity: 0; | |
| } | |
| } | |
| .score-display { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| text-shadow: 0 0 10px #10b981; | |
| letter-spacing: 2px; | |
| } | |
| .instructions { | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| letter-spacing: 1px; | |
| } | |
| .key-hint { | |
| color: #10b981; | |
| font-weight: 700; | |
| text-shadow: 0 0 5px #10b981; | |
| animation: keyPulse 2s ease-in-out infinite; | |
| } | |
| @keyframes keyPulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .hud-element { | |
| background: rgba(0, 0, 0, 0.7); | |
| border: 1px solid rgba(16, 185, 129, 0.3); | |
| box-shadow: 0 0 20px rgba(16, 185, 129, 0.2); | |
| } | |
| .grid-line { | |
| position: absolute; | |
| background: rgba(16, 185, 129, 0.2); | |
| } | |
| .grid-line.horizontal { | |
| width: 100%; | |
| height: 1px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .grid-line.vertical { | |
| width: 1px; | |
| height: 100%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| } | |
| .angle-markers { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| } | |
| .angle-marker { | |
| position: absolute; | |
| width: 2px; | |
| height: 10px; | |
| background: rgba(16, 185, 129, 0.5); | |
| transform-origin: center 160px; | |
| left: 50%; | |
| top: 0; | |
| } | |
| </style> | |
| </head> | |
| <body class="flex flex-col items-center justify-center min-h-screen text-green-400"> | |
| <div class="absolute top-4 left-4 hud-element p-4 rounded-lg"> | |
| <div class="text-sm opacity-70">METAL DETECTOR</div> | |
| <div class="text-lg font-bold">Model XR-7</div> | |
| </div> | |
| <div class="absolute top-4 right-4 hud-element p-4 rounded-lg"> | |
| <div class="text-sm opacity-70">FREQUENCY</div> | |
| <div class="text-lg font-bold">14.5 kHz</div> | |
| </div> | |
| <div class="flex flex-col items-center"> | |
| <h1 class="text-4xl font-bold mb-2 tracking-wider">METAL DETECTION</h1> | |
| <p class="instructions mb-8 text-center">Press <span class="key-hint">E</span> when the sweep line crosses a <span class="text-red-400">red blip</span></p> | |
| <div class="relative radar-container rounded-full"> | |
| <!-- Grid lines --> | |
| <div class="grid-line horizontal"></div> | |
| <div class="grid-line vertical"></div> | |
| <!-- Angle markers --> | |
| <div class="angle-markers"> | |
| <div class="angle-marker" style="transform: rotate(0deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(30deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(60deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(90deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(120deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(150deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(180deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(210deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(240deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(270deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(300deg)"></div> | |
| <div class="angle-marker" style="transform: rotate(330deg)"></div> | |
| </div> | |
| <!-- Scanning circles --> | |
| <div class="absolute inset-8 rounded-full border border-green-500 opacity-20"></div> | |
| <div class="absolute inset-16 rounded-full border border-green-500 opacity-20"></div> | |
| <div class="absolute inset-24 rounded-full border border-green-500 opacity-20"></div> | |
| <!-- Radar sweep line --> | |
| <div class="sweep-line" id="sweepLine"></div> | |
| <!-- Blips will be added here dynamically --> | |
| <div id="blipsContainer"></div> | |
| <!-- Hit effects --> | |
| <div id="hitEffects"></div> | |
| </div> | |
| <div class="mt-8 flex items-center space-x-8"> | |
| <div class="hud-element px-6 py-3 rounded-lg"> | |
| <div class="text-sm opacity-70">TARGETS LOCKED</div> | |
| <div class="score-display text-3xl" id="scoreDisplay">0/3</div> | |
| </div> | |
| <div class="hud-element px-6 py-3 rounded-lg"> | |
| <div class="text-sm opacity-70">ACCURACY</div> | |
| <div class="score-display text-3xl" id="accuracyDisplay">0%</div> | |
| </div> | |
| </div> | |
| <div class="mt-4 hud-element px-6 py-3 rounded-lg"> | |
| <div id="statusDisplay" class="text-lg">Scanning for metals...</div> | |
| </div> | |
| </div> | |
| <audio id="hitSound" preload="auto"> | |
| <source src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..." type="audio/wav"> | |
| </audio> | |
| <audio id="missSound" preload="auto"> | |
| <source src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..." type="audio/wav"> | |
| </audio> | |
| <audio id="successSound" preload="auto"> | |
| <source src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..." type="audio/wav"> | |
| </audio> | |
| <script> | |
| // Game variables | |
| let score = 0; | |
| let gameActive = true; | |
| let blips = []; | |
| let lockedBlips = []; | |
| let sweepAngle = 0; | |
| let totalAttempts = 0; | |
| let successfulHits = 0; | |
| // Game constants | |
| const TOTAL_TARGETS = 3; | |
| const SWEEP_SPEED = 4; // seconds per rotation | |
| const HIT_TOLERANCE = 6; // degrees | |
| // DOM elements | |
| const sweepLine = document.getElementById('sweepLine'); | |
| const blipsContainer = document.getElementById('blipsContainer'); | |
| const scoreDisplay = document.getElementById('scoreDisplay'); | |
| const statusDisplay = document.getElementById('statusDisplay'); | |
| const accuracyDisplay = document.getElementById('accuracyDisplay'); | |
| const hitEffects = document.getElementById('hitEffects'); | |
| const hitSound = document.getElementById('hitSound'); | |
| const missSound = document.getElementById('missSound'); | |
| const successSound = document.getElementById('successSound'); | |
| // Create oscilloscope-style audio using Web Audio API | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| function playHitSound() { | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.frequency.setValueAtTime(800, audioContext.currentTime); | |
| oscillator.type = 'sine'; | |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); | |
| oscillator.start(audioContext.currentTime); | |
| oscillator.stop(audioContext.currentTime + 0.3); | |
| } | |
| function playMissSound() { | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.frequency.setValueAtTime(200, audioContext.currentTime); | |
| oscillator.type = 'sawtooth'; | |
| gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); | |
| oscillator.start(audioContext.currentTime); | |
| oscillator.stop(audioContext.currentTime + 0.2); | |
| } | |
| function playSuccessSound() { | |
| const notes = [523.25, 659.25, 783.99]; // C, E, G | |
| notes.forEach((freq, index) => { | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.frequency.setValueAtTime(freq, audioContext.currentTime + index * 0.1); | |
| oscillator.type = 'sine'; | |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime + index * 0.1); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + index * 0.1 + 0.5); | |
| oscillator.start(audioContext.currentTime + index * 0.1); | |
| oscillator.stop(audioContext.currentTime + index * 0.1 + 0.5); | |
| }); | |
| } | |
| // Initialize the game | |
| function initGame() { | |
| // Create initial blips | |
| createBlips(); | |
| // Set up keyboard event listener | |
| document.addEventListener('keydown', handleKeyPress); | |
| // Update the sweep line rotation | |
| setInterval(updateSweep, 50); | |
| } | |
| // Create blips on the radar | |
| function createBlips() { | |
| const numBlips = 5; | |
| const minAngleDiff = 12; // Minimum angle difference between blips | |
| const maxAttempts = 5; // Prevent infinite loop | |
| let placed = 0; | |
| let attempts = 0; | |
| while (placed < numBlips && attempts < maxAttempts) { | |
| const angle = Math.random() * 360; | |
| const distance = 0.3 + Math.random() * 0.5; | |
| const x = 160 + 160 * distance * Math.cos(angle * Math.PI / 180); | |
| const y = 160 + 160 * distance * Math.sin(angle * Math.PI / 180); | |
| // Check angular distance from existing blips | |
| let tooClose = blips.some(blip => { | |
| const diff = Math.abs(blip.angle - angle); | |
| const angleDiff = Math.min(diff, 360 - diff); // handle wraparound | |
| return angleDiff < minAngleDiff; | |
| }); | |
| if (!tooClose) { | |
| const blip = document.createElement('div'); | |
| blip.className = 'blip'; | |
| blip.style.left = x + 'px'; | |
| blip.style.top = y + 'px'; | |
| blip.dataset.angle = angle; | |
| blipsContainer.appendChild(blip); | |
| blips.push({ | |
| element: blip, | |
| angle: angle, | |
| locked: false | |
| }); | |
| placed++; | |
| } | |
| attempts++; | |
| } | |
| } | |
| // Update the sweep line rotation | |
| function updateSweep() { | |
| sweepAngle = (sweepAngle + (360 / (SWEEP_SPEED * 20))) % 360; | |
| sweepLine.style.transform = `rotate(${sweepAngle}deg)`; | |
| } | |
| // Handle key press | |
| function handleKeyPress(event) { | |
| if (!gameActive || event.key.toLowerCase() !== 'e') return; | |
| totalAttempts++; | |
| let hit = false; | |
| // Check if any blip is within the tolerance | |
| for (let i = 0; i < blips.length; i++) { | |
| const blip = blips[i]; | |
| if (blip.locked) continue; | |
| const angleDiff = Math.abs((blip.angle - sweepAngle + 360) % 360); | |
| const isWithinTolerance = angleDiff <= HIT_TOLERANCE || angleDiff >= (360 - HIT_TOLERANCE); | |
| if (isWithinTolerance) { | |
| // Hit! | |
| blip.locked = true; | |
| blip.element.classList.add('locked-blip'); | |
| lockedBlips.push(blip); | |
| // Create hit effect | |
| createHitEffect(blip.element.offsetLeft + 6, blip.element.offsetTop + 6); | |
| score++; | |
| successfulHits++; | |
| updateScoreDisplay(); | |
| playHitSound(); | |
| hit = true; | |
| break; | |
| } | |
| } | |
| if (!hit) { | |
| playMissSound(); | |
| } | |
| updateAccuracy(); | |
| // Check if game is complete | |
| if (score >= TOTAL_TARGETS) { | |
| gameComplete(); | |
| } | |
| } | |
| // Create hit effect | |
| function createHitEffect(x, y) { | |
| const effect = document.createElement('div'); | |
| effect.className = 'hit-effect'; | |
| effect.style.left = x + 'px'; | |
| effect.style.top = y + 'px'; | |
| hitEffects.appendChild(effect); | |
| // Remove after animation completes | |
| setTimeout(() => { | |
| effect.remove(); | |
| }, 600); | |
| } | |
| // Update score display | |
| function updateScoreDisplay() { | |
| scoreDisplay.textContent = `${score}/${TOTAL_TARGETS}`; | |
| if (score < TOTAL_TARGETS) { | |
| statusDisplay.textContent = `Lock onto target ${score + 1}...`; | |
| } | |
| } | |
| // Update accuracy display | |
| function updateAccuracy() { | |
| const accuracy = totalAttempts > 0 ? Math.round((successfulHits / totalAttempts) * 100) : 0; | |
| accuracyDisplay.textContent = `${accuracy}%`; | |
| } | |
| // Game complete | |
| function gameComplete() { | |
| gameActive = false; | |
| statusDisplay.textContent = "All targets locked! Excavation authorized."; | |
| playSuccessSound(); | |
| // Add celebration effect | |
| setTimeout(() => { | |
| document.body.style.animation = 'celebration 2s ease-in-out'; | |
| }, 1000); | |
| // Remove after 3 seconds | |
| setTimeout(() => { | |
| if (confirm("Metal detection complete! Proceed with excavation?")) { | |
| location.reload(); | |
| } | |
| }, 3000); | |
| } | |
| // Add celebration animation | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| @keyframes celebration { | |
| 0% { filter: brightness(1); } | |
| 50% { filter: brightness(1.5) hue-rotate(120deg); } | |
| 100% { filter: brightness(1); } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| // Start the game | |
| initGame(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Mankeysocks/ex-7" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |