Explore
52°F
Partly Cloudy
Book
Pin Colors

`); printWindow.document.close(); printWindow.print(); } function generateItineraryHTML() { let html = ''; TRIP_DAYS.forEach(day => { const places = tripPlan[day.id] || []; if (places.length > 0) { html += '

' + day.icon + ' ' + day.label + '

'; places.forEach(id => { const p = allPlaces.find(x => x.id === id); if (p) { html += '
' + p.name + '
' + p.area + (p.hours ? ' • ' + p.hours : '') + '
'; } }); } }); return html || '

No places saved yet.

'; } function toggleDirectionsMenu() { const menu = document.getElementById('directionsMenu'); if (menu) { menu.classList.toggle('open'); // Close when clicking outside document.addEventListener('click', function closeMenu(e) { if (!e.target.closest('.directions-dropdown')) { menu.classList.remove('open'); document.removeEventListener('click', closeMenu); } }); } } function showToast(message) { // Create toast element if it doesn't exist let toast = document.getElementById('toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'toast'; toast.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:12px 24px;border-radius:8px;font-size:14px;z-index:9999;opacity:0;transition:opacity .3s'; document.body.appendChild(toast); } toast.textContent = message; toast.style.opacity = '1'; setTimeout(() => { toast.style.opacity = '0'; }, 3000); } function clearAllMarkers() { Object.keys(visibleOnMap).forEach(id => { if (markers[id]) { map.removeLayer(markers[id]); delete markers[id]; } }); visibleOnMap = {}; markerCounter = 1; } function addMarkerToMap(p, num) { if (!p.lat || !p.lng) return; const color = getCatColor(p); const marker = L.marker([p.lat, p.lng], { icon: L.divIcon({ className: 'custom-marker', html: `
${num}
`, iconSize: [28, 28], iconAnchor: [14, 14] }) }).addTo(map); marker.on('click', () => { openDetail(p); // Add bounce animation const el = marker.getElement(); if(el) { el.classList.add('marker-bounce-continuous'); setTimeout(() => el.classList.remove('marker-bounce-continuous'), 3000); } }); // Add tooltip with place name marker.bindTooltip(p.name, { permanent: false, direction: 'top', offset: [0, -30], className: 'place-tooltip' }); markers[p.id] = marker; visibleOnMap[p.id] = num; } document.addEventListener('click', function(e) { // Check for place-item click const placeItem = e.target.closest('.place-item'); if (placeItem) { const btn = e.target.closest('.place-item-btn'); if (btn) { // Handle button clicks e.stopPropagation(); const action = btn.dataset.action; const id = btn.dataset.id; if (action === 'map') { togglePlaceOnMap(id, e); } else if (action === 'save') { toggleQuickSave(id); } } else { // Handle row click - open detail and highlight marker const id = placeItem.dataset.id; if (id) { openDetailById(id); highlightMarker(id); } } } }); // Hover effect for place items - bounce marker if on map document.addEventListener('mouseover', function(e) { const placeItem = e.target.closest('.place-item'); if (placeItem) { const id = placeItem.dataset.id; if (id && visibleOnMap[id]) { bounceMarker(id); } } }); // Bounce a marker by ID function bounceMarker(id) { if (!markers[id]) return; const marker = markers[id]; const icon = marker._icon; if (icon) { const markerDiv = icon.querySelector('.map-marker') || icon; markerDiv.classList.remove('bounce'); // Force reflow to restart animation void markerDiv.offsetWidth; markerDiv.classList.add('bounce'); setTimeout(() => markerDiv.classList.remove('bounce'), 800); } } // Highlight marker and center map function highlightMarker(id) { const p = allPlaces.find(x => x.id === id); if (!p) return; // Add to map if not already if (!visibleOnMap[id]) { addMarker(p, nextNum); nextNum++; renderContent(); } // Center and zoom if (p.lat && p.lng && p.lat !== 0 && p.lng !== 0) { map.flyTo([p.lat, p.lng], 15, {duration: 0.5}); } // Bounce setTimeout(() => bounceMarker(id), 300); } function showOnMap(id){closeDetail();const p=allPlaces.find(x=>x.id===id);if(p){if(!visibleOnMap[id]){addMarker(p,nextNum);nextNum++;renderContent()}map.flyTo([p.lat,p.lng],15,{duration:.5});if(window.innerWidth<900)document.getElementById('sidebar').classList.add('collapsed')}} function toggleSave(id){const idx=savedPlaces.indexOf(id);if(idx>-1)savedPlaces.splice(idx,1);else savedPlaces.push(id);localStorage.setItem('vs_saved',JSON.stringify(savedPlaces));if(currentPlace?.id===id)openDetail(currentPlace)} function sharePlace(){if(!currentPlace)return;const p=currentPlace;const url=`https://maps.google.com/?q=${p.lat},${p.lng}`;if(navigator.share)navigator.share({title:p.name,text:p.desc,url});else{navigator.clipboard.writeText(`${p.name}\n${url}`);alert('Copied!')}} function openGallery(){galleryIdx=0;updateGallery();document.getElementById('galleryModal').classList.add('open')} function closeGallery(){document.getElementById('galleryModal').classList.remove('open')} function prevImg(){galleryIdx=(galleryIdx-1+galleryImgs.length)%galleryImgs.length;updateGallery()} function nextImg(){galleryIdx=(galleryIdx+1)%galleryImgs.length;updateGallery()} function updateGallery(){document.getElementById('galleryImg').src=galleryImgs[galleryIdx];document.getElementById('galleryCounter').textContent=`${galleryIdx+1}/${galleryImgs.length}`} document.addEventListener('keydown',e=>{if(!document.getElementById('galleryModal').classList.contains('open'))return;if(e.key==='Escape')closeGallery();if(e.key==='ArrowLeft')prevImg();if(e.key==='ArrowRight')nextImg()}) document.addEventListener('click',e=>{if(!e.target.closest('.save-dropdown')){document.querySelectorAll('.save-dropdown-menu').forEach(m=>m.classList.remove('show'))}}) function debounce(fn,wait){let t;return(...args)=>{clearTimeout(t);t=setTimeout(()=>fn(...args),wait)}} // User & login functions let currentUser = JSON.parse(localStorage.getItem('vs_user') || 'null'); function showLoginModal(){ document.getElementById('loginEmail').value=''; document.getElementById('loginPassword').value=''; document.getElementById('loginModal').classList.add('open'); } function closeModal(id){ document.getElementById(id).classList.remove('open'); } function doLogin(){ const email = document.getElementById('loginEmail').value.trim(); const pass = document.getElementById('loginPassword').value; if(!email || !pass){alert('Please enter email and password');return} currentUser = {email, name: email.split('@')[0]}; localStorage.setItem('vs_user', JSON.stringify(currentUser)); localStorage.setItem('vs_saved_' + currentUser.email, JSON.stringify(savedItems)); closeModal('loginModal'); updateLoginUI(); alert('Signed in! Your trip is now saved to your account.'); } function doSignup(){ const email = document.getElementById('loginEmail').value.trim(); const pass = document.getElementById('loginPassword').value; if(!email || !pass){alert('Please enter email and password');return} if(pass.length < 6){alert('Password must be at least 6 characters');return} currentUser = {email, name: email.split('@')[0]}; localStorage.setItem('vs_user', JSON.stringify(currentUser)); localStorage.setItem('vs_saved_' + currentUser.email, JSON.stringify(savedItems)); closeModal('loginModal'); updateLoginUI(); alert('Account created! Your trip is now saved.'); } function doLogout(){ if(confirm('Sign Out? Your saved places will remain on this device.')){ currentUser = null; localStorage.removeItem('vs_user'); updateLoginUI(); } } function updateLoginUI(){ const status = document.getElementById('loginStatus'); const btn = document.getElementById('loginBtn'); if(currentUser){ status.innerHTML = `
${currentUser.name.charAt(0).toUpperCase()}
${currentUser.name}`; btn.textContent = 'Sign Out'; btn.className = 'login-btn logout'; btn.onclick = doLogout; // Sync from cloud if available const cloudSaved = localStorage.getItem('vs_saved_' + currentUser.email); if(cloudSaved){ const cloudItems = JSON.parse(cloudSaved); if(Object.keys(cloudItems).length > Object.keys(savedItems).length){ savedItems = cloudItems; localStorage.setItem('vs_saved_items', JSON.stringify(savedItems)); renderContent(); } } } else { status.innerHTML = `Save your trip`; btn.textContent = 'Sign In'; btn.className = 'login-btn'; btn.onclick = showLoginModal; } } function useCurrentLocation(){ if(navigator.geolocation){ navigator.geolocation.getCurrentPosition(pos => { userLat = pos.coords.latitude; userLng = pos.coords.longitude; document.getElementById('locationInput').value = `${userLat.toFixed(4)}, ${userLng.toFixed(4)}`; renderContent(); // Re-render to show distances }, err => { alert('Could not get location. Please enter manually.'); }); } else { alert('Geolocation not supported'); } } // User location - default to Pigeon Forge center let userLat = 35.7884; let userLng = -83.5543; // Calculate distance between two points (Haversine formula) function getDistance(lat1, lng1, lat2, lng2) { const R = 3959; // Earth's radius in miles const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng/2) * Math.sin(dLng/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } // Format distance for display function formatDistance(miles) { if (miles < 0.1) return '< 0.1 mi'; if (miles < 10) return miles.toFixed(1) + ' mi'; return Math.round(miles) + ' mi'; } document.addEventListener('DOMContentLoaded', () => { init(); updateLoginUI(); // Sidebar resize functionality const sidebar = document.getElementById('sidebar'); const resizeHandle = document.getElementById('sidebarResize'); let isResizing = false; let startX, startWidth; resizeHandle.addEventListener('mousedown', (e) => { isResizing = true; startX = e.clientX; startWidth = sidebar.offsetWidth; resizeHandle.classList.add('active'); document.body.style.cursor = 'ew-resize'; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isResizing) return; const diff = e.clientX - startX; const newWidth = Math.min(Math.max(startWidth + diff, 320), 800); sidebar.style.width = newWidth + 'px'; map.invalidateSize(); }); document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; resizeHandle.classList.remove('active'); document.body.style.cursor = ''; document.body.style.userSelect = ''; } }); }); // Hover on sidebar place items triggers marker bounce document.addEventListener('mouseover', function(e) { var placeItem = e.target.closest('.place-item'); if (placeItem && placeItem.dataset && placeItem.dataset.id) { var id = placeItem.dataset.id; if (visibleOnMap[id]) { bounceMarker(id); } } }); // Bounce marker when place is selected function bounceMarkerById(placeId) { if(placeMarkers[placeId]) { const marker = placeMarkers[placeId]; const el = marker.getElement(); if(el) { el.classList.remove('marker-bounce-continuous'); void el.offsetWidth; // Trigger reflow el.classList.add('marker-bounce-continuous'); setTimeout(() => el.classList.remove('marker-bounce-continuous'), 3000); } } }