Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tron-Style Explorer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| overflow: hidden; | |
| background: linear-gradient(to bottom, #000428, #00090f); | |
| color: #00f7ff; | |
| } | |
| #gameContainer { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #gameCanvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #uiOverlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| } | |
| .glow-text { | |
| text-shadow: 0 0 10px #00f7ff, 0 0 20px #00f7ff; | |
| } | |
| .building-icon { | |
| border: 2px solid #008cff; | |
| background-color: rgba(0, 40, 80, 0.7); | |
| box-shadow: 0 0 15px rgba(0, 136, 255, 0.7); | |
| transition: all 0.3s ease; | |
| } | |
| .building-icon.active { | |
| box-shadow: 0 0 25px rgba(0, 195, 255, 0.9); | |
| transform: scale(1.1); | |
| } | |
| .neon-border { | |
| border: 1px solid #008cff; | |
| box-shadow: 0 0 10px rgba(0, 140, 255, 0.8); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-black"> | |
| <div id="gameContainer"> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="uiOverlay"> | |
| <!-- Top UI Section --> | |
| <div class="p-4"> | |
| <div class="flex justify-between"> | |
| <h1 class="text-3xl md:text-4xl font-bold glow-text">WEB3 DISTRICT</h1> | |
| <div class="flex space-x-4"> | |
| <div class="bg-black bg-opacity-50 neon-border p-3 rounded-lg"> | |
| <h3 class="text-sm font-semibold mb-1">TOP SCORES</h3> | |
| <div class="text-xs space-y-1"> | |
| <div id="score1">1. ---</div> | |
| <div id="score2">2. ---</div> | |
| <div id="score3">3. ---</div> | |
| </div> | |
| </div> | |
| <div class="bg-black bg-opacity-50 neon-border p-3 rounded-lg"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-3 h-3 rounded-full bg-green-500 animate-pulse"></div> | |
| <span>SCORE: <span id="score">0</span></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Building Discovery Indicator - Left Side --> | |
| <div class="absolute left-8 top-1/2 transform -translate-y-1/2"> | |
| <div class="bg-black bg-opacity-70 neon-border rounded-lg p-4"> | |
| <h2 class="text-lg font-semibold text-center mb-2">BUILDING DISCOVERY</h2> | |
| <div class="flex flex-col space-y-3"> | |
| <div class="building-icon w-12 h-12 rounded-lg flex items-center justify-center">1</div> | |
| <div class="building-icon w-12 h-12 rounded-lg flex items-center justify-center">2</div> | |
| <div class="building-icon w-12 h-12 rounded-lg flex items-center justify-center">3</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bottom Control UI --> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-end"> | |
| <div class="flex-1"></div> <!-- Spacer --> | |
| <div class="bg-black bg-opacity-50 neon-border p-4 rounded-lg w-48 mr-8"> | |
| <h2 class="text-lg font-semibold mb-2">EXPLORED</h2> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span>BUILDING 1:</span> | |
| <span id="building1">❌</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>BUILDING 2:</span> | |
| <span id="building2">❌</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>BUILDING 3:</span> | |
| <span id="building3">❌</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-black bg-opacity-50 neon-border p-4 rounded-lg w-48"> | |
| <h2 class="text-lg font-semibold mb-2">STATUS</h2> | |
| <div> | |
| <p id="status">Exploring Grid...</p> | |
| <div class="w-full bg-gray-800 rounded-full h-2.5 mt-2"> | |
| <div id="energyBar" class="bg-blue-600 h-2.5 rounded-full" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game initialization | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Create the game state | |
| const gameState = { | |
| score: 0, | |
| buildingsExplored: [false, false, false], | |
| exploreTimes: [0, 0, 0], // Track when buildings were found | |
| startTime: Date.now(), | |
| playerPosition: { x: 0, z: 0 }, | |
| energy: 100, | |
| buildings: [ | |
| { x: 20, z: 20, radius: 5, id: 1 }, | |
| { x: -15, z: 30, radius: 7, id: 2 }, | |
| { x: 0, z: -25, radius: 6, id: 3 } | |
| ], | |
| obstacles: [], | |
| obstacleSpeed: 0.05, | |
| gameActive: true, | |
| obstaclesActive: false | |
| }; | |
| // Time-based scoring factors | |
| const maxTimeScore = 300; // Max bonus for speed | |
| const baseScore = 100; // Base score per building | |
| // Update the UI | |
| function updateUI() { | |
| document.getElementById('score').textContent = gameState.score; | |
| document.getElementById('energyBar').style.width = `${gameState.energy}%`; | |
| const statusEl = document.getElementById('status'); | |
| // Update building exploration status | |
| document.getElementById('building1').textContent = gameState.buildingsExplored[0] ? '✅' : '❌'; | |
| document.getElementById('building2').textContent = gameState.buildingsExplored[1] ? '✅' : '❌'; | |
| document.getElementById('building3').textContent = gameState.buildingsExplored[2] ? '❌' : '❌'; | |
| // Check if all buildings are found | |
| if (gameState.buildingsExplored.every(b => b) && gameState.gameActive) { | |
| gameState.gameActive = false; | |
| statusEl.textContent = 'CONGRATULATIONS! You discovered all buildings!'; | |
| setTimeout(() => { | |
| const celebration = document.createElement('div'); | |
| celebration.innerHTML = ` | |
| <div class="absolute inset-0 bg-black bg-opacity-80 flex items-center justify-center"> | |
| <div class="text-center p-10 bg-gray-900 rounded-lg neon-border max-w-md"> | |
| <h2 class="text-3xl font-bold mb-6 glow-text">MISSION COMPLETE!</h2> | |
| <p class="mb-6 text-lg">All WEB3 Districts discovered!<br>Final Score: ${gameState.score}</p> | |
| <div class="glow-text text-4xl mb-6">🎉✨</div> | |
| <div class="mb-4 text-sm text-gray-400">Press ENTER to play again</div> | |
| <button id="restartBtn" class="bg-blue-700 hover:bg-blue-600 text-white font-bold py-3 px-8 rounded-lg transition-all transform hover:scale-105"> | |
| PLAY AGAIN | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| document.getElementById('uiOverlay').appendChild(celebration); | |
| // Add confetti effect | |
| const confetti = document.createElement('div'); | |
| confetti.id = 'confetti'; | |
| confetti.className = 'fixed inset-0 pointer-events-none'; | |
| document.body.appendChild(confetti); | |
| }, 1000); | |
| } | |
| // Check player distance to buildings | |
| let nearBuilding = false; | |
| gameState.buildings.forEach((building, index) => { | |
| const distance = Math.sqrt( | |
| Math.pow(gameState.playerPosition.x - building.x, 2) + | |
| Math.pow(gameState.playerPosition.z - building.z, 2) | |
| ); | |
| if (distance < building.radius) { | |
| if (!gameState.buildingsExplored[index]) { | |
| gameState.buildingsExplored[index] = true; | |
| gameState.exploreTimes[index] = Date.now(); | |
| // Calculate score with time bonus (faster = more points) | |
| const timeTaken = (Date.now() - gameState.startTime) / 1000; | |
| const timeBonus = Math.max(0, maxTimeScore - timeTaken); | |
| const buildingScore = baseScore + Math.floor(timeBonus); | |
| gameState.score += buildingScore; | |
| gameState.energy = Math.min(gameState.energy + 20, 100); | |
| statusEl.textContent = `Discovered ${building.name}! +${buildingScore} points`; | |
| updateLeaderboard(gameState.score); | |
| } | |
| nearBuilding = true; | |
| const icons = document.querySelectorAll('.building-icon'); | |
| icons[index].classList.add('active'); | |
| } else { | |
| const icons = document.querySelectorAll('.building-icon'); | |
| icons[index].classList.remove('active'); | |
| } | |
| }); | |
| // Energy depletion | |
| if (gameState.energy > 0 && gameState.gameActive) { | |
| gameState.energy -= 0.05; | |
| } | |
| // End game condition | |
| if (gameState.energy <= 0) { | |
| gameState.gameActive = false; | |
| statusEl.textContent = 'GAME OVER - All energy depleted!'; | |
| document.getElementById('energyBar').classList.add('bg-red-500'); | |
| // Show restart button | |
| setTimeout(() => { | |
| const restartBtn = document.createElement('div'); | |
| restartBtn.innerHTML = ` | |
| <div class="absolute inset-0 bg-black bg-opacity-80 flex items-center justify-center"> | |
| <div class="text-center p-10 bg-gray-900 rounded-lg neon-border max-w-md"> | |
| <h2 class="text-2xl font-bold mb-4 glow-text">GAME OVER</h2> | |
| <p class="mb-4">Final Score: ${gameState.score}</p> | |
| <div class="mb-4 text-sm text-gray-400">Press ENTER to play again</div> | |
| <button id="restartBtn" class="bg-blue-700 hover:bg-blue-600 text-white font-bold py-2 px-6 rounded-lg transition-all transform hover:scale-105"> | |
| PLAY AGAIN | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| document.getElementById('uiOverlay').appendChild(restartBtn); | |
| }, 1500); | |
| } | |
| } | |
| // Set up Three.js scene | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x000010); | |
| scene.fog = new THREE.Fog(0x000022, 15, 50); | |
| // Create camera | |
| const camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| camera.position.set(0, 10, -15); | |
| camera.lookAt(0, 0, 0); | |
| // Create renderer | |
| const renderer = new THREE.WebGLRenderer({ | |
| canvas: document.getElementById('gameCanvas'), | |
| antialias: true | |
| }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(window.devicePixelRatio); | |
| // Create Tron-style infinite grid | |
| function createGrid() { | |
| // Create main grid geometry | |
| const gridSize = 100; | |
| const divisions = 50; | |
| const gridHelper = new THREE.GridHelper(gridSize*2, divisions, 0x00ffff, 0x00ffff); | |
| gridHelper.position.y = 0.01; // Slightly above floor to avoid z-fighting | |
| scene.add(gridHelper); | |
| // Make grid lines glow | |
| gridHelper.material.opacity = 0.3; | |
| gridHelper.material.transparent = true; | |
| gridHelper.material.linewidth = 3; | |
| // Create flicker effect | |
| const speed = 2; | |
| let elapsedTime = 0; | |
| function updateGrid(delta) { | |
| elapsedTime += delta; | |
| const flicker = Math.sin(elapsedTime * speed) * 0.2 + 0.8; | |
| gridHelper.material.opacity = 0.3 * flicker; | |
| } | |
| return { update: updateGrid }; | |
| } | |
| // Create futuristic character as a pink glowing sphere | |
| function createPlayer() { | |
| const player = new THREE.Group(); | |
| // Main pink sphere | |
| const sphereGeo = new THREE.SphereGeometry(1.2, 32, 32); | |
| const sphereMat = new THREE.MeshStandardMaterial({ | |
| color: 0xff00ff, // More magenta/fuccia color | |
| emissive: 0xff00ff, | |
| emissiveIntensity: 1.5, | |
| roughness: 0.1, | |
| metalness: 0.9 | |
| }); | |
| const sphere = new THREE.Mesh(sphereGeo, sphereMat); | |
| player.add(sphere); | |
| // Add triangle wing/direction indicator | |
| const wingGeo = new THREE.ConeGeometry(0.5, 1.5, 3); | |
| const wingMat = new THREE.MeshStandardMaterial({ | |
| color: 0x00ffff, | |
| emissive: 0x00ffff, | |
| emissiveIntensity: 0.8 | |
| }); | |
| const wing = new THREE.Mesh(wingGeo, wingMat); | |
| wing.position.z = -1.2; | |
| wing.rotation.x = Math.PI/2; | |
| player.add(wing); | |
| // Glow effect with point light | |
| const pointLight = new THREE.PointLight(0xff88ee, 1, 10); | |
| pointLight.position.set(0, 0, 0); | |
| player.add(pointLight); | |
| // Pulsing light effect | |
| const pulseSpeed = 5; | |
| let pulseTime = 0; | |
| function updatePulse(delta) { | |
| pulseTime += delta; | |
| const pulseIntensity = 0.5 + Math.sin(pulseTime * pulseSpeed) * 0.5; | |
| pointLight.intensity = 1 + pulseIntensity; | |
| sphereMat.emissiveIntensity = 1 + pulseIntensity * 0.5; | |
| } | |
| return { mesh: player, update: updatePulse }; | |
| } | |
| // Create buildings | |
| function createBuildings() { | |
| // Building 1 | |
| const building1 = new THREE.Mesh( | |
| new THREE.BoxGeometry(8, 10, 8), | |
| new THREE.MeshBasicMaterial({ color: 0x0088ff, wireframe: true }) | |
| ); | |
| building1.position.set(20, 5, 20); | |
| // Building 2 | |
| const building2 = new THREE.Mesh( | |
| new THREE.CylinderGeometry(6, 6, 12, 16), | |
| new THREE.MeshBasicMaterial({ color: 0x00aaff, wireframe: true }) | |
| ); | |
| building2.position.set(-15, 6, 30); | |
| // Building 3 | |
| const pyramidGeometry = new THREE.ConeGeometry(5, 12, 4); | |
| const building3 = new THREE.Mesh( | |
| pyramidGeometry, | |
| new THREE.MeshBasicMaterial({ color: 0x0088ee, wireframe: true }) | |
| ); | |
| building3.rotation.y = Math.PI / 4; | |
| building3.position.set(0, 6, -25); | |
| scene.add(building1); | |
| scene.add(building2); | |
| scene.add(building3); | |
| return [building1, building2, building3]; | |
| } | |
| // Create visual effects and obstacles | |
| function createEffects() { | |
| // Create ambient light | |
| const ambientLight = new THREE.AmbientLight(0x0088ff, 0.2); | |
| scene.add(ambientLight); | |
| // Create directional light | |
| const dirLight = new THREE.DirectionalLight(0x00ffff, 0.8); | |
| dirLight.position.set(0, 30, 0); | |
| scene.add(dirLight); | |
| // Create obstacles (blue cubes that turn red later) | |
| for (let i = 0; i < 5; i++) { | |
| const obstacle = new THREE.Mesh( | |
| new THREE.BoxGeometry(2, 2, 2), | |
| new THREE.MeshBasicMaterial({ color: 0x0000ff }) | |
| ); | |
| obstacle.position.set( | |
| Math.random() * 80 - 40, | |
| 1, | |
| Math.random() * 80 - 40 | |
| ); | |
| scene.add(obstacle); | |
| gameState.obstacles.push({ | |
| mesh: obstacle, | |
| isActive: false, | |
| moveDirection: { | |
| x: Math.random() * 2 - 1, | |
| z: Math.random() * 2 - 1 | |
| } | |
| }); | |
| } | |
| // Add some grid markers | |
| for (let i = 0; i < 20; i++) { | |
| const size = 0.5 + Math.random() * 1.5; | |
| const marker = new THREE.Mesh( | |
| new THREE.BoxGeometry(size, size, size), | |
| new THREE.MeshBasicMaterial({ color: 0x00ffff }) | |
| ); | |
| marker.position.set( | |
| Math.random() * 60 - 30, | |
| 1, | |
| Math.random() * 60 - 30 | |
| ); | |
| scene.add(marker); | |
| } | |
| } | |
| // Movement system - mouse click and drag | |
| let isDragging = false; | |
| let dragStartX = 0; | |
| let dragStartY = 0; | |
| let currentDragX = 0; | |
| let currentDragY = 0; | |
| // Mouse event handlers for virtual joystick | |
| document.addEventListener('mousedown', (e) => { | |
| isDragging = true; | |
| dragStartX = e.clientX; | |
| dragStartY = e.clientY; | |
| currentDragX = 0; | |
| currentDragY = 0; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (!isDragging) return; | |
| currentDragX = e.clientX - dragStartX; | |
| currentDragY = e.clientY - dragStartY; | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| isDragging = false; | |
| currentDragX = 0; | |
| currentDragY = 0; | |
| }); | |
| function handleMovement(player) { | |
| if (!isDragging) return; | |
| const moveSpeed = gameState.obstaclesActive ? 0.25 : 0.2; | |
| const sensitivity = 0.01; | |
| const maxCameraOffset = 5; | |
| // Calculate movement direction from mouse drag (natural joystick) | |
| player.position.x -= currentDragX * sensitivity; | |
| player.position.z -= currentDragY * sensitivity; | |
| // Calculate normalized drag vector | |
| const dragMagnitude = Math.sqrt(currentDragX * currentDragX + currentDragY * currentDragY); | |
| const normalizedX = dragMagnitude > 0 ? currentDragX / dragMagnitude : 0; | |
| const normalizedY = dragMagnitude > 0 ? currentDragY / dragMagnitude : 0; | |
| // Calculate camera offset based on drag direction (magnitude capped) | |
| const dragRatio = Math.min(dragMagnitude / 100, 1); // Normalize to 0-1 range | |
| const cameraOffsetX = -normalizedX * maxCameraOffset * dragRatio; | |
| const cameraOffsetZ = -normalizedY * maxCameraOffset * dragRatio - 15; // -15 is base distance | |
| // Set camera position with smooth offset from player | |
| camera.position.x = player.position.x + cameraOffsetX; | |
| camera.position.z = player.position.z + cameraOffsetZ; | |
| camera.position.y = 8; | |
| // Camera looks slightly ahead in the direction of movement | |
| const lookAheadX = player.position.x + cameraOffsetX * 0.5; | |
| const lookAheadZ = player.position.z + cameraOffsetZ * 0.5; | |
| camera.lookAt(lookAheadX, player.position.y + 1, lookAheadZ); | |
| // Update game state with player position | |
| gameState.playerPosition = { | |
| x: player.position.x, | |
| z: player.position.z | |
| }; | |
| } | |
| // Create the scene objects | |
| createGrid(); | |
| createEffects(); | |
| const player = createPlayer(); | |
| scene.add(player.mesh); | |
| const buildings = createBuildings(); | |
| // Leaderboard functionality | |
| let leaderboard = JSON.parse(localStorage.getItem('leaderboard')) || [0, 0, 0]; | |
| function updateLeaderboard(newScore) { | |
| leaderboard.push(newScore); | |
| leaderboard = leaderboard.sort((a,b) => b-a).slice(0, 3); | |
| localStorage.setItem('leaderboard', JSON.stringify(leaderboard)); | |
| // Update UI | |
| document.getElementById('score1').textContent = `1. ${leaderboard[0] || '---'}`; | |
| document.getElementById('score2').textContent = `2. ${leaderboard[1] || '---'}`; | |
| document.getElementById('score3').textContent = `3. ${leaderboard[2] || '---'}`; | |
| } | |
| // Initialize leaderboard display | |
| updateLeaderboard(0); | |
| // Reset game state | |
| function resetGame() { | |
| // Remove end-game overlays | |
| document.querySelectorAll('#uiOverlay div > div').forEach(el => { | |
| if (el.textContent.includes('GAME OVER') || el.textContent.includes('MISSION COMPLETE')) { | |
| el.remove(); | |
| } | |
| }); | |
| // Remove confetti if exists | |
| const confetti = document.getElementById('confetti'); | |
| if (confetti) confetti.remove(); | |
| // Reset game state | |
| gameState.score = 0; | |
| gameState.buildingsExplored = [false, false, false]; | |
| gameState.exploreTimes = [0, 0, 0]; | |
| gameState.startTime = Date.now(); | |
| gameState.energy = 100; | |
| gameState.gameActive = true; | |
| gameState.obstaclesActive = false; | |
| // Reset UI elements | |
| document.getElementById('energyBar').classList.remove('bg-red-500'); | |
| document.getElementById('energyBar').style.width = '100%'; | |
| document.getElementById('status').textContent = 'Exploring Grid...'; | |
| document.getElementById('score').textContent = '0'; | |
| document.getElementById('building1').textContent = '❌'; | |
| document.getElementById('building2').textContent = '❌'; | |
| document.getElementById('building3').textContent = '❌'; | |
| document.querySelectorAll('.building-icon').forEach(icon => | |
| icon.classList.remove('active') | |
| ); | |
| // Reset player position and camera | |
| if (player && player.mesh) { | |
| player.mesh.position.set(0, 1, 0); | |
| camera.position.set(0, 10, -15); | |
| camera.lookAt(0, 0, 0); | |
| } | |
| // Reset obstacles positions and colors | |
| gameState.obstacles.forEach(obstacle => { | |
| obstacle.mesh.position.set( | |
| Math.random() * 80 - 40, | |
| 1, | |
| Math.random() * 80 - 40 | |
| ); | |
| obstacle.mesh.material.color.setHex(0x0000ff); | |
| obstacle.isActive = false; | |
| }); | |
| // Remove any existing restart buttons | |
| document.querySelectorAll('#restartBtn').forEach(btn => btn.remove()); | |
| // Restart animation loop | |
| requestAnimationFrame(animate); | |
| } | |
| // Add restart handler for Enter key only | |
| document.addEventListener('keydown', (event) => { | |
| if (event.key === 'Enter' && !gameState.gameActive) { | |
| resetGame(); | |
| } | |
| }); | |
| // Animation and game loop | |
| function animateObstacles() { | |
| if (!gameState.obstaclesActive && Date.now() - gameState.startTime > 20000) { // After 20sec | |
| gameState.obstaclesActive = true; | |
| gameState.obstacles.forEach(obstacle => { | |
| obstacle.mesh.material.color.setHex(0xff0000); | |
| obstacle.isActive = true; | |
| }); | |
| } | |
| if (gameState.obstaclesActive) { | |
| gameState.obstacles.forEach(obstacle => { | |
| // Move obstacles toward player but at half speed | |
| const direction = { | |
| x: (gameState.playerPosition.x - obstacle.mesh.position.x) * gameState.obstacleSpeed, | |
| z: (gameState.playerPosition.z - obstacle.mesh.position.z) * gameState.obstacleSpeed | |
| }; | |
| obstacle.mesh.position.x += direction.x; | |
| obstacle.mesh.position.z += direction.z; | |
| // Check collision with player | |
| const distance = Math.sqrt( | |
| Math.pow(gameState.playerPosition.x - obstacle.mesh.position.x, 2) + | |
| Math.pow(gameState.playerPosition.z - obstacle.mesh.position.z, 2) | |
| ); | |
| if (distance < 2 && gameState.gameActive) { | |
| gameState.energy -= 10; // Hit by obstacle | |
| } | |
| }); | |
| } | |
| } | |
| function animate() { | |
| if (gameState.gameActive) { | |
| requestAnimationFrame(animate); | |
| // Handle player movement and pulse effect | |
| handleMovement(player.mesh); | |
| animateObstacles(); | |
| if (player.update) { | |
| player.update(0.016); // Approximate delta time | |
| } | |
| // Animate buildings with a slight pulsation | |
| const time = Date.now() * 0.001; | |
| buildings.forEach(building => { | |
| building.material.color.setRGB( | |
| 0, | |
| 0.5 + Math.sin(time) * 0.2, | |
| 1.0 | |
| ); | |
| }); | |
| // Update UI state | |
| updateUI(); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| const grid = createGrid(); | |
| animate(); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| }); | |
| </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=web3district/web3discoverygame" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |