Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CommuteCompare - Real Estate Distance Analyzer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <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> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| #map { | |
| height: 400px; | |
| border-radius: 0.75rem; | |
| z-index: 0; | |
| } | |
| .property-card { | |
| transition: all 0.3s ease; | |
| } | |
| .property-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); | |
| } | |
| .commute-bar { | |
| height: 8px; | |
| border-radius: 4px; | |
| } | |
| .route-line { | |
| stroke-width: 4; | |
| stroke-linecap: round; | |
| stroke-dasharray: 10, 10; | |
| animation: dash 1s linear infinite; | |
| } | |
| @keyframes dash { | |
| from { | |
| stroke-dashoffset: 20; | |
| } | |
| to { | |
| stroke-dashoffset: 0; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-10 text-center"> | |
| <h1 class="text-4xl font-bold text-indigo-800 mb-2">CommuteCompare</h1> | |
| <p class="text-lg text-gray-600">Find your perfect home based on your work commute</p> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Input Section --> | |
| <div class="lg:col-span-1 bg-white p-6 rounded-xl shadow-md"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Commute Preferences</h2> | |
| <div class="space-y-6"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Work Address</label> | |
| <div class="relative"> | |
| <input type="text" id="workAddress" placeholder="Enter your workplace address" | |
| class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| <div class="absolute inset-y-0 right-0 flex items-center pr-3"> | |
| <i class="fas fa-briefcase text-gray-400"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Maximum Commute Time</label> | |
| <div class="flex items-center space-x-2"> | |
| <input type="range" id="maxCommute" min="5" max="120" value="30" | |
| class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <span id="commuteValue" class="text-sm font-medium text-gray-700 w-12 text-center">30 min</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Transport Mode</label> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <button class="transport-mode-btn active bg-indigo-100 text-indigo-700" data-mode="driving"> | |
| <i class="fas fa-car mr-2"></i> Drive | |
| </button> | |
| <button class="transport-mode-btn bg-gray-100 text-gray-700" data-mode="transit"> | |
| <i class="fas fa-bus mr-2"></i> Transit | |
| </button> | |
| <button class="transport-mode-btn bg-gray-100 text-gray-700" data-mode="walking"> | |
| <i class="fas fa-walking mr-2"></i> Walk | |
| </button> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Price Range</label> | |
| <div class="flex items-center space-x-2"> | |
| <input type="number" id="minPrice" placeholder="Min" | |
| class="w-1/2 px-3 py-2 border border-gray-300 rounded-lg"> | |
| <input type="number" id="maxPrice" placeholder="Max" | |
| class="w-1/2 px-3 py-2 border border-gray-300 rounded-lg"> | |
| </div> | |
| </div> | |
| <button id="searchBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 px-4 rounded-lg font-medium transition duration-300"> | |
| <i class="fas fa-search mr-2"></i> Find Properties | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Map Section --> | |
| <div class="lg:col-span-2"> | |
| <div id="map" class="mb-6"></div> | |
| <!-- Results Summary --> | |
| <div class="bg-white p-6 rounded-xl shadow-md mb-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Properties Matching Your Commute</h2> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-sm text-gray-500">Sort by:</span> | |
| <select class="border border-gray-300 rounded-lg px-3 py-1 text-sm"> | |
| <option>Commute Time</option> | |
| <option>Price</option> | |
| <option>Distance</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="propertyResults"> | |
| <!-- Sample Property Cards (will be populated by JS) --> | |
| <div class="property-card bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm"> | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-semibold text-lg text-gray-800">123 Maple Street</h3> | |
| <span class="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">15 min</span> | |
| </div> | |
| <p class="text-gray-600 text-sm mb-3">2 bd | 1 ba | 950 sqft</p> | |
| <p class="text-indigo-600 font-bold text-xl mb-3">$320,000</p> | |
| <div class="flex items-center text-sm text-gray-500 mb-2"> | |
| <i class="fas fa-map-marker-alt mr-1 text-indigo-500"></i> | |
| <span>1.2 miles from work</span> | |
| </div> | |
| <div class="h-2 w-full bg-gray-200 rounded-full mb-2"> | |
| <div class="h-2 bg-green-500 rounded-full" style="width: 25%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Short commute</span> | |
| <span>Long commute</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="property-card bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm"> | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-semibold text-lg text-gray-800">456 Oak Avenue</h3> | |
| <span class="bg-yellow-100 text-yellow-800 text-xs px-2 py-1 rounded-full">25 min</span> | |
| </div> | |
| <p class="text-gray-600 text-sm mb-3">3 bd | 2 ba | 1,250 sqft</p> | |
| <p class="text-indigo-600 font-bold text-xl mb-3">$385,000</p> | |
| <div class="flex items-center text-sm text-gray-500 mb-2"> | |
| <i class="fas fa-map-marker-alt mr-1 text-indigo-500"></i> | |
| <span>3.5 miles from work</span> | |
| </div> | |
| <div class="h-2 w-full bg-gray-200 rounded-full mb-2"> | |
| <div class="h-2 bg-yellow-500 rounded-full" style="width: 50%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Short commute</span> | |
| <span>Long commute</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Commute Comparison Section --> | |
| <div class="bg-white p-6 rounded-xl shadow-md mt-8"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Commute Comparison</h2> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Property</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distance</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Commute Time</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Route</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Annual Commute Cost</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="comparisonTable"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <div class="flex-shrink-0 h-10 w-10 bg-indigo-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-home text-indigo-600"></i> | |
| </div> | |
| <div class="ml-4"> | |
| <div class="text-sm font-medium text-gray-900">123 Maple Street</div> | |
| <div class="text-sm text-gray-500">$320,000</div> | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">1.2 miles</td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <div class="w-20 bg-gray-200 rounded-full h-2.5 mr-2"> | |
| <div class="bg-green-500 h-2.5 rounded-full" style="width: 25%"></div> | |
| </div> | |
| <span class="text-sm font-medium text-gray-700">15 min</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-car text-indigo-500"></i> | |
| <span class="text-sm text-gray-500">Mostly highway</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">$1,200/yr</td> | |
| </tr> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <div class="flex-shrink-0 h-10 w-10 bg-indigo-100 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-home text-indigo-600"></i> | |
| </div> | |
| <div class="ml-4"> | |
| <div class="text-sm font-medium text-gray-900">456 Oak Avenue</div> | |
| <div class="text-sm text-gray-500">$385,000</div> | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">3.5 miles</td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <div class="w-20 bg-gray-200 rounded-full h-2.5 mr-2"> | |
| <div class="bg-yellow-500 h-2.5 rounded-full" style="width: 50%"></div> | |
| </div> | |
| <span class="text-sm font-medium text-gray-700">25 min</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-car text-indigo-500"></i> | |
| <span class="text-sm text-gray-500">City streets</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">$1,800/yr</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize the map | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Set up the map centered on a default location (New York) | |
| const map = L.map('map').setView([40.7128, -74.0060], 12); | |
| // Add tile layer (OpenStreetMap) | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | |
| }).addTo(map); | |
| // Add sample markers (work and home locations) | |
| const workMarker = L.marker([40.7128, -74.0060], { | |
| icon: L.divIcon({ | |
| html: '<div class="bg-indigo-600 text-white rounded-full p-2 flex items-center justify-center shadow-lg"><i class="fas fa-briefcase"></i></div>', | |
| className: 'border-0', | |
| iconSize: [40, 40] | |
| }) | |
| }).addTo(map).bindPopup("Your Workplace"); | |
| const homeMarker1 = L.marker([40.7228, -74.0060], { | |
| icon: L.divIcon({ | |
| html: '<div class="bg-green-600 text-white rounded-full p-2 flex items-center justify-center shadow-lg"><i class="fas fa-home"></i></div>', | |
| className: 'border-0', | |
| iconSize: [40, 40] | |
| }) | |
| }).addTo(map).bindPopup("123 Maple Street<br>15 min commute"); | |
| const homeMarker2 = L.marker([40.7028, -74.0160], { | |
| icon: L.divIcon({ | |
| html: '<div class="bg-yellow-600 text-white rounded-full p-2 flex items-center justify-center shadow-lg"><i class="fas fa-home"></i></div>', | |
| className: 'border-0', | |
| iconSize: [40, 40] | |
| }) | |
| }).addTo(map).bindPopup("456 Oak Avenue<br>25 min commute"); | |
| // Add a polyline between work and home locations (sample route) | |
| const polyline = L.polyline([ | |
| [40.7128, -74.0060], | |
| [40.7228, -74.0060] | |
| ], { | |
| color: '#4f46e5', | |
| weight: 4, | |
| dashArray: '10, 10' | |
| }).addTo(map); | |
| // Transport mode buttons functionality | |
| const transportButtons = document.querySelectorAll('.transport-mode-btn'); | |
| transportButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| transportButtons.forEach(btn => { | |
| btn.classList.remove('active', 'bg-indigo-100', 'text-indigo-700'); | |
| btn.classList.add('bg-gray-100', 'text-gray-700'); | |
| }); | |
| this.classList.add('active', 'bg-indigo-100', 'text-indigo-700'); | |
| this.classList.remove('bg-gray-100', 'text-gray-700'); | |
| // In a real app, we would update the route calculation based on transport mode | |
| console.log('Transport mode changed to:', this.dataset.mode); | |
| }); | |
| }); | |
| // Update commute time slider value display | |
| const commuteSlider = document.getElementById('maxCommute'); | |
| const commuteValue = document.getElementById('commuteValue'); | |
| commuteSlider.addEventListener('input', function() { | |
| commuteValue.textContent = this.value + ' min'; | |
| }); | |
| // Search button functionality | |
| document.getElementById('searchBtn').addEventListener('click', function() { | |
| // In a real app, this would fetch properties based on the inputs | |
| const workAddress = document.getElementById('workAddress').value; | |
| const maxCommute = document.getElementById('maxCommute').value; | |
| const minPrice = document.getElementById('minPrice').value; | |
| const maxPrice = document.getElementById('maxPrice').value; | |
| console.log('Searching with:', { | |
| workAddress, | |
| maxCommute, | |
| minPrice, | |
| maxPrice | |
| }); | |
| // Show a loading state | |
| this.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Searching...'; | |
| setTimeout(() => { | |
| this.innerHTML = '<i class="fas fa-search mr-2"></i> Find Properties'; | |
| // In a real app, we would update the results here | |
| }, 1500); | |
| }); | |
| // Sample data for properties (in a real app, this would come from an API) | |
| const sampleProperties = [ | |
| { | |
| address: "123 Maple Street", | |
| price: "$320,000", | |
| details: "2 bd | 1 ba | 950 sqft", | |
| distance: "1.2 miles from work", | |
| commuteTime: 15, | |
| commutePercentage: 25, | |
| commuteType: "Mostly highway", | |
| annualCost: "$1,200/yr", | |
| location: [40.7228, -74.0060] | |
| }, | |
| { | |
| address: "456 Oak Avenue", | |
| price: "$385,000", | |
| details: "3 bd | 2 ba | 1,250 sqft", | |
| distance: "3.5 miles from work", | |
| commuteTime: 25, | |
| commutePercentage: 50, | |
| commuteType: "City streets", | |
| annualCost: "$1,800/yr", | |
| location: [40.7028, -74.0160] | |
| } | |
| ]; | |
| // Function to generate property cards (in a real app, this would be more dynamic) | |
| function generatePropertyCards() { | |
| const container = document.getElementById('propertyResults'); | |
| container.innerHTML = ''; | |
| sampleProperties.forEach(property => { | |
| const card = document.createElement('div'); | |
| card.className = 'property-card bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm'; | |
| // Determine badge color based on commute time | |
| let badgeClass = 'bg-green-100 text-green-800'; | |
| if (property.commuteTime > 20) { | |
| badgeClass = 'bg-yellow-100 text-yellow-800'; | |
| } | |
| if (property.commuteTime > 40) { | |
| badgeClass = 'bg-red-100 text-red-800'; | |
| } | |
| // Determine bar color based on commute time | |
| let barColor = 'bg-green-500'; | |
| if (property.commuteTime > 20) { | |
| barColor = 'bg-yellow-500'; | |
| } | |
| if (property.commuteTime > 40) { | |
| barColor = 'bg-red-500'; | |
| } | |
| card.innerHTML = ` | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-semibold text-lg text-gray-800">${property.address}</h3> | |
| <span class="${badgeClass} text-xs px-2 py-1 rounded-full">${property.commuteTime} min</span> | |
| </div> | |
| <p class="text-gray-600 text-sm mb-3">${property.details}</p> | |
| <p class="text-indigo-600 font-bold text-xl mb-3">${property.price}</p> | |
| <div class="flex items-center text-sm text-gray-500 mb-2"> | |
| <i class="fas fa-map-marker-alt mr-1 text-indigo-500"></i> | |
| <span>${property.distance}</span> | |
| </div> | |
| <div class="h-2 w-full bg-gray-200 rounded-full mb-2"> | |
| <div class="h-2 ${barColor} rounded-full" style="width: ${property.commutePercentage}%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Short commute</span> | |
| <span>Long commute</span> | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| } | |
| // Initial generation of property cards | |
| generatePropertyCards(); | |
| }); | |
| </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=thientran/commutecmpdeepsite" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |