jerome / index.html
jeromegenevray's picture
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
<!DOCTYPE html>
<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 !important;
}
.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: '&copy; <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.cos1) * Math.cos2) *
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>