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> |