Spaces:
Running
Running
| <html lang="it"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Navigatore Italia</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/> | |
| <script src="https://unpkg.com/@babylonjs/core@latest"></script> | |
| <script src="https://unpkg.com/@babylonjs/loaders@latest"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet"/> | |
| <style> | |
| #map-2d { | |
| height: 100vh; | |
| width: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| z-index: 1; | |
| } | |
| #map-3d { | |
| height: 100vh; | |
| width: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| z-index: 0; | |
| visibility: hidden; | |
| } | |
| .search-suggestion { | |
| @apply p-2 hover:bg-gray-100 cursor-pointer text-sm border-b border-gray-200 last:border-b-0; | |
| } | |
| .voice-bubble { | |
| @apply bg-blue-600 text-white px-3 py-1 rounded-full text-xs animate-pulse; | |
| } | |
| .route-step { | |
| @apply p-2 border-b border-gray-100 text-sm; | |
| } | |
| .route-step:last-child { | |
| @apply border-b-0; | |
| } | |
| </style> | |
| </head> | |
| <body class="font-sans bg-gray-50 relative overflow-hidden"> | |
| <!-- 3D Map Container (Babylon.js) --> | |
| <div id="map-3d"></div> | |
| <!-- 2D Map Container (Leaflet) --> | |
| <div id="map-2d"></div> | |
| <!-- Top Navigation Bar --> | |
| <div class="absolute top-0 left-0 w-full z-50 p-4 flex justify-between items-center bg-gradient-to-b from-black/70 to-transparent text-white"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="bg-blue-500 p-2 rounded-full"> | |
| <i class="fas fa-map-marked-alt text-white"></i> | |
| </div> | |
| <h1 class="text-lg font-bold">Navigatore Italia</h1> | |
| </div> | |
| <button id="view-toggle" class="bg-white/20 backdrop-blur-sm p-2 rounded-full"> | |
| <i class="fas fa-cube text-white"></i> | |
| </button> | |
| </div> | |
| <!-- Search Bar --> | |
| <div class="absolute top-20 left-1/2 transform -translate-x-1/2 w-11/12 z-50 bg-white rounded-full shadow-lg overflow-hidden"> | |
| <div class="flex items-center p-2"> | |
| <i class="fas fa-search text-gray-400 ml-3"></i> | |
| <input | |
| type="text" | |
| id="search-input" | |
| placeholder="Cerca città, indirizzo..." | |
| class="flex-grow p-2 outline-none text-sm" | |
| /> | |
| </div> | |
| <!-- Search Suggestions --> | |
| <div id="search-suggestions" class="hidden bg-white rounded-b-full shadow-lg max-h-60 overflow-y-auto"> | |
| <!-- Dynamically populated --> | |
| </div> | |
| </div> | |
| <!-- Bottom Panel --> | |
| <div id="bottom-panel" class="absolute bottom-0 left-0 w-full bg-white rounded-t-2xl shadow-lg p-4 transform translate-y-full transition-transform duration-300 z-50"> | |
| <div class="w-12 h-1 bg-gray-300 rounded-full mx-auto mb-4"></div> | |
| <!-- Current Location --> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-green-500 p-2 rounded-full mr-3"> | |
| <i class="fas fa-location-arrow text-white transform rotate-45"></i> | |
| </div> | |
| <div> | |
| <p class="text-xs text-gray-500">Posizione attuale</p> | |
| <p id="current-location" class="font-medium">Rilevamento posizione...</p> | |
| </div> | |
| </div> | |
| <!-- Route Instructions --> | |
| <div id="route-instructions" class="hidden"> | |
| <h3 class="font-bold mb-2">Istruzioni</h3> | |
| <div id="route-steps" class="space-y-1"> | |
| <!-- Dynamically added --> | |
| </div> | |
| </div> | |
| <!-- Start Navigation Button --> | |
| <button id="start-navigation" class="hidden mt-4 bg-blue-600 text-white w-full py-3 rounded-xl font-bold shadow-lg hover:bg-blue-700 transition"> | |
| <i class="fas fa-car mr-2"></i> Inizia Navigazione | |
| </button> | |
| </div> | |
| <!-- Voice Guidance Bubble --> | |
| <div class="absolute top-32 right-5 z-50 hidden" id="voice-bubble"> | |
| <div class="voice-bubble px-4 py-2 rounded-full max-w-xs"> | |
| <i class="fas fa-volume-up mr-1"></i> <span id="voice-text">Preparati a girare</span> | |
| </div> | |
| </div> | |
| <!-- Compass & Speed --> | |
| <div class="absolute top-16 left-5 z-50 bg-white/90 backdrop-blur-sm p-2 rounded-full shadow hidden" id="compass-speed"> | |
| <div class="text-center"> | |
| <div class="text-xs font-bold" id="speed-value">0</div> | |
| <div class="text-xs text-gray-600">km/h</div> | |
| </div> | |
| </div> | |
| <script> | |
| // Simulated Italian locations for search | |
| const italianLocations = [ | |
| "Roma, Italia", | |
| "Milano, Italia", | |
| "Napoli, Italia", | |
| "Torino, Italia", | |
| "Palermo, Italia", | |
| "Genova, Italia", | |
| "Bologna, Italia", | |
| "Firenze, Italia", | |
| "Bari, Italia", | |
| "Catania, Italia", | |
| "Venezia, Italia", | |
| "Verona, Italia", | |
| "Messina, Italia", | |
| "Padova, Italia", | |
| "Trieste, Italia", | |
| "Brescia, Italia", | |
| "Taranto, Italia", | |
| "Prato, Italia", | |
| "Reggio Calabria, Italia", | |
| "Modena, Italia", | |
| "Via Garibaldi, Roma", | |
| "Piazza del Duomo, Milano", | |
| "Costiera Amalfitana, Salerno", | |
| "Lago di Garda, Lombardia", | |
| "Cinque Terre, La Spezia", | |
| "Valle d'Aosta", | |
| "Dolomiti, Trentino", | |
| "Sicilia, Catania", | |
| "Sardegna, Cagliari", | |
| "Toscana, Siena" | |
| ]; | |
| // Initialize Leaflet Map (2D) | |
| const map2d = L.map('map-2d').setView([41.9028, 12.4964], 6); // Center on Italy | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | |
| }).addTo(map2d); | |
| // Initialize Babylon.js (3D) | |
| const canvas = document.getElementById("map-3d"); | |
| const engine = new BABYLON.Engine(canvas, true); | |
| const createScene = function () { | |
| const scene = new BABYLON.Scene(engine); | |
| scene.clearColor = new BABYLON.Color3(0.8, 0.9, 1); | |
| const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 20, new BABYLON.Vector3(0, 0, 0), scene); | |
| camera.useAutoRotationBehavior = true; | |
| camera.autoRotationBehavior.idleRotationSpeed = 0.01; | |
| camera.autoRotationBehavior.zoomStopsAnimation = false; | |
| camera.panningSensibility = 0; | |
| camera.lowerRadiusLimit = 5; | |
| camera.upperRadiusLimit = 50; | |
| camera.attachControl(canvas, true); | |
| const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene); | |
| light.intensity = 0.7; | |
| // Create Italy-like 3D terrain | |
| const ground = BABYLON.MeshBuilder.CreateGroundFromHeightMap( | |
| "gdHM", | |
| "https://www.babylonjs-playground.com/textures/heightMap.png", | |
| {width: 20, height: 20, subdivisions: 100, minHeight: 0, maxHeight: 10, onReady: function(mesh) { | |
| mesh.position.y = -5; | |
| const material = new BABYLON.StandardMaterial("mat", scene); | |
| material.diffuseTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/italy-map.jpg", scene); | |
| mesh.material = material; | |
| }}, | |
| scene | |
| ); | |
| // Create some random 3D buildings to represent cities | |
| const colors = [new BABYLON.Color3(0.8, 0.4, 0.4), new BABYLON.Color3(0.6, 0.6, 0.8), new BABYLON.Color3(0.9, 0.7, 0.5)]; | |
| const cities = [ | |
| { name: "Roma", x: 0, z: 0, height: 2 }, | |
| { name: "Milano", x: 3, z: -2, height: 1.8 }, | |
| { name: "Napoli", x: -2, z: 3, height: 1.6 }, | |
| { name: "Torino", x: 4, z: -4, height: 1.4 }, | |
| { name: "Palermo", x: -6, z: 6, height: 1.2 } | |
| ]; | |
| cities.forEach(city => { | |
| const box = BABYLON.MeshBuilder.CreateBox("city", {width: 0.5, depth: 0.5, height: city.height}, scene); | |
| box.position = new BABYLON.Vector3(city.x, city.height/2, city.z); | |
| const mat = new BABYLON.StandardMaterial("mat", scene); | |
| mat.diffuseColor = colors[Math.floor(Math.random() * colors.length)]; | |
| box.material = mat; | |
| }); | |
| return scene; | |
| }; | |
| const scene = createScene(); | |
| engine.runRenderLoop(function () { | |
| scene.render(); | |
| }); | |
| window.addEventListener("resize", function () { | |
| engine.resize(); | |
| }); | |
| // UI Interactions | |
| const searchInput = document.getElementById('search-input'); | |
| const searchSuggestions = document.getElementById('search-suggestions'); | |
| const bottomPanel = document.getElementById('bottom-panel'); | |
| const startNavigationBtn = document.getElementById('start-navigation'); | |
| const routeInstructions = document.getElementById('route-instructions'); | |
| const routeSteps = document.getElementById('route-steps'); | |
| const currentLocation = document.getElementById('current-location'); | |
| const viewToggle = document.getElementById('view-toggle'); | |
| const map3d = document.getElementById('map-3d'); | |
| const voiceBubble = document.getElementById('voice-bubble'); | |
| const voiceText = document.getElementById('voice-text'); | |
| const compassSpeed = document.getElementById('compass-speed'); | |
| const speedValue = document.getElementById('speed-value'); | |
| // Toggle between 2D and 3D views | |
| viewToggle.addEventListener('click', () => { | |
| if (map3d.style.visibility === 'visible') { | |
| map3d.style.visibility = 'hidden'; | |
| document.getElementById('map-2d').style.zIndex = '1'; | |
| viewToggle.innerHTML = '<i class="fas fa-cube text-white"></i>'; | |
| } else { | |
| map3d.style.visibility = 'visible'; | |
| document.getElementById('map-2d').style.zIndex = '0'; | |
| viewToggle.innerHTML = '<i class="fas fa-map text-white"></i>'; | |
| } | |
| }); | |
| // Search input handling | |
| searchInput.addEventListener('input', () => { | |
| const query = searchInput.value.toLowerCase(); | |
| if (query.length > 2) { | |
| const matches = italianLocations.filter(loc => loc.toLowerCase().includes(query)); | |
| if (matches.length > 0) { | |
| searchSuggestions.innerHTML = ''; | |
| matches.forEach(match => { | |
| const div = document.createElement('div'); | |
| div.className = 'search-suggestion'; | |
| div.textContent = match; | |
| div.addEventListener('click', () => { | |
| searchInput.value = match; | |
| searchSuggestions.classList.add('hidden'); | |
| // Simulate setting destination | |
| setDestination(match); | |
| }); | |
| searchSuggestions.appendChild(div); | |
| }); | |
| searchSuggestions.classList.remove('hidden'); | |
| } else { | |
| searchSuggestions.classList.add('hidden'); | |
| } | |
| } else { | |
| searchSuggestions.classList.add('hidden'); | |
| } | |
| }); | |
| // Close suggestions when clicking outside | |
| document.addEventListener('click', (e) => { | |
| if (!e.target.closest('.search-suggestion') && e.target !== searchInput) { | |
| searchSuggestions.classList.add('hidden'); | |
| } | |
| }); | |
| // Set destination and show route | |
| function setDestination(destination) { | |
| // Animate up the bottom panel | |
| bottomPanel.classList.remove('translate-y-full'); | |
| // Simulate current position | |
| currentLocation.textContent = "Roma, Via Nazionale"; | |
| // Show start navigation button and simulate route | |
| startNavigationBtn.classList.remove('hidden'); | |
| // Simulate some route steps | |
| const steps = [ | |
| "Parti da Via Nazionale verso ovest", | |
| "Svolta a sinistra su Via Cavour", | |
| "Continua su Via Nazionale per 500m", | |
| "Svolta a destra su Via XX Settembre", | |
| "Continua dritto per 1.2 km", | |
| "Destinazione raggiunta" | |
| ]; | |
| routeSteps.innerHTML = ''; | |
| steps.forEach(step => { | |
| const stepDiv = document.createElement('div'); | |
| stepDiv.className = 'route-step'; | |
| stepDiv.textContent = step; | |
| routeSteps.appendChild(stepDiv); | |
| }); | |
| routeInstructions.classList.remove('hidden'); | |
| } | |
| // Start navigation simulation | |
| startNavigationBtn.addEventListener('click', () => { | |
| routeInstructions.classList.add('hidden'); | |
| startNavigationBtn.classList.add('hidden'); | |
| // Show voice bubble and speed | |
| voiceBubble.classList.remove('hidden'); | |
| compassSpeed.classList.remove('hidden'); | |
| // Simulate voice guidance | |
| const voiceMessages = [ | |
| "Preparati a girare a sinistra su Via Cavour", | |
| "Continua dritto per 500 metri", | |
| "Tra 300 metri, svolta a destra", | |
| "Destinazione sulla destra", | |
| "Hai raggiunto la destinazione" | |
| ]; | |
| let messageIndex = 0; | |
| const voiceInterval = setInterval(() => { | |
| if (messageIndex < voiceMessages.length) { | |
| voiceText.textContent = voiceMessages[messageIndex]; | |
| messageIndex++; | |
| } else { | |
| clearInterval(voiceInterval); | |
| setTimeout(() => { | |
| alert("Hai raggiunto la destinazione!"); | |
| // Reset | |
| voiceBubble.classList.add('hidden'); | |
| compassSpeed.classList.add('hidden'); | |
| bottomPanel.classList.add('translate-y-full'); | |
| searchInput.value = ''; | |
| }, 2000); | |
| } | |
| }, 8000); | |
| // Simulate speed changes | |
| let currentSpeed = 0; | |
| const speedInterval = setInterval(() => { | |
| if (messageIndex < 4) { | |
| currentSpeed = Math.floor(Math.random() * 60) + 30; // 30-90 km/h | |
| } else { | |
| currentSpeed = Math.max(0, currentSpeed - 10); | |
| } | |
| speedValue.textContent = currentSpeed; | |
| }, 1000); | |
| // Stop speed simulation when done | |
| setTimeout(() => { | |
| clearInterval(speedInterval); | |
| }, 30000); | |
| }); | |
| // Simulate location updates | |
| let userMarker; | |
| let userPosition = [41.9028, 12.4964]; // Start in Rome | |
| function updateLocation() { | |
| // Simulate small movements | |
| userPosition[0] += (Math.random() - 0.5) * 0.001; | |
| userPosition[1] += (Math.random() - 0.5) * 0.001; | |
| if (userMarker) { | |
| userMarker.setLatLng(userPosition); | |
| } else { | |
| userMarker = L.marker(userPosition, { | |
| icon: L.divIcon({ | |
| className: 'user-location', | |
| html: '<div style="background-color: #4285F4; width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 5px rgba(0,0,0,0.5);"></div>', | |
| iconSize: [16, 16], | |
| iconAnchor: [8, 8] | |
| }) | |
| }).addTo(map2d); | |
| } | |
| // Pan map to user if navigation is active | |
| if (!startNavigationBtn.classList.contains('hidden') && !startNavigationBtn.classList.contains('hidden')) { | |
| map2d.panTo(userPosition); | |
| } | |
| } | |
| // Update location every 3 seconds | |
| setInterval(updateLocation, 3000); | |
| updateLocation(); // Initial location | |
| // Try to get real user location | |
| if (navigator.geolocation) { | |
| navigator.geolocation.getCurrentPosition(position => { | |
| userPosition = [position.coords.latitude, position.coords.longitude]; | |
| updateLocation(); | |
| currentLocation.textContent = `Lat: ${position.coords.latitude.toFixed(4)}, Lng: ${position.coords.longitude.toFixed(4)}`; | |
| }, error => { | |
| console.log("Unable to get location: ", error); | |
| }, { enableHighAccuracy: true }); | |
| } | |
| // Double-tap to expand bottom panel | |
| let lastTap = 0; | |
| bottomPanel.addEventListener('touchend', (e) => { | |
| const currentTime = new Date().getTime(); | |
| const tapLength = currentTime - lastTap; | |
| if (tapLength < 300 && tapLength > 0) { | |
| // Double tap detected | |
| if (bottomPanel.classList.contains('translate-y-full')) { | |
| bottomPanel.classList.remove('translate-y-full'); | |
| } else { | |
| bottomPanel.classList.add('translate-y-full'); | |
| } | |
| } | |
| lastTap = currentTime; | |
| }); | |
| // Initial voice guidance | |
| setTimeout(() => { | |
| if (voiceBubble.classList.contains('hidden')) { | |
| voiceBubble.classList.remove('hidden'); | |
| voiceText.textContent = "Benvenuto nel navigatore Italia"; | |
| } | |
| }, 2000); | |
| </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-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=condorhacker/condor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |