first commit
This commit is contained in:
241
truckstops.html
Normal file
241
truckstops.html
Normal file
@@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Truck Stops & Parking | PilotEdge</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>
|
||||
<style>
|
||||
#map { height: 450px; width: 100%; border-radius: 0.75rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-50 min-h-screen flex flex-col">
|
||||
|
||||
<div id="main-nav"></div>
|
||||
<div id="poc-banner"></div>
|
||||
|
||||
<!-- Page Header -->
|
||||
<section class="bg-slate-900 text-white pt-24 pb-12 px-4">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<h1 class="text-3xl md:text-4xl font-bold mb-3">Truck Stops & Parking for Oversize Loads</h1>
|
||||
<p class="text-lg text-gray-400 max-w-3xl">Find oversize-friendly truck stops, rest areas, and staging locations across the US. Community-verified with driver comments and entrance dimensions.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<section class="max-w-7xl mx-auto px-4 pt-8 w-full">
|
||||
<div class="bg-white rounded-2xl shadow-lg p-6">
|
||||
<div class="flex flex-col md:flex-row gap-4 items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm font-semibold text-slate-700 mb-1">Search Truck Stops</label>
|
||||
<input type="text" id="ts-search" oninput="filterStops()" placeholder="e.g. Iowa 80, Amarillo, TX..." class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-1">
|
||||
<input type="checkbox" id="ts-oversize-only" onchange="filterStops()" class="w-4 h-4 text-amber-500 border-slate-300 rounded focus:ring-amber-400">
|
||||
<label for="ts-oversize-only" class="text-sm font-semibold text-slate-700 whitespace-nowrap">Oversize Friendly Only</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Map -->
|
||||
<section class="max-w-7xl mx-auto px-4 pt-8 w-full">
|
||||
<div class="bg-white rounded-2xl shadow-lg p-4">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Truck Stop Cards -->
|
||||
<section class="max-w-7xl mx-auto px-4 pt-8 pb-8 w-full">
|
||||
<div id="ts-list" class="space-y-6"></div>
|
||||
</section>
|
||||
|
||||
<!-- Submit a Location -->
|
||||
<section class="max-w-7xl mx-auto px-4 pb-12 w-full">
|
||||
<div class="bg-amber-50 border-2 border-amber-200 rounded-2xl p-8 text-center">
|
||||
<h2 class="text-2xl font-bold text-slate-900 mb-2">Know an Oversize-Friendly Location?</h2>
|
||||
<p class="text-slate-600 mb-4">Help the community by suggesting truck stops, rest areas, or staging lots that accommodate oversize loads.</p>
|
||||
<button onclick="alert('POC: This would open a submission form for new truck stop locations. Feature coming in production release.')" class="bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold px-6 py-3 rounded-lg transition-colors shadow-md hover:shadow-lg">
|
||||
📍 Submit a Location
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="main-footer"></div>
|
||||
|
||||
<script src="mock-data.js"></script>
|
||||
<script src="mock-data-extended.js"></script>
|
||||
<script src="nav.js"></script>
|
||||
<script>
|
||||
renderNav('truckstops');
|
||||
renderBanner();
|
||||
renderFooter();
|
||||
|
||||
// Facility emoji mapping
|
||||
const facilityIcons = {
|
||||
fuel: '⛽', food: '🍔', restrooms: '🚻', showers: '🚿',
|
||||
mechanic: '🔧', scale: '⚖️', ev_charging: '🔌', hotel: '🏨',
|
||||
trucking_museum: '🏛️', barber: '💈', chiropractor: '🦴', iron_skillet: '🍳'
|
||||
};
|
||||
|
||||
let map, markers = [];
|
||||
|
||||
function initMap() {
|
||||
map = L.map('map').setView([39.5, -98.5], 4);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 18
|
||||
}).addTo(map);
|
||||
addMarkers(MOCK_TRUCK_STOPS);
|
||||
}
|
||||
|
||||
function addMarkers(stops) {
|
||||
markers.forEach(m => map.removeLayer(m));
|
||||
markers = [];
|
||||
stops.forEach(stop => {
|
||||
const color = stop.oversizeFriendly ? '#22c55e' : '#ef4444';
|
||||
const marker = L.circleMarker([stop.location.lat, stop.location.lng], {
|
||||
radius: 9, fillColor: color, color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.85
|
||||
}).addTo(map);
|
||||
marker.bindPopup(`
|
||||
<div style="min-width:200px">
|
||||
<strong style="font-size:14px">${stop.name}</strong><br>
|
||||
<span style="color:#64748b">${stop.location.city}, ${stop.location.state}</span><br>
|
||||
<span style="display:inline-block;margin-top:4px;padding:2px 8px;border-radius:9999px;font-size:11px;font-weight:600;color:#fff;background:${stop.oversizeFriendly ? '#22c55e' : '#ef4444'}">
|
||||
${stop.oversizeFriendly ? 'OVERSIZE FRIENDLY' : 'NOT RECOMMENDED'}
|
||||
</span><br>
|
||||
<button onclick="scrollToCard('${stop.id}')" style="margin-top:8px;background:#f59e0b;color:#0f172a;border:none;padding:6px 14px;border-radius:8px;font-weight:700;font-size:12px;cursor:pointer">View Details</button>
|
||||
</div>
|
||||
`);
|
||||
markers.push(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function scrollToCard(id) {
|
||||
const el = document.getElementById('card-' + id);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
el.classList.add('ring-2', 'ring-amber-400');
|
||||
setTimeout(() => el.classList.remove('ring-2', 'ring-amber-400'), 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function renderStops(stops) {
|
||||
const list = document.getElementById('ts-list');
|
||||
if (!stops.length) {
|
||||
list.innerHTML = '<div class="text-center py-12 text-slate-500">No truck stops match your filters.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = stops.map(stop => `
|
||||
<div id="card-${stop.id}" class="bg-white rounded-2xl shadow-lg p-6 transition-all duration-300">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-900">${stop.name}</h3>
|
||||
<p class="text-slate-500">${stop.location.city}, ${stop.location.state}</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold whitespace-nowrap ${stop.oversizeFriendly ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
|
||||
${stop.oversizeFriendly ? '✅ OVERSIZE FRIENDLY' : '⛔ NOT RECOMMENDED FOR OVERSIZE'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Specs Grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
||||
<div class="bg-slate-50 rounded-xl p-3 text-center">
|
||||
<div class="text-xs text-slate-500 font-semibold mb-1">Entrance Width</div>
|
||||
<div class="text-sm font-bold text-slate-800">${stop.entranceWidth}</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 rounded-xl p-3 text-center">
|
||||
<div class="text-xs text-slate-500 font-semibold mb-1">Entrance Height</div>
|
||||
<div class="text-sm font-bold text-slate-800">${stop.entranceHeight}</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 rounded-xl p-3 text-center">
|
||||
<div class="text-xs text-slate-500 font-semibold mb-1">Lot Size</div>
|
||||
<div class="text-sm font-bold text-slate-800">${stop.lotSize}</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 rounded-xl p-3 text-center">
|
||||
<div class="text-xs text-slate-500 font-semibold mb-1">Oversize Capacity</div>
|
||||
<div class="text-sm font-bold text-slate-800">${stop.oversizeCapacity}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Facilities -->
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
${stop.facilities.map(f => `
|
||||
<span class="bg-slate-100 rounded-full px-3 py-1 text-xs font-medium text-slate-700">
|
||||
${facilityIcons[f] || '📌'} ${f.replace(/_/g, ' ')}
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-slate-600 mb-5">${stop.description}</p>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="border-t border-slate-200 pt-4">
|
||||
<h4 class="text-sm font-bold text-slate-900 mb-3">💬 Driver Comments (${stop.comments.length})</h4>
|
||||
<div class="space-y-3 mb-4">
|
||||
${stop.comments.map(c => `
|
||||
<div class="bg-slate-50 rounded-xl p-3">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs font-bold text-slate-700">${c.user}</span>
|
||||
<span class="text-xs text-slate-400">${c.date}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600">${c.text}</p>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div id="comment-form-${stop.id}" class="hidden">
|
||||
<textarea id="comment-text-${stop.id}" rows="3" placeholder="Share your experience at this location..." class="w-full border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none mb-2"></textarea>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="submitComment('${stop.id}')" class="bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold px-4 py-2 rounded-lg text-sm transition-colors">Submit Comment</button>
|
||||
<button onclick="toggleCommentForm('${stop.id}')" class="bg-slate-200 hover:bg-slate-300 text-slate-700 font-bold px-4 py-2 rounded-lg text-sm transition-colors">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="comment-btn-${stop.id}" onclick="toggleCommentForm('${stop.id}')" class="text-sm font-semibold text-amber-600 hover:text-amber-700 transition-colors">
|
||||
+ Add Comment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function toggleCommentForm(id) {
|
||||
const form = document.getElementById('comment-form-' + id);
|
||||
const btn = document.getElementById('comment-btn-' + id);
|
||||
const visible = !form.classList.contains('hidden');
|
||||
form.classList.toggle('hidden');
|
||||
btn.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function submitComment(id) {
|
||||
const text = document.getElementById('comment-text-' + id).value.trim();
|
||||
if (!text) { alert('Please enter a comment.'); return; }
|
||||
alert('POC: Comment submitted! In production, this would save to the database.\n\nYour comment: "' + text + '"');
|
||||
document.getElementById('comment-text-' + id).value = '';
|
||||
toggleCommentForm(id);
|
||||
}
|
||||
|
||||
function filterStops() {
|
||||
const query = document.getElementById('ts-search').value.toLowerCase();
|
||||
const oversizeOnly = document.getElementById('ts-oversize-only').checked;
|
||||
const filtered = MOCK_TRUCK_STOPS.filter(stop => {
|
||||
const matchSearch = !query ||
|
||||
stop.name.toLowerCase().includes(query) ||
|
||||
stop.location.city.toLowerCase().includes(query) ||
|
||||
stop.location.state.toLowerCase().includes(query);
|
||||
const matchOversize = !oversizeOnly || stop.oversizeFriendly;
|
||||
return matchSearch && matchOversize;
|
||||
});
|
||||
renderStops(filtered);
|
||||
addMarkers(filtered);
|
||||
}
|
||||
|
||||
// Initialize
|
||||
initMap();
|
||||
renderStops(MOCK_TRUCK_STOPS);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user