Spaces:
Running
Running

Diminuer de moitié en hauteur la zone "prêt à scanner" et la carte de Paris. Utiliser l'espace disponible entre "carte de paris" et la carte géolocalisée pour afficher les messages pour l'utilisateur - Follow Up Deployment
859be87
verified
<html lang="fr"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Paris Invaders - Chassez les aliens dans Paris</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
<style> | |
@keyframes float { | |
0%, 100% { transform: translateY(0); } | |
50% { transform: translateY(-10px); } | |
} | |
.floating { | |
animation: float 3s ease-in-out infinite; | |
} | |
.pulse { | |
animation: pulse 2s infinite; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.1); } | |
100% { transform: scale(1); } | |
} | |
#map { | |
height: 200px; | |
width: 100%; | |
border-radius: 1rem; | |
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
} | |
.radar-scan { | |
height: 200px ; | |
} | |
.alien-icon { | |
filter: drop-shadow(0 0 8px rgba(72, 187, 120, 0.7)); | |
} | |
.radar-scan { | |
position: relative; | |
overflow: hidden; | |
} | |
.radar-scan::after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: linear-gradient( | |
transparent 49%, | |
rgba(72, 187, 120, 0.3) 50%, | |
transparent 51% | |
); | |
animation: radar-beam 4s linear infinite; | |
pointer-events: none; | |
} | |
@keyframes radar-beam { | |
0% { transform: translateY(-100%); } | |
100% { transform: translateY(100%); } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<!-- Header --> | |
<header class="flex flex-col md:flex-row justify-between items-center mb-8"> | |
<div class="flex items-center mb-4 md:mb-0"> | |
<i class="fas fa-robot text-green-400 text-4xl mr-3 floating"></i> | |
<h1 class="text-3xl font-bold bg-gradient-to-r from-green-400 to-blue-500 bg-clip-text text-transparent"> | |
PARIS INVADERS | |
</h1> | |
</div> | |
<div class="flex items-center space-x-4"> | |
<div class="bg-gray-800 px-4 py-2 rounded-full flex items-center"> | |
<i class="fas fa-bolt text-yellow-400 mr-2"></i> | |
<span id="score" class="font-bold">0</span> | |
</div> | |
<div class="bg-gray-800 px-4 py-2 rounded-full flex items-center"> | |
<i class="fas fa-heart text-red-400 mr-2"></i> | |
<span id="lives" class="font-bold">3</span> | |
</div> | |
</div> | |
</header> | |
<!-- Main Game Area --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Radar Section --> | |
<div class="lg:col-span-1 bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<h2 class="text-xl font-bold mb-4 flex items-center"> | |
<i class="fas fa-satellite-dish text-blue-400 mr-2"></i> | |
Radar Alien | |
</h2> | |
<div class="radar-scan bg-gray-900 rounded-lg p-4 mb-4 relative h-64"> | |
<div id="radar-content" class="h-full flex items-center justify-center"> | |
<div class="text-center"> | |
<i class="fas fa-satellite text-blue-400 text-4xl mb-2"></i> | |
<p class="text-gray-400">Recherche d'aliens en cours...</p> | |
</div> | |
</div> | |
</div> | |
<div class="bg-gray-700 rounded-lg p-4"> | |
<div class="flex justify-between items-center mb-2"> | |
<span class="text-gray-300">Progression:</span> | |
<span id="progress" class="font-bold">0%</span> | |
</div> | |
<div class="w-full bg-gray-600 rounded-full h-2.5"> | |
<div id="progress-bar" class="bg-green-500 h-2.5 rounded-full" style="width: 0%"></div> | |
</div> | |
</div> | |
</div> | |
<!-- Map Section --> | |
<div class="lg:col-span-2 bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold flex items-center"> | |
<i class="fas fa-map-marked-alt text-red-400 mr-2"></i> | |
Carte de Paris | |
</h2> | |
<div id="location-status" class="bg-gray-700 px-3 py-1 rounded-full text-sm flex items-center"> | |
<i class="fas fa-spinner fa-spin mr-2"></i> | |
<span>Localisation en cours...</span> | |
</div> | |
</div> | |
<div id="info-messages" class="mb-4 bg-gray-700 rounded-lg p-3 text-sm text-gray-300"> | |
<p>Bienvenue dans Paris Invaders! Utilisez le radar pour détecter les aliens, puis approchez-vous pour les attaquer.</p> | |
</div> | |
<div id="map" class="mb-4"></div> | |
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> | |
<button id="scan-btn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg flex items-center justify-center transition"> | |
<i class="fas fa-satellite mr-2"></i> Scanner | |
</button> | |
<button id="attack-btn" class="bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded-lg flex items-center justify-center transition disabled:opacity-50" disabled> | |
<i class="fas fa-bomb mr-2"></i> Attaquer | |
</button> | |
<button id="shield-btn" class="bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg flex items-center justify-center transition"> | |
<i class="fas fa-shield-alt mr-2"></i> Bouclier | |
</button> | |
<button id="upgrade-btn" class="bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded-lg flex items-center justify-center transition"> | |
<i class="fas fa-level-up-alt mr-2"></i> Améliorer | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Alien Alert --> | |
<div id="alien-alert" class="fixed top-4 left-1/2 transform -translate-x-1/2 bg-red-700 text-white px-6 py-3 rounded-lg shadow-xl hidden items-center z-50 w-11/12 max-w-md text-center"> | |
<i class="fas fa-exclamation-triangle mr-3 text-xl"></i> | |
<span id="alert-message">Alien détecté à proximité!</span> | |
<button class="ml-4 bg-white text-red-700 px-4 py-1 rounded-full font-bold hover:bg-gray-100 transition">Compris</button> | |
</div> | |
<!-- Game Over Modal --> | |
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50"> | |
<div class="bg-gray-800 rounded-xl p-8 max-w-md w-full text-center"> | |
<i class="fas fa-gamepad text-5xl text-red-500 mb-4"></i> | |
<h2 class="text-3xl font-bold mb-2">Game Over</h2> | |
<p class="text-gray-300 mb-6">Vous avez éliminé <span id="final-score" class="text-green-400 font-bold">0</span> aliens!</p> | |
<div class="flex space-x-4 justify-center"> | |
<button id="restart-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg transition"> | |
Rejouer | |
</button> | |
<button id="share-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition"> | |
<i class="fab fa-twitter mr-2"></i>Partager | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Game State | |
const gameState = { | |
score: 0, | |
lives: 3, | |
progress: 0, | |
playerPosition: null, | |
aliens: [], | |
scanActive: false, | |
shieldActive: false, | |
gameActive: true | |
}; | |
// DOM Elements | |
const scoreElement = document.getElementById('score'); | |
const livesElement = document.getElementById('lives'); | |
const progressElement = document.getElementById('progress'); | |
const progressBar = document.getElementById('progress-bar'); | |
const radarContent = document.getElementById('radar-content'); | |
const locationStatus = document.getElementById('location-status'); | |
const scanBtn = document.getElementById('scan-btn'); | |
const attackBtn = document.getElementById('attack-btn'); | |
const shieldBtn = document.getElementById('shield-btn'); | |
const upgradeBtn = document.getElementById('upgrade-btn'); | |
const alienAlert = document.getElementById('alien-alert'); | |
const gameOverModal = document.getElementById('game-over-modal'); | |
const finalScoreElement = document.getElementById('final-score'); | |
const restartBtn = document.getElementById('restart-btn'); | |
const shareBtn = document.getElementById('share-btn'); | |
// Initialize Map with Leaflet | |
let map; | |
let playerMarker; | |
function initMap() { | |
// Create map centered on Paris | |
map = L.map('map').setView([48.8566, 2.3522], 13); | |
// Add OpenStreetMap tiles | |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | |
}).addTo(map); | |
// Try to get real user location | |
if (navigator.geolocation) { | |
navigator.geolocation.watchPosition( | |
position => { | |
const lat = position.coords.latitude; | |
const lng = position.coords.longitude; | |
gameState.playerPosition = { lat, lng }; | |
// Update or create player marker | |
if (playerMarker) { | |
playerMarker.setLatLng([lat, lng]); | |
} else { | |
playerMarker = L.marker([lat, lng], { | |
icon: L.divIcon({ | |
className: 'player-icon', | |
html: '<i class="fas fa-user text-blue-500 text-2xl"></i>', | |
iconSize: [24, 24] | |
}) | |
}).addTo(map); | |
} | |
// Center map on player | |
map.setView([lat, lng], 15); | |
locationStatus.innerHTML = `<i class="fas fa-map-marker-alt text-green-400 mr-2"></i><span>${lat.toFixed(4)}, ${lng.toFixed(4)}</span>`; | |
// Generate aliens if not already done | |
if (gameState.aliens.length === 0) { | |
generateAliens(); | |
} | |
}, | |
error => { | |
console.error("Geolocation error:", error); | |
// Fallback to Paris center if geolocation fails | |
gameState.playerPosition = { lat: 48.8566, lng: 2.3522 }; | |
locationStatus.innerHTML = '<i class="fas fa-map-marker-alt text-yellow-400 mr-2"></i><span>Paris, France (approximé)</span>'; | |
generateAliens(); | |
}, | |
{ | |
enableHighAccuracy: true, | |
maximumAge: 10000, | |
timeout: 5000 | |
} | |
); | |
} else { | |
// Browser doesn't support geolocation | |
gameState.playerPosition = { lat: 48.8566, lng: 2.3522 }; | |
locationStatus.innerHTML = '<i class="fas fa-map-marker-alt text-yellow-400 mr-2"></i><span>Paris, France (approximé)</span>'; | |
generateAliens(); | |
} | |
} | |
// Calculate distance in meters using Haversine formula | |
function calculateDistance(lat1, lon1, lat2, lon2) { | |
const R = 6371e3; // Earth radius in meters | |
const φ1 = lat1 * Math.PI/180; | |
const φ2 = lat2 * Math.PI/180; | |
const Δφ = (lat2-lat1) * Math.PI/180; | |
const Δλ = (lon2-lon1) * Math.PI/180; | |
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + | |
Math.cos(φ1) * Math.cos(φ2) * | |
Math.sin(Δλ/2) * Math.sin(Δλ/2); | |
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); | |
return R * c; | |
} | |
// Move aliens randomly | |
function moveAliens() { | |
if (!gameState.gameActive) return; | |
gameState.aliens.forEach(alien => { | |
if (!alien.defeated) { | |
// Move alien randomly by small amount (about 1-3 meters) | |
const moveLat = (Math.random() * 0.00003 - 0.000015); | |
const moveLng = (Math.random() * 0.00003 - 0.000015); | |
alien.lat += moveLat; | |
alien.lng += moveLng; | |
if (alien.marker) { | |
alien.marker.setLatLng([alien.lat, alien.lng]); | |
} | |
} | |
}); | |
setTimeout(moveAliens, 2000); // Move every 2 seconds | |
} | |
// Generate random aliens in Paris | |
function generateAliens() { | |
const alienTypes = ['green', 'red', 'purple', 'boss']; | |
const alienIcons = { | |
green: 'fa-robot', | |
red: 'fa-bug', | |
purple: 'fa-space-shuttle', | |
boss: 'fa-skull' | |
}; | |
// Clear existing alien markers | |
gameState.aliens.forEach(alien => { | |
if (alien.marker) { | |
map.removeLayer(alien.marker); | |
} | |
}); | |
gameState.aliens = []; | |
for (let i = 0; i < 10; i++) { | |
const type = i === 9 ? 'boss' : alienTypes[Math.floor(Math.random() * 3)]; | |
const lat = gameState.playerPosition.lat + (Math.random() * 0.002 - 0.001); // Closer initial spawn | |
const lng = gameState.playerPosition.lng + (Math.random() * 0.002 - 0.001); | |
const alien = { | |
id: i, | |
type: type, | |
lat: lat, | |
lng: lng, | |
power: type === 'boss' ? 3 : 1, | |
icon: alienIcons[type], | |
color: type === 'green' ? 'text-green-400' : | |
type === 'red' ? 'text-red-400' : | |
type === 'purple' ? 'text-purple-400' : 'text-yellow-400', | |
defeated: false, | |
marker: null | |
}; | |
// Create marker for this alien | |
alien.marker = L.marker([lat, lng], { | |
icon: L.divIcon({ | |
className: 'alien-marker', | |
html: `<i class="fas ${alien.icon} ${alien.color} text-2xl"></i>`, | |
iconSize: [24, 24] | |
}) | |
}).addTo(map); | |
// Add click handler to select alien | |
alien.marker.on('click', () => { | |
selectAlien(alien.id); | |
}); | |
gameState.aliens.push(alien); | |
} | |
updateRadar(); | |
} | |
// Update radar display | |
function updateRadar() { | |
if (!gameState.scanActive) { | |
radarContent.innerHTML = ` | |
<div class="text-center"> | |
<i class="fas fa-satellite text-blue-400 text-4xl mb-2"></i> | |
<p class="text-gray-400">Prêt à scanner</p> | |
</div> | |
`; | |
return; | |
} | |
// Show aliens on radar | |
let radarHTML = '<div class="grid grid-cols-3 gap-4">'; | |
gameState.aliens.forEach(alien => { | |
if (!alien.defeated) { | |
const distance = calculateDistance( | |
gameState.playerPosition.lat, | |
gameState.playerPosition.lng, | |
alien.lat, | |
alien.lng | |
); | |
if (distance < 0.5) { // Only show nearby aliens | |
radarHTML += ` | |
<div class="text-center"> | |
<i class="fas ${alien.icon} ${alien.color} text-3xl mb-1 alien-icon pulse" data-id="${alien.id}"></i> | |
<div class="text-xs ${alien.type === 'boss' ? 'text-yellow-400 font-bold' : 'text-gray-300'}"> | |
${alien.type === 'boss' ? 'BOSS' : 'Alien'} - ${distance.toFixed(2)}km | |
</div> | |
</div> | |
`; | |
} | |
} | |
}); | |
radarHTML += '</div>'; | |
radarContent.innerHTML = radarHTML || ` | |
<div class="text-center"> | |
<i class="fas fa-question-circle text-gray-400 text-4xl mb-2"></i> | |
<p class="text-gray-400">Aucun alien détecté</p> | |
</div> | |
`; | |
// Add event listeners to alien icons | |
document.querySelectorAll('.alien-icon').forEach(icon => { | |
icon.addEventListener('click', function() { | |
const alienId = parseInt(this.getAttribute('data-id')); | |
selectAlien(alienId); | |
}); | |
}); | |
} | |
// Select an alien to attack | |
function selectAlien(id) { | |
const alien = gameState.aliens.find(a => a.id === id); | |
if (!alien || alien.defeated) return; | |
// Check if player is within 5 meters | |
const distance = calculateDistance( | |
gameState.playerPosition.lat, | |
gameState.playerPosition.lng, | |
alien.lat, | |
alien.lng | |
); | |
if (distance > 5) { | |
showAlert("Approchez-vous à moins de 5 mètres de l'alien pour pouvoir l'attaquer."); | |
return; | |
} | |
attackBtn.disabled = false; | |
attackBtn.setAttribute('data-alien-id', id); | |
// Show alert for boss | |
if (alien.type === 'boss') { | |
showAlert("Attention! Un alien BOSS a été détecté!"); | |
} | |
} | |
// Perform attack | |
function attack() { | |
const alienId = parseInt(attackBtn.getAttribute('data-alien-id')); | |
const alien = gameState.aliens.find(a => a.id === alienId); | |
if (!alien) { | |
attackBtn.disabled = true; | |
return; | |
} | |
// Calculate chance to hit (80% base, reduced by alien power) | |
const hitChance = 0.8 - (alien.power * 0.1); | |
const isHit = Math.random() < hitChance; | |
if (isHit) { | |
// Alien is defeated | |
alien.defeated = true; | |
if (alien.marker) { | |
map.removeLayer(alien.marker); | |
} | |
gameState.score += alien.type === 'boss' ? 100 : 10; | |
gameState.progress += alien.type === 'boss' ? 20 : 5; | |
// Update UI | |
scoreElement.textContent = gameState.score; | |
updateProgress(); | |
// Show success message | |
showAlert(`Alien ${alien.type} éliminé! +${alien.type === 'boss' ? 100 : 10} points`); | |
// Check for game completion | |
if (gameState.progress >= 100) { | |
gameState.progress = 100; | |
updateProgress(); | |
endGame(true); | |
} | |
} else { | |
// Alien counter-attacks if it's a boss | |
if (alien.type === 'boss') { | |
gameState.lives -= 2; | |
} else { | |
gameState.lives -= 1; | |
} | |
livesElement.textContent = gameState.lives; | |
showAlert("Attaque ratée! L'alien contre-attaque!"); | |
// Check for game over | |
if (gameState.lives <= 0) { | |
gameState.lives = 0; | |
endGame(false); | |
} | |
} | |
// Reset attack button | |
attackBtn.disabled = true; | |
updateRadar(); | |
} | |
// Activate scan | |
function activateScan() { | |
if (gameState.scanActive) return; | |
gameState.scanActive = true; | |
scanBtn.disabled = true; | |
scanBtn.classList.add('bg-blue-800'); | |
updateRadar(); | |
// Scan lasts for 5 seconds | |
setTimeout(() => { | |
gameState.scanActive = false; | |
scanBtn.disabled = false; | |
scanBtn.classList.remove('bg-blue-800'); | |
updateRadar(); | |
}, 5000); | |
} | |
// Activate shield | |
function activateShield() { | |
if (gameState.shieldActive) return; | |
gameState.shieldActive = true; | |
shieldBtn.disabled = true; | |
shieldBtn.classList.add('bg-purple-800'); | |
showAlert("Bouclier activé! Vous êtes protégé pendant 10 secondes."); | |
// Shield lasts for 10 seconds | |
setTimeout(() => { | |
gameState.shieldActive = false; | |
shieldBtn.disabled = false; | |
shieldBtn.classList.remove('bg-purple-800'); | |
showAlert("Bouclier désactivé."); | |
}, 10000); | |
} | |
// Upgrade weapon | |
function upgradeWeapon() { | |
// In a full game, this would improve attack power | |
showAlert("Arme améliorée! Vos attaques sont plus puissantes maintenant."); | |
} | |
// Update progress | |
function updateProgress() { | |
const progress = Math.min(100, gameState.progress); | |
progressElement.textContent = `${progress}%`; | |
progressBar.style.width = `${progress}%`; | |
} | |
// Show alert message | |
function showAlert(message) { | |
document.getElementById('alert-message').textContent = message; | |
alienAlert.classList.remove('hidden'); | |
// Also show in info messages area | |
const infoDiv = document.getElementById('info-messages'); | |
infoDiv.innerHTML = `<p>${message}</p>`; | |
infoDiv.classList.add('bg-blue-900', 'text-white'); | |
setTimeout(() => { | |
alienAlert.classList.add('hidden'); | |
infoDiv.classList.remove('bg-blue-900', 'text-white'); | |
}, 3000); | |
} | |
// End game | |
function endGame(victory) { | |
gameState.gameActive = false; | |
finalScoreElement.textContent = gameState.score; | |
if (victory) { | |
document.querySelector('#game-over-modal h2').textContent = "Victoire!"; | |
document.querySelector('#game-over-modal i').className = "fas fa-trophy text-5xl text-yellow-400 mb-4"; | |
} | |
gameOverModal.classList.remove('hidden'); | |
} | |
// Restart game | |
function restartGame() { | |
gameState.score = 0; | |
gameState.lives = 3; | |
gameState.progress = 0; | |
gameState.aliens = []; | |
gameState.scanActive = false; | |
gameState.shieldActive = false; | |
gameState.gameActive = true; | |
scoreElement.textContent = '0'; | |
livesElement.textContent = '3'; | |
updateProgress(); | |
gameOverModal.classList.add('hidden'); | |
// Regenerate aliens | |
generateAliens(); | |
} | |
// Share score | |
function shareScore() { | |
const tweetText = `J'ai éliminé ${gameState.score} aliens dans Paris Invaders! Pouvez-vous faire mieux? #ParisInvaders`; | |
window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`, '_blank'); | |
} | |
// Event Listeners | |
document.addEventListener('DOMContentLoaded', initMap); | |
scanBtn.addEventListener('click', activateScan); | |
attackBtn.addEventListener('click', attack); | |
shieldBtn.addEventListener('click', activateShield); | |
upgradeBtn.addEventListener('click', upgradeWeapon); | |
restartBtn.addEventListener('click', restartGame); | |
shareBtn.addEventListener('click', shareScore); | |
alienAlert.querySelector('button').addEventListener('click', () => { | |
alienAlert.classList.add('hidden'); | |
}); | |
</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=jeromegenevray/jerome" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |