Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Route Planner | Traffic Optimized</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"> | |
| <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap" async defer></script> | |
| <style> | |
| #map { | |
| height: 400px; | |
| width: 100%; | |
| } | |
| .autocomplete-dropdown { | |
| position: absolute; | |
| z-index: 1000; | |
| width: calc(100% - 2px); | |
| background: white; | |
| border: 1px solid #e2e8f0; | |
| border-top: none; | |
| border-radius: 0 0 0.375rem 0.375rem; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| } | |
| .autocomplete-item { | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| } | |
| .autocomplete-item:hover { | |
| background-color: #f1f5f9; | |
| } | |
| .sidebar { | |
| transition: all 0.3s ease; | |
| } | |
| .sidebar-hidden { | |
| transform: translateX(100%); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="flex flex-col min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-blue-600 text-white p-4 shadow-md"> | |
| <div class="container mx-auto flex justify-between items-center"> | |
| <h1 class="text-2xl font-bold flex items-center"> | |
| <i class="fas fa-route mr-2"></i> Route Planner | |
| </h1> | |
| <button id="settings-btn" class="p-2 rounded-full hover:bg-blue-700 transition"> | |
| <i class="fas fa-cog text-xl"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-grow container mx-auto p-4 flex flex-col md:flex-row gap-6"> | |
| <!-- Left Panel - Form --> | |
| <div class="w-full md:w-1/3 lg:w-1/4 bg-white p-6 rounded-lg shadow-md"> | |
| <div class="mb-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Plan Your Route</h2> | |
| <!-- Start Location --> | |
| <div class="mb-4 relative"> | |
| <label for="start" class="block text-sm font-medium text-gray-700 mb-1">Start Location</label> | |
| <div class="relative"> | |
| <input type="text" id="start" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Enter address or select from map"> | |
| <button id="start-map-btn" class="absolute right-3 top-3 text-gray-500 hover:text-blue-600"> | |
| <i class="fas fa-map-marker-alt"></i> | |
| </button> | |
| </div> | |
| <div id="start-dropdown" class="autocomplete-dropdown hidden"></div> | |
| <div class="mt-1 flex flex-wrap gap-2"> | |
| <button class="address-chip bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs flex items-center" data-address="Home"> | |
| <i class="fas fa-home mr-1"></i> Home | |
| </button> | |
| <button class="address-chip bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs flex items-center" data-address="Work"> | |
| <i class="fas fa-briefcase mr-1"></i> Work | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Destination --> | |
| <div class="mb-4 relative"> | |
| <label for="destination" class="block text-sm font-medium text-gray-700 mb-1">Destination</label> | |
| <div class="relative"> | |
| <input type="text" id="destination" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Enter address or select from map"> | |
| <button id="dest-map-btn" class="absolute right-3 top-3 text-gray-500 hover:text-blue-600"> | |
| <i class="fas fa-map-marker-alt"></i> | |
| </button> | |
| </div> | |
| <div id="dest-dropdown" class="autocomplete-dropdown hidden"></div> | |
| <div class="mt-1 flex flex-wrap gap-2"> | |
| <button class="address-chip bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs flex items-center" data-address="Work"> | |
| <i class="fas fa-briefcase mr-1"></i> Work | |
| </button> | |
| <button class="address-chip bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs flex items-center" data-address="Gym"> | |
| <i class="fas fa-dumbbell mr-1"></i> Gym | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex justify-between mb-4"> | |
| <button id="swap-btn" class="text-blue-600 hover:text-blue-800 flex items-center"> | |
| <i class="fas fa-exchange-alt mr-2"></i> Swap | |
| </button> | |
| <button id="save-route-btn" class="text-blue-600 hover:text-blue-800 flex items-center"> | |
| <i class="fas fa-bookmark mr-2"></i> Save Route | |
| </button> | |
| </div> | |
| <!-- Route Options --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-2">Route Options</h3> | |
| <div class="flex flex-wrap gap-2"> | |
| <button id="fastest-route" class="route-option bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
| <i class="fas fa-bolt mr-2"></i> Fastest | |
| </button> | |
| <button id="shortest-route" class="route-option bg-gray-200 text-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <i class="fas fa-route mr-2"></i> Shortest | |
| </button> | |
| <button id="less-traffic-route" class="route-option bg-gray-200 text-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <i class="fas fa-car-side mr-2"></i> Less Traffic | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Departure Time --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-2">Departure Time</h3> | |
| <div class="flex items-center gap-2"> | |
| <input type="datetime-local" id="departure-time" class="p-2 border border-gray-300 rounded-lg"> | |
| <button id="now-btn" class="bg-gray-100 hover:bg-gray-200 px-3 py-2 rounded-lg text-sm"> | |
| Now | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Find Route Button --> | |
| <button id="find-route-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg shadow-md transition flex items-center justify-center"> | |
| <i class="fas fa-search mr-2"></i> Find Best Route | |
| </button> | |
| </div> | |
| <!-- Saved Routes --> | |
| <div> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-medium text-gray-700">Saved Routes</h3> | |
| <button id="show-all-routes" class="text-blue-600 text-sm">View All</button> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="saved-route bg-gray-50 p-3 rounded-lg border border-gray-200 hover:border-blue-400 cursor-pointer"> | |
| <div class="flex justify-between"> | |
| <span class="font-medium">Home → Work</span> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| <div class="text-sm text-gray-600">Fastest route - 25 min</div> | |
| </div> | |
| <div class="saved-route bg-gray-50 p-3 rounded-lg border border-gray-200 hover:border-blue-400 cursor-pointer"> | |
| <div class="flex justify-between"> | |
| <span class="font-medium">Work → Gym</span> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| <div class="text-sm text-gray-600">Less traffic - 18 min</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Map and Results --> | |
| <div class="flex-grow bg-white rounded-lg shadow-md overflow-hidden"> | |
| <!-- Map --> | |
| <div id="map"></div> | |
| <!-- Route Details --> | |
| <div id="route-details" class="p-4 border-t border-gray-200 hidden"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-lg font-semibold">Route Details</h3> | |
| <button id="clear-route" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> Clear | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"> | |
| <div class="bg-blue-50 p-3 rounded-lg"> | |
| <div class="text-sm text-blue-600">Distance</div> | |
| <div id="distance" class="text-xl font-bold">15.2 miles</div> | |
| </div> | |
| <div class="bg-green-50 p-3 rounded-lg"> | |
| <div class="text-sm text-green-600">Duration</div> | |
| <div id="duration" class="text-xl font-bold">25 min</div> | |
| </div> | |
| <div class="bg-purple-50 p-3 rounded-lg"> | |
| <div class="text-sm text-purple-600">Traffic</div> | |
| <div id="traffic" class="text-xl font-bold">Moderate</div> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="font-medium mb-2">Directions</h4> | |
| <div id="directions-panel" class="space-y-2 max-h-64 overflow-y-auto"> | |
| <!-- Directions will be populated here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Settings Sidebar --> | |
| <div id="settings-sidebar" class="fixed top-0 right-0 h-full w-80 bg-white shadow-lg transform sidebar-sidebar-hidden z-50 transition-all duration-300 ease-in-out"> | |
| <div class="p-6 h-full overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-6 border-b border-gray-200 pb-4"> | |
| <h2 class="text-xl font-bold">Settings</h2> | |
| <button id="close-settings" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <!-- Settings Sections --> | |
| <div class="space-y-8"> | |
| <!-- Home Address --> | |
| <div> | |
| <h3 class="font-medium text-gray-700 mb-3">Home Address</h3> | |
| <div class="relative"> | |
| <input type="text" id="home-address" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your home address"> | |
| <div id="home-dropdown" class="autocomplete-dropdown hidden"></div> | |
| </div> | |
| </div> | |
| <!-- Favorite Locations --> | |
| <div> | |
| <h3 class="font-medium text-gray-700 mb-3">Saved Locations</h3> | |
| <div id="favorite-locations" class="space-y-2"> | |
| <div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg"> | |
| <div> | |
| <span class="font-medium">Work</span> | |
| <div class="text-sm text-gray-600">123 Business St, City</div> | |
| </div> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg"> | |
| <div> | |
| <span class="font-medium">Gym</span> | |
| <div class="text-sm text-gray-600">456 Fitness Ave, City</div> | |
| </div> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <button id="add-favorite-btn" class="mt-2 text-blue-600 flex items-center"> | |
| <i class="fas fa-plus mr-2"></i> Add New Location | |
| </button> | |
| <!-- Add Favorite Form (hidden by default) --> | |
| <div id="add-favorite-form" class="mt-3 hidden space-y-3"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Name</label> | |
| <input type="text" id="favorite-name" class="w-full p-2 border border-gray-300 rounded-lg"> | |
| </div> | |
| <div class="relative"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Address</label> | |
| <input type="text" id="favorite-address" class="w-full p-2 border border-gray-300 rounded-lg"> | |
| <div id="favorite-dropdown" class="autocomplete-dropdown hidden"></div> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button id="cancel-favorite" class="flex-grow bg-gray-200 hover:bg-gray-300 p-2 rounded-lg"> | |
| Cancel | |
| </button> | |
| <button id="save-favorite" class="flex-grow bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg"> | |
| Save | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- App Preferences --> | |
| <div> | |
| <h3 class="font-medium text-gray-700 mb-3">App Preferences</h3> | |
| <div class="space-y-4"> | |
| <div class="flex items-center justify-between"> | |
| <span>Dark Mode</span> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" class="sr-only peer"> | |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div> | |
| </label> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <span>Traffic Alerts</span> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" checked class="sr-only peer"> | |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div> | |
| </label> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <span>Auto-save routes</span> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" checked class="sr-only peer"> | |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="border-t border-gray-200 pt-4"> | |
| <button class="w-full bg-red-50 hover:bg-red-100 text-red-600 p-2 rounded-lg font-medium"> | |
| Clear All Saved Data | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize variables | |
| let map; | |
| let directionsService; | |
| let directionsRenderer; | |
| let autocompleteStart; | |
| let autocompleteDest; | |
| let autocompleteHome; | |
| let autocompleteFavorite; | |
| let markers = []; | |
| let selectedRouteType = 'fastest'; | |
| // Initialize map when the API is loaded | |
| function initMap() { | |
| // Default to New York if geolocation fails | |
| const defaultCenter = { lat: 40.7128, lng: -74.0060 }; | |
| map = new google.maps.Map(document.getElementById("map"), { | |
| zoom: 12, | |
| center: defaultCenter, | |
| mapTypeControl: false, | |
| streetViewControl: false | |
| }); | |
| directionsService = new google.maps.DirectionsService(); | |
| directionsRenderer = new google.maps.DirectionsRenderer({ | |
| map: map, | |
| suppressMarkers: false, | |
| panel: document.getElementById("directions-panel") | |
| }); | |
| // Initialize autocomplete services | |
| autocompleteStart = new google.maps.places.Autocomplete( | |
| document.getElementById("start"), | |
| { types: ["geocode"] } | |
| ); | |
| autocompleteDest = new google.maps.places.Autocomplete( | |
| document.getElementById("destination"), | |
| { types: ["geocode"] } | |
| ); | |
| autocompleteHome = new google.maps.places.Autocomplete( | |
| document.getElementById("home-address"), | |
| { types: ["geocode"] } | |
| ); | |
| autocompleteFavorite = new google.maps.places.Autocomplete( | |
| document.getElementById("favorite-address"), | |
| { types: ["geocode"] } | |
| ); | |
| // Set up autocomplete listeners | |
| setupAutocomplete("start", "start-dropdown"); | |
| setupAutocomplete("destination", "dest-dropdown"); | |
| setupAutocomplete("home-address", "home-dropdown"); | |
| setupAutocomplete("favorite-address", "favorite-dropdown"); | |
| // Try to get current location | |
| if (navigator.geolocation) { | |
| navigator.geolocation.getCurrentPosition( | |
| (position) => { | |
| const pos = { | |
| lat: position.coords.latitude, | |
| lng: position.coords.longitude | |
| }; | |
| map.setCenter(pos); | |
| // Set the current location as the default start point | |
| const geocoder = new google.maps.Geocoder(); | |
| geocoder.geocode({ location: pos }, (results, status) => { | |
| if (status === "OK" && results[0]) { | |
| document.getElementById("start").value = results[0].formatted_address; | |
| } | |
| }); | |
| }, | |
| () => { | |
| // Handle geolocation error by using default center | |
| console.log("Geolocation permission denied or failed"); | |
| map.setCenter(defaultCenter); | |
| } | |
| ); | |
| } else { | |
| // Browser doesn't support Geolocation | |
| console.log("Geolocation not supported"); | |
| map.setCenter(defaultCenter); | |
| } | |
| } | |
| // Helper function to set up autocomplete | |
| function setupAutocomplete(inputId, dropdownId) { | |
| const input = document.getElementById(inputId); | |
| const dropdown = document.getElementById(dropdownId); | |
| input.addEventListener("input", function() { | |
| if (this.value.length > 2) { | |
| // Here you would normally call the Places API to get predictions | |
| // For demo purposes, we'll simulate it | |
| simulateAutocomplete(this.value, dropdown); | |
| } else { | |
| dropdown.classList.add("hidden"); | |
| } | |
| }); | |
| // Hide dropdown when clicking outside | |
| document.addEventListener("click", function(e) { | |
| if (e.target !== input) { | |
| dropdown.classList.add("hidden"); | |
| } | |
| }); | |
| } | |
| // Simulate autocomplete results (in a real app, you'd call the Places API) | |
| function simulateAutocomplete(query, dropdown) { | |
| dropdown.innerHTML = ''; | |
| // Simulate API delay | |
| setTimeout(() => { | |
| const mockResults = [ | |
| `${query} Street, City, Country`, | |
| `${query} Avenue, City, Country`, | |
| `${query} Business Center, City, Country`, | |
| `${query} Mall, City, Country` | |
| ]; | |
| mockResults.forEach(result => { | |
| const item = document.createElement("div"); | |
| item.className = "autocomplete-item"; | |
| item.textContent = result; | |
| item.addEventListener("click", function() { | |
| // This would be the input associated with the dropdown | |
| const input = dropdown.previousElementSibling.querySelector("input") || | |
| dropdown.parentElement.previousElementSibling.querySelector("input"); | |
| input.value = result; | |
| dropdown.classList.add("hidden"); | |
| }); | |
| dropdown.appendChild(item); | |
| }); | |
| dropdown.classList.remove("hidden"); | |
| }, 300); | |
| } | |
| // Function to calculate and display the route | |
| function calculateRoute() { | |
| const start = document.getElementById("start").value; | |
| const end = document.getElementById("destination").value; | |
| const departureTime = document.getElementById("departure-time").value; | |
| if (!start || !end) { | |
| alert("Please enter both start and destination locations"); | |
| return; | |
| } | |
| let request = { | |
| origin: start, | |
| destination: end, | |
| travelMode: google.maps.TravelMode.DRIVING, | |
| provideRouteAlternatives: true | |
| }; | |
| // Add departure time if provided | |
| if (departureTime) { | |
| request.drivingOptions = { | |
| departureTime: new Date(departureTime), | |
| trafficModel: selectedRouteType === 'less-traffic' ? 'pessimistic' : 'bestguess' | |
| }; | |
| } | |
| directionsService.route(request, function(response, status) { | |
| if (status === "OK") { | |
| // Show route details panel | |
| document.getElementById("route-details").classList.remove("hidden"); | |
| // Process response to choose best route based on selection | |
| let chosenRoute; | |
| if (selectedRouteType === 'fastest') { | |
| // Find route with shortest duration | |
| chosenRoute = response.routes.reduce((prev, curr) => | |
| prev.legs[0].duration.value < curr.legs[0].duration.value ? prev : curr | |
| ); | |
| } else if (selectedRouteType === 'shortest') { | |
| // Find route with shortest distance | |
| chosenRoute = response.routes.reduce((prev, curr) => | |
| prev.legs[0].distance.value < curr.legs[0].distance.value ? prev : curr | |
| ); | |
| } else { | |
| // Less traffic route (custom logic needed) | |
| chosenRoute = response.routes[0]; // Simplification for demo | |
| } | |
| // Display chosen route | |
| directionsRenderer.setDirections({ | |
| routes: [chosenRoute], | |
| request: request | |
| }); | |
| // Update route details | |
| const leg = chosenRoute.legs[0]; | |
| document.getElementById("distance").textContent = leg.distance.text; | |
| document.getElementById("duration").textContent = leg.duration.text; | |
| // Simple traffic estimation (would use real traffic data in a real app) | |
| let trafficLevel = "Light"; | |
| if (leg.duration_in_traffic) { | |
| const diff = leg.duration_in_traffic.value - leg.duration.value; | |
| const percentDiff = (diff / leg.duration.value) * 100; | |
| if (percentDiff > 30) trafficLevel = "Heavy"; | |
| else if (percentDiff > 15) trafficLevel = "Moderate"; | |
| else trafficLevel = "Light"; | |
| } | |
| document.getElementById("traffic").textContent = trafficLevel; | |
| } else { | |
| alert("Directions request failed due to " + status); | |
| } | |
| }); | |
| } | |
| // Set up event listeners when DOM is loaded | |
| document.addEventListener("DOMContentLoaded", function() { | |
| // Set current time as default departure time | |
| const now = new Date(); | |
| now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); | |
| document.getElementById("departure-time").value = now.toISOString().slice(0, 16); | |
| // Event listeners for route options | |
| document.getElementById("fastest-route").addEventListener("click", function() { | |
| document.querySelectorAll(".route-option").forEach(btn => { | |
| btn.classList.remove("bg-blue-600", "text-white"); | |
| btn.classList.add("bg-gray-200", "text-gray-800"); | |
| }); | |
| this.classList.remove("bg-gray-200", "text-gray-800"); | |
| this.classList.add("bg-blue-600", "text-white"); | |
| selectedRouteType = 'fastest'; | |
| }); | |
| document.getElementById("shortest-route").addEventListener("click", function() { | |
| document.querySelectorAll(".route-option").forEach(btn => { | |
| btn.classList.remove("bg-blue-600", "text-white"); | |
| btn.classList.add("bg-gray-200", "text-gray-800"); | |
| }); | |
| this.classList.remove("bg-gray-200", "text-gray-800"); | |
| this.classList.add("bg-blue-600", "text-white"); | |
| selectedRouteType = 'shortest'; | |
| }); | |
| document.getElementById("less-traffic-route").addEventListener("click", function() { | |
| document.querySelectorAll(".route-option").forEach(btn => { | |
| btn.classList.remove("bg-blue-600", "text-white"); | |
| btn.classList.add("bg-gray-200", "text-gray-800"); | |
| }); | |
| this.classList.remove("bg-gray-200", "text-gray-800"); | |
| this.classList.add("bg-blue-600", "text-white"); | |
| selectedRouteType = 'less-traffic'; | |
| }); | |
| // "Now" button for departure time | |
| document.getElementById("now-btn").addEventListener("click", function() { | |
| const now = new Date(); | |
| now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); | |
| document.getElementById("departure-time").value = now.toISOString().slice(0, 16); | |
| }); | |
| // Find Route button | |
| document.getElementById("find-route-btn").addEventListener("click", calculateRoute); | |
| // Clear Route button | |
| document.getElementById("clear-route").addEventListener("click", function() { | |
| document.getElementById("route-details").classList.add("hidden"); | |
| directionsRenderer.setDirections({ routes: [] }); | |
| document.getElementById("directions-panel").innerHTML = ""; | |
| }); | |
| // Swap start and destination | |
| document.getElementById("swap-btn").addEventListener("click", function() { | |
| const start = document.getElementById("start"); | |
| const dest = document.getElementById("destination"); | |
| const temp = start.value; | |
| start.value = dest.value; | |
| dest.value = temp; | |
| }); | |
| // Settings button | |
| document.getElementById("settings-btn").addEventListener("click", function() { | |
| document.getElementById("settings-sidebar").classList.remove("sidebar-hidden"); | |
| }); | |
| // Close settings | |
| document.getElementById("close-settings").addEventListener("click", function() { | |
| document.getElementById("settings-sidebar").classList.add("sidebar-hidden"); | |
| }); | |
| // Add favorite location | |
| document.getElementById("add-favorite-btn").addEventListener("click", function() { | |
| document.getElementById("add-favorite-form").classList.remove("hidden"); | |
| document.getElementById("add-favorite-btn").classList.add("hidden"); | |
| }); | |
| // Cancel adding favorite | |
| document.getElementById("cancel-favorite").addEventListener("click", function() { | |
| document.getElementById("add-favorite-form").classList.add("hidden"); | |
| document.getElementById("add-favorite-btn").classList.remove("hidden"); | |
| }); | |
| // Save favorite location | |
| document.getElementById("save-favorite").addEventListener("click", function() { | |
| const name = document.getElementById("favorite-name").value; | |
| const address = document.getElementById("favorite-address").value; | |
| if (name && address) { | |
| const favoriteElement = document.createElement("div"); | |
| favoriteElement.className = "flex items-center justify-between bg-gray-50 p-3 rounded-lg"; | |
| favoriteElement.innerHTML = ` | |
| <div> | |
| <span class="font-medium">${name}</span> | |
| <div class="text-sm text-gray-600">${address}</div> | |
| </div> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| `; | |
| document.getElementById("favorite-locations").appendChild(favoriteElement); | |
| // Reset form | |
| document.getElementById("favorite-name").value = ""; | |
| document.getElementById("favorite-address").value = ""; | |
| document.getElementById("add-favorite-form").classList.add("hidden"); | |
| document.getElementById("add-favorite-btn").classList.remove("hidden"); | |
| } else { | |
| alert("Please enter both name and address"); | |
| } | |
| }); | |
| // Save route | |
| document.getElementById("save-route-btn").addEventListener("click", function() { | |
| const start = document.getElementById("start").value; | |
| const dest = document.getElementById("destination").value; | |
| if (start && dest) { | |
| const savedRoutes = document.querySelector(".saved-route").parentNode; | |
| const newRoute = document.createElement("div"); | |
| newRoute.className = "saved-route bg-gray-50 p-3 rounded-lg border border-gray-200 hover:border-blue-400 cursor-pointer mt-2"; | |
| newRoute.innerHTML = ` | |
| <div class="flex justify-between"> | |
| <span class="font-medium">${start.split(",")[0]} → ${dest.split(",")[0]}</span> | |
| <button class="text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| <div class="text-sm text-gray-600">${selectedRouteType} route</div> | |
| `; | |
| savedRoutes.appendChild(newRoute); | |
| // Click handler for the new route | |
| newRoute.addEventListener("click", function() { | |
| document.getElementById("start").value = start; | |
| document.getElementById("destination").value = dest; | |
| calculateRoute(); | |
| }); | |
| alert("Route saved to your favorites!"); | |
| } else { | |
| alert("Please enter both start and destination locations"); | |
| } | |
| }); | |
| // Home and Work address chips | |
| document.querySelectorAll(".address-chip").forEach(chip => { | |
| chip.addEventListener("click", function() { | |
| const input = this.closest(".mb-4").querySelector("input"); | |
| if (input.id === "start" || input.id === "destination") { | |
| input.value = this.getAttribute("data-address"); | |
| } | |
| }); | |
| }); | |
| // Start from map button | |
| document.getElementById("start-map-btn").addEventListener("click", function() { | |
| alert("Click on the map to set start location"); | |
| setupMapClickListener("start"); | |
| }); | |
| // Destination from map button | |
| document.getElementById("dest-map-btn").addEventListener("click", function() { | |
| alert("Click on the map to set destination"); | |
| setupMapClickListener("destination"); | |
| }); | |
| }); | |
| // Set up map click listener for setting location | |
| function setupMapClickListener(inputId) { | |
| // Clear existing markers | |
| markers.forEach(marker => marker.setMap(null)); | |
| markers = []; | |
| const listener = map.addListener("click", (e) => { | |
| // Remove the listener after first click | |
| google.maps.event.removeListener(listener); | |
| // Add marker | |
| const marker = new google.maps.Marker({ | |
| position: e.latLng, | |
| map: map, | |
| draggable: true | |
| }); | |
| markers.push(marker); | |
| // Geocode the clicked location | |
| const geocoder = new google.maps.Geocoder(); | |
| geocoder.geocode({ location: e.latLng }, (results, status) => { | |
| if (status === "OK" && results[0]) { | |
| document.getElementById(inputId).value = results[0].formatted_address; | |
| } else { | |
| document.getElementById(inputId).value = `${e.latLng.lat()}, ${e.latLng.lng()}`; | |
| } | |
| }); | |
| // Update position if marker is dragged | |
| marker.addListener("dragend", (e) => { | |
| geocoder.geocode({ location: e.latLng }, (results, status) => { | |
| if (status === "OK" && results[0]) { | |
| document.getElementById(inputId).value = results[0].formatted_address; | |
| } | |
| }); | |
| }); | |
| }); | |
| } | |
| // Handle window resizing | |
| window.addEventListener("resize", function() { | |
| if (map) { | |
| google.maps.event.trigger(map, "resize"); | |
| } | |
| }); | |
| </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=Samikciku/traffic-test" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |