INITIAL COMMIT
This commit is contained in:
		
							
								
								
									
										619
									
								
								app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										619
									
								
								app.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,619 @@
 | 
				
			|||||||
 | 
					// Budapest Sights Data
 | 
				
			||||||
 | 
					const sightsData = [
 | 
				
			||||||
 | 
					  { id: 1, name: "Hungarian Parliament Building", category: "Historic Landmark", description: "One of Europe's most stunning government buildings, located right on the Danube. Features neo-Gothic architecture with over 40 kg of gold, 691 rooms, and 96m height commemorating Hungary's founding year 896.", latitude: 47.5076, longitude: 19.0458, admission: "From €30, guided tours available in English", hours: "Various tour times, advance booking recommended", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 2, name: "Fisherman's Bastion", category: "Historic Landmark", description: "Fairytale-like neo-Romanesque terrace with seven towers representing the seven Magyar tribes. Offers iconic views of Parliament and the Danube. Lower terrace free 24/7, upper ramparts require ticket.", latitude: 47.5023, longitude: 19.0350, admission: "Lower terrace free, upper ramparts 1,500 HUF (€4)", hours: "Lower 24/7, Upper 9:00-21:00 (summer), 9:00-19:00 (rest of year)", visit_duration: "45-60 minutes" },
 | 
				
			||||||
 | 
					  { id: 3, name: "Buda Castle", category: "Historic Landmark", description: "UNESCO World Heritage Site, once home to Hungarian kings. Now houses the Hungarian National Gallery and Budapest History Museum. Beautiful courtyards and terraces with stunning views of Pest.", latitude: 47.4967, longitude: 19.0397, admission: "Grounds free, museums require separate tickets", hours: "Grounds 24/7, museums vary", visit_duration: "90-120 minutes" },
 | 
				
			||||||
 | 
					  { id: 4, name: "Matthias Church", category: "Religious Site", description: "Stunning Gothic church with colorful Zsolnay tiled roof, originally built in 1015. Hosted royal coronations and has excellent acoustics for classical concerts.", latitude: 47.5019, longitude: 19.0348, admission: "3,100 HUF (€8) adults, bell tower extra 3,700 HUF", hours: "Mon-Sat 9:00-17:45, Sun 13:00-17:45", visit_duration: "45-60 minutes" },
 | 
				
			||||||
 | 
					  { id: 5, name: "Chain Bridge (Széchenyi Lánchíd)", category: "Historic Landmark", description: "Budapest's oldest permanent bridge (1849), first to connect Buda and Pest. Iconic symbol with stone lions, suspension chains, beautiful when illuminated at night.", latitude: 47.4983, longitude: 19.0436, admission: "Free", hours: "24/7", visit_duration: "20-30 minutes" },
 | 
				
			||||||
 | 
					  { id: 6, name: "St. Stephen's Basilica", category: "Religious Site", description: "Neoclassical church named after Hungary's first king, whose mummified right hand is preserved here. 96m height (same as Parliament). Panoramic dome with 360° views.", latitude: 47.5009, longitude: 19.0534, admission: "6,000 HUF (€15) including dome, 20% discount with Budapest Card", hours: "Mon-Sat 9:00-17:45, Sun 13:00-17:45", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 7, name: "Heroes' Square", category: "Monument", description: "Iconic square featuring the Millennium Monument with Archangel Gabriel atop central column, surrounded by statues of Hungary's national leaders. Flanked by Museum of Fine Arts and Műcsarnok.", latitude: 47.5146, longitude: 19.0777, admission: "Free", hours: "24/7", visit_duration: "30-45 minutes" },
 | 
				
			||||||
 | 
					  { id: 8, name: "Széchenyi Thermal Baths", category: "Thermal Bath", description: "Europe's largest medicinal bath complex with 18 pools (3 outdoor). Neo-baroque architecture, waters from 1,246m deep well. Famous for chess players in outdoor pools, even in winter.", latitude: 47.5196, longitude: 19.0815, admission: "From 8,600 HUF (€22) weekdays, 9,200 HUF (€23) weekends", hours: "6:00-22:00 daily", visit_duration: "2-4 hours" },
 | 
				
			||||||
 | 
					  { id: 9, name: "Gellért Thermal Baths", category: "Thermal Bath", description: "Stunning Art Nouveau baths (1912-1918) at foot of Gellért Hill. Famous for blue Zsolnay tiles, marble columns, stained-glass ceilings. 10 pools with wave pool in summer.", latitude: 47.4826, longitude: 19.0523, admission: "From 17,700 HUF (€45)", hours: "9:00-19:00 daily", visit_duration: "2-3 hours" },
 | 
				
			||||||
 | 
					  { id: 10, name: "Gellért Hill & Citadel", category: "Viewpoint", description: "235m high hill with panoramic city views. Features Citadel fortress (1851) and Liberty Statue. Easy hike with stunning vistas, especially at sunset.", latitude: 47.4867, longitude: 19.0463, admission: "Free", hours: "24/7", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 11, name: "Great Market Hall (Central Market)", category: "Market", description: "Largest and oldest indoor market (1897) with neo-Gothic design and Zsolnay tile roof. Three floors with fresh produce, local foods, souvenirs, and traditional Hungarian street food on upper level.", latitude: 47.4870, longitude: 19.0583, admission: "Free entry", hours: "Mon 6:00-17:00, Tue-Fri 6:00-18:00, Sat 6:00-15:00, Sun closed", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 12, name: "Shoes on the Danube Bank", category: "Memorial", description: "Poignant Holocaust memorial with 60 pairs of iron shoes honoring 3,500 Jews shot into the Danube by Arrow Cross militia (1944-45). Located 300m south of Parliament.", latitude: 47.5050, longitude: 19.0451, admission: "Free", hours: "24/7", visit_duration: "15-30 minutes" },
 | 
				
			||||||
 | 
					  { id: 13, name: "Dohány Street Synagogue", category: "Religious Site", description: "Europe's largest synagogue (1859) in Moorish Revival style, seats 3,000. Complex includes Heroes' Temple, Jewish Museum, memorial garden. Located at former ghetto border.", latitude: 47.4959, longitude: 19.0618, admission: "Tickets required, guided tours available", hours: "Weekdays only, closed Saturdays and Jewish holidays", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 14, name: "Hungarian State Opera House", category: "Cultural Venue", description: "Neo-Renaissance masterpiece (1884) by Miklós Ybl on Andrássy Avenue. Interior decorated with 7kg of gold. Offers ballet and opera performances, guided tours available.", latitude: 47.5027, longitude: 19.0586, admission: "Tours and performances vary", hours: "Tour times vary, performances evenings", visit_duration: "45-60 minutes (tour) or 2-3 hours (performance)" },
 | 
				
			||||||
 | 
					  { id: 15, name: "Szimpla Kert", category: "Ruin Bar", description: "Original and most famous ruin bar (2002) in abandoned building. Eclectic decor with vintage items, multiple rooms, outdoor courtyard. Sunday farmers market. Free concerts.", latitude: 47.4968, longitude: 19.0631, admission: "Free entry, pay for drinks", hours: "Daily 12:00-late, Sunday market 9:00-14:00", visit_duration: "90-120 minutes" },
 | 
				
			||||||
 | 
					  { id: 16, name: "Vajdahunyad Castle", category: "Historic Landmark", description: "Eclectic castle in City Park blending Romanesque, Gothic, Renaissance, and Baroque styles. Originally built for 1896 Millennium Exhibition, rebuilt in stone due to popularity.", latitude: 47.5148, longitude: 19.0826, admission: "Grounds free, museum inside requires ticket", hours: "Grounds 24/7, museum varies", visit_duration: "45-60 minutes" },
 | 
				
			||||||
 | 
					  { id: 17, name: "Andrássy Avenue", category: "Street/District", description: "Budapest's most elegant boulevard, UNESCO World Heritage Site. Lined with neo-Renaissance townhouses, luxury shops, cafes. Connects city center to Heroes' Square.", latitude: 47.5035, longitude: 19.0640, admission: "Free", hours: "24/7", visit_duration: "60-90 minutes (walking)" },
 | 
				
			||||||
 | 
					  { id: 18, name: "Elizabeth Lookout Tower", category: "Viewpoint", description: "Fairytale stone tower on János Hill offering 360° panoramic views of Budapest and surrounding hills. On clear days, can spot Mátra mountains.", latitude: 47.5180, longitude: 18.9570, admission: "Small fee", hours: "Varies by season", visit_duration: "45-60 minutes" },
 | 
				
			||||||
 | 
					  { id: 19, name: "Váci Street", category: "Shopping Street", description: "Famous pedestrian shopping street running through city center. Filled with shops, restaurants, cafes. Connects to Great Market Hall at southern end.", latitude: 47.4950, longitude: 19.0525, admission: "Free", hours: "24/7 (shops vary)", visit_duration: "60-90 minutes" },
 | 
				
			||||||
 | 
					  { id: 20, name: "City Park (Városliget)", category: "Park", description: "Large public park with lake, Vajdahunyad Castle, Széchenyi Baths, Heroes' Square. Popular for walking, boating in summer, ice skating in winter.", latitude: 47.5157, longitude: 19.0833, admission: "Free", hours: "24/7", visit_duration: "90-120 minutes" }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// App state
 | 
				
			||||||
 | 
					let map;
 | 
				
			||||||
 | 
					let markers = [];
 | 
				
			||||||
 | 
					let selectedSights = new Set();
 | 
				
			||||||
 | 
					let hotelMarker = null;
 | 
				
			||||||
 | 
					let routePolyline = null;
 | 
				
			||||||
 | 
					let hotelLocation = null;
 | 
				
			||||||
 | 
					let isPlacingHotel = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Constants
 | 
				
			||||||
 | 
					const WALKING_SPEED_KMH = 5.0;
 | 
				
			||||||
 | 
					const WALKING_SPEED_MPH = 3.1;
 | 
				
			||||||
 | 
					const STREET_FACTOR = 1.3;
 | 
				
			||||||
 | 
					const BUDAPEST_CENTER = [47.4979, 19.0402];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Category colors for markers
 | 
				
			||||||
 | 
					const categoryColors = {
 | 
				
			||||||
 | 
					  "Historic Landmark": "#dc2626",
 | 
				
			||||||
 | 
					  "Religious Site": "#fbbf24",
 | 
				
			||||||
 | 
					  "Thermal Bath": "#1e40af",
 | 
				
			||||||
 | 
					  "Viewpoint": "#059669",
 | 
				
			||||||
 | 
					  "Cultural Venue": "#7c3aed",
 | 
				
			||||||
 | 
					  "Market": "#ea580c",
 | 
				
			||||||
 | 
					  "Memorial": "#6b7280",
 | 
				
			||||||
 | 
					  "Ruin Bar": "#0891b2",
 | 
				
			||||||
 | 
					  "Street/District": "#0891b2",
 | 
				
			||||||
 | 
					  "Shopping Street": "#0891b2",
 | 
				
			||||||
 | 
					  "Park": "#059669",
 | 
				
			||||||
 | 
					  "Monument": "#6b7280"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Category icons
 | 
				
			||||||
 | 
					const categoryIcons = {
 | 
				
			||||||
 | 
					  "Historic Landmark": "🏛️",
 | 
				
			||||||
 | 
					  "Religious Site": "⛪",
 | 
				
			||||||
 | 
					  "Thermal Bath": "🛁",
 | 
				
			||||||
 | 
					  "Viewpoint": "🏔️",
 | 
				
			||||||
 | 
					  "Cultural Venue": "🎭",
 | 
				
			||||||
 | 
					  "Market": "🏪",
 | 
				
			||||||
 | 
					  "Memorial": "🕊️",
 | 
				
			||||||
 | 
					  "Ruin Bar": "🍺",
 | 
				
			||||||
 | 
					  "Street/District": "🛣️",
 | 
				
			||||||
 | 
					  "Shopping Street": "🛍️",
 | 
				
			||||||
 | 
					  "Park": "🌳",
 | 
				
			||||||
 | 
					  "Monument": "🗿"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Initialize the application
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					  initializeMap();
 | 
				
			||||||
 | 
					  renderSightsList();
 | 
				
			||||||
 | 
					  setupEventListeners();
 | 
				
			||||||
 | 
					  showInstructions();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Initialize Leaflet map
 | 
				
			||||||
 | 
					function initializeMap() {
 | 
				
			||||||
 | 
					  map = L.map('map').setView(BUDAPEST_CENTER, 13);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 | 
				
			||||||
 | 
					    attribution: '© OpenStreetMap contributors',
 | 
				
			||||||
 | 
					    maxZoom: 18
 | 
				
			||||||
 | 
					  }).addTo(map);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Add all sight markers
 | 
				
			||||||
 | 
					  sightsData.forEach((sight, index) => {
 | 
				
			||||||
 | 
					    addSightMarker(sight, index + 1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Handle map clicks for hotel placement
 | 
				
			||||||
 | 
					  map.on('click', handleMapClick);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Add sight marker to map
 | 
				
			||||||
 | 
					function addSightMarker(sight, number) {
 | 
				
			||||||
 | 
					  const category = sight.category;
 | 
				
			||||||
 | 
					  const color = categoryColors[category] || categoryColors["other"];
 | 
				
			||||||
 | 
					  const icon = categoryIcons[category] || "📍";
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const customIcon = L.divIcon({
 | 
				
			||||||
 | 
					    html: `<div class="custom-marker" style="background-color: ${color}; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">${number}</div>`,
 | 
				
			||||||
 | 
					    className: 'custom-div-icon',
 | 
				
			||||||
 | 
					    iconSize: [30, 30],
 | 
				
			||||||
 | 
					    iconAnchor: [15, 15],
 | 
				
			||||||
 | 
					    popupAnchor: [0, -15]
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const marker = L.marker([sight.latitude, sight.longitude], { icon: customIcon })
 | 
				
			||||||
 | 
					    .bindPopup(createPopupContent(sight))
 | 
				
			||||||
 | 
					    .addTo(map);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  marker.sightData = sight;
 | 
				
			||||||
 | 
					  markers.push(marker);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create popup content for markers
 | 
				
			||||||
 | 
					function createPopupContent(sight) {
 | 
				
			||||||
 | 
					  const icon = categoryIcons[sight.category] || "📍";
 | 
				
			||||||
 | 
					  return `
 | 
				
			||||||
 | 
					    <div class="popup-content">
 | 
				
			||||||
 | 
					      <h4>${icon} ${sight.name}</h4>
 | 
				
			||||||
 | 
					      <p><strong>Category:</strong> ${sight.category}</p>
 | 
				
			||||||
 | 
					      <p><strong>Admission:</strong> ${sight.admission}</p>
 | 
				
			||||||
 | 
					      <p><strong>Hours:</strong> ${sight.hours}</p>
 | 
				
			||||||
 | 
					      <p><strong>Duration:</strong> ${sight.visit_duration}</p>
 | 
				
			||||||
 | 
					      <button onclick="openSightModal(${sight.id})" class="btn btn--primary btn--sm">More Details</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle map clicks for hotel placement
 | 
				
			||||||
 | 
					function handleMapClick(e) {
 | 
				
			||||||
 | 
					  if (isPlacingHotel) {
 | 
				
			||||||
 | 
					    placeHotel(e.latlng);
 | 
				
			||||||
 | 
					    isPlacingHotel = false;
 | 
				
			||||||
 | 
					    document.getElementById('placeHotelBtn').textContent = '📍 Place My Hotel';
 | 
				
			||||||
 | 
					    document.body.style.cursor = 'default';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Place hotel marker
 | 
				
			||||||
 | 
					function placeHotel(latlng) {
 | 
				
			||||||
 | 
					  // Remove existing hotel marker
 | 
				
			||||||
 | 
					  if (hotelMarker) {
 | 
				
			||||||
 | 
					    map.removeLayer(hotelMarker);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const hotelIcon = L.divIcon({
 | 
				
			||||||
 | 
					    html: `<div class="custom-marker" style="background-color: #ec4899; color: white; border-radius: 50%; width: 35px; height: 35px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 16px; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">🏨</div>`,
 | 
				
			||||||
 | 
					    className: 'custom-div-icon',
 | 
				
			||||||
 | 
					    iconSize: [35, 35],
 | 
				
			||||||
 | 
					    iconAnchor: [17.5, 17.5],
 | 
				
			||||||
 | 
					    popupAnchor: [0, -17.5]
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  hotelMarker = L.marker(latlng, { icon: hotelIcon })
 | 
				
			||||||
 | 
					    .bindPopup('<div><h4>🏨 Your Hotel</h4><p>Starting point for your route</p></div>')
 | 
				
			||||||
 | 
					    .addTo(map);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  hotelLocation = latlng;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Show hotel info
 | 
				
			||||||
 | 
					  document.getElementById('hotelInfo').classList.remove('hidden');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Recalculate route if sights are selected
 | 
				
			||||||
 | 
					  if (selectedSights.size > 0) {
 | 
				
			||||||
 | 
					    updateRouteDisplay();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Remove hotel marker
 | 
				
			||||||
 | 
					function removeHotel() {
 | 
				
			||||||
 | 
					  if (hotelMarker) {
 | 
				
			||||||
 | 
					    map.removeLayer(hotelMarker);
 | 
				
			||||||
 | 
					    hotelMarker = null;
 | 
				
			||||||
 | 
					    hotelLocation = null;
 | 
				
			||||||
 | 
					    document.getElementById('hotelInfo').classList.add('hidden');
 | 
				
			||||||
 | 
					    updateRouteDisplay();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Render sights list in sidebar
 | 
				
			||||||
 | 
					function renderSightsList(filter = 'all', searchTerm = '') {
 | 
				
			||||||
 | 
					  const sightsList = document.getElementById('sightsList');
 | 
				
			||||||
 | 
					  sightsList.innerHTML = '';
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const filteredSights = sightsData.filter(sight => {
 | 
				
			||||||
 | 
					    const matchesFilter = filter === 'all' || 
 | 
				
			||||||
 | 
					                         sight.category === filter || 
 | 
				
			||||||
 | 
					                         (filter === 'other' && !Object.keys(categoryColors).includes(sight.category));
 | 
				
			||||||
 | 
					    const matchesSearch = sight.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
 | 
				
			||||||
 | 
					                         sight.description.toLowerCase().includes(searchTerm.toLowerCase());
 | 
				
			||||||
 | 
					    return matchesFilter && matchesSearch;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  filteredSights.forEach(sight => {
 | 
				
			||||||
 | 
					    const sightElement = document.createElement('div');
 | 
				
			||||||
 | 
					    sightElement.className = 'sight-item';
 | 
				
			||||||
 | 
					    sightElement.innerHTML = createSightItemHTML(sight);
 | 
				
			||||||
 | 
					    sightElement.addEventListener('click', (e) => {
 | 
				
			||||||
 | 
					      if (e.target.type !== 'checkbox') {
 | 
				
			||||||
 | 
					        openSightModal(sight.id);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    sightsList.appendChild(sightElement);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create sight item HTML
 | 
				
			||||||
 | 
					function createSightItemHTML(sight) {
 | 
				
			||||||
 | 
					  const icon = categoryIcons[sight.category] || "📍";
 | 
				
			||||||
 | 
					  const isSelected = selectedSights.has(sight.id);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return `
 | 
				
			||||||
 | 
					    <div class="sight-item-header">
 | 
				
			||||||
 | 
					      <div>
 | 
				
			||||||
 | 
					        <div class="sight-item-title">${icon} ${sight.name}</div>
 | 
				
			||||||
 | 
					        <div class="sight-category">${sight.category}</div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <input type="checkbox" class="sight-checkbox" 
 | 
				
			||||||
 | 
					             ${isSelected ? 'checked' : ''} 
 | 
				
			||||||
 | 
					             onchange="toggleSightSelection(${sight.id})">
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="sight-description">${sight.description.substring(0, 120)}...</div>
 | 
				
			||||||
 | 
					    <div class="sight-details">
 | 
				
			||||||
 | 
					      <strong>Duration:</strong> ${sight.visit_duration} • 
 | 
				
			||||||
 | 
					      <strong>Admission:</strong> ${sight.admission}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Toggle sight selection for route planning
 | 
				
			||||||
 | 
					function toggleSightSelection(sightId) {
 | 
				
			||||||
 | 
					  if (selectedSights.has(sightId)) {
 | 
				
			||||||
 | 
					    selectedSights.delete(sightId);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    selectedSights.add(sightId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  updateRouteDisplay();
 | 
				
			||||||
 | 
					  renderSightsList(getCurrentFilter(), getCurrentSearch());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update route display
 | 
				
			||||||
 | 
					function updateRouteDisplay() {
 | 
				
			||||||
 | 
					  const routeStats = document.getElementById('routeStats');
 | 
				
			||||||
 | 
					  const selectedSightsContainer = document.getElementById('selectedSights');
 | 
				
			||||||
 | 
					  const downloadBtn = document.getElementById('downloadRouteBtn');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (selectedSights.size === 0) {
 | 
				
			||||||
 | 
					    routeStats.classList.add('hidden');
 | 
				
			||||||
 | 
					    selectedSightsContainer.innerHTML = '';
 | 
				
			||||||
 | 
					    downloadBtn.classList.add('hidden');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Remove route polyline
 | 
				
			||||||
 | 
					    if (routePolyline) {
 | 
				
			||||||
 | 
					      map.removeLayer(routePolyline);
 | 
				
			||||||
 | 
					      routePolyline = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Show stats
 | 
				
			||||||
 | 
					  routeStats.classList.remove('hidden');
 | 
				
			||||||
 | 
					  downloadBtn.classList.remove('hidden');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Get selected sight objects
 | 
				
			||||||
 | 
					  const selectedSightObjects = Array.from(selectedSights).map(id => 
 | 
				
			||||||
 | 
					    sightsData.find(sight => sight.id === id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Calculate route
 | 
				
			||||||
 | 
					  const route = calculateOptimalRoute(selectedSightObjects);
 | 
				
			||||||
 | 
					  const { totalDistance, totalTime } = calculateRouteStats(route);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Update stats display
 | 
				
			||||||
 | 
					  document.getElementById('totalDistance').textContent = 
 | 
				
			||||||
 | 
					    `${totalDistance.toFixed(1)} km (${(totalDistance * 0.621371).toFixed(1)} mi)`;
 | 
				
			||||||
 | 
					  document.getElementById('totalTime').textContent = 
 | 
				
			||||||
 | 
					    `${Math.round(totalTime)} minutes`;
 | 
				
			||||||
 | 
					  document.getElementById('selectedCount').textContent = 
 | 
				
			||||||
 | 
					    `${selectedSights.size} sight${selectedSights.size !== 1 ? 's' : ''}`;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Render selected sights with distances
 | 
				
			||||||
 | 
					  renderSelectedSights(route);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Draw route on map
 | 
				
			||||||
 | 
					  drawRouteOnMap(route);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Calculate optimal route
 | 
				
			||||||
 | 
					function calculateOptimalRoute(sights) {
 | 
				
			||||||
 | 
					  if (sights.length === 0) return [];
 | 
				
			||||||
 | 
					  if (sights.length === 1) return sights;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  let startPoint = hotelLocation ? 
 | 
				
			||||||
 | 
					    { latitude: hotelLocation.lat, longitude: hotelLocation.lng } : 
 | 
				
			||||||
 | 
					    sights[0];
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const route = [];
 | 
				
			||||||
 | 
					  const remaining = [...sights];
 | 
				
			||||||
 | 
					  let current = startPoint;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // If we have a hotel, start from there
 | 
				
			||||||
 | 
					  if (hotelLocation) {
 | 
				
			||||||
 | 
					    route.push({ isHotel: true, ...current });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Greedy algorithm: always go to nearest unvisited sight
 | 
				
			||||||
 | 
					  while (remaining.length > 0) {
 | 
				
			||||||
 | 
					    let nearestIndex = 0;
 | 
				
			||||||
 | 
					    let minDistance = calculateDistance(
 | 
				
			||||||
 | 
					      current.latitude, current.longitude,
 | 
				
			||||||
 | 
					      remaining[0].latitude, remaining[0].longitude
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (let i = 1; i < remaining.length; i++) {
 | 
				
			||||||
 | 
					      const distance = calculateDistance(
 | 
				
			||||||
 | 
					        current.latitude, current.longitude,
 | 
				
			||||||
 | 
					        remaining[i].latitude, remaining[i].longitude
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (distance < minDistance) {
 | 
				
			||||||
 | 
					        minDistance = distance;
 | 
				
			||||||
 | 
					        nearestIndex = i;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const nextSight = remaining.splice(nearestIndex, 1)[0];
 | 
				
			||||||
 | 
					    route.push(nextSight);
 | 
				
			||||||
 | 
					    current = nextSight;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return route;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Calculate route statistics
 | 
				
			||||||
 | 
					function calculateRouteStats(route) {
 | 
				
			||||||
 | 
					  if (route.length <= 1) return { totalDistance: 0, totalTime: 0 };
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  let totalDistance = 0;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  for (let i = 0; i < route.length - 1; i++) {
 | 
				
			||||||
 | 
					    const current = route[i];
 | 
				
			||||||
 | 
					    const next = route[i + 1];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const distance = calculateDistance(
 | 
				
			||||||
 | 
					      current.latitude, current.longitude,
 | 
				
			||||||
 | 
					      next.latitude, next.longitude
 | 
				
			||||||
 | 
					    ) * STREET_FACTOR;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    totalDistance += distance;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const totalTime = (totalDistance / WALKING_SPEED_KMH) * 60;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return { totalDistance, totalTime };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Calculate distance between two points using Haversine formula
 | 
				
			||||||
 | 
					function calculateDistance(lat1, lon1, lat2, lon2) {
 | 
				
			||||||
 | 
					  const R = 6371; // Earth's radius in km
 | 
				
			||||||
 | 
					  const dLat = (lat2 - lat1) * Math.PI / 180;
 | 
				
			||||||
 | 
					  const dLon = (lon2 - lon1) * 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(dLon/2) * Math.sin(dLon/2);
 | 
				
			||||||
 | 
					  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
 | 
				
			||||||
 | 
					  return R * c;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Render selected sights in route order
 | 
				
			||||||
 | 
					function renderSelectedSights(route) {
 | 
				
			||||||
 | 
					  const container = document.getElementById('selectedSights');
 | 
				
			||||||
 | 
					  container.innerHTML = '';
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const sightsOnly = route.filter(item => !item.isHotel);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  sightsOnly.forEach((sight, index) => {
 | 
				
			||||||
 | 
					    const nextSight = sightsOnly[index + 1];
 | 
				
			||||||
 | 
					    const prevSight = index === 0 ? 
 | 
				
			||||||
 | 
					      (hotelLocation ? { latitude: hotelLocation.lat, longitude: hotelLocation.lng } : sight) :
 | 
				
			||||||
 | 
					      sightsOnly[index - 1];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const distance = calculateDistance(
 | 
				
			||||||
 | 
					      prevSight.latitude, prevSight.longitude,
 | 
				
			||||||
 | 
					      sight.latitude, sight.longitude
 | 
				
			||||||
 | 
					    ) * STREET_FACTOR;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const element = document.createElement('div');
 | 
				
			||||||
 | 
					    element.className = 'selected-sight';
 | 
				
			||||||
 | 
					    element.innerHTML = `
 | 
				
			||||||
 | 
					      <div class="sight-order">${index + 1}</div>
 | 
				
			||||||
 | 
					      <div class="selected-sight-info">
 | 
				
			||||||
 | 
					        <div class="selected-sight-name">${sight.name}</div>
 | 
				
			||||||
 | 
					        <div class="selected-sight-distance">
 | 
				
			||||||
 | 
					          ${index === 0 && hotelLocation ? 'From hotel: ' : 'From previous: '}
 | 
				
			||||||
 | 
					          ${distance.toFixed(1)} km (${(distance * 0.621371).toFixed(1)} mi)
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    element.addEventListener('click', () => {
 | 
				
			||||||
 | 
					      map.setView([sight.latitude, sight.longitude], 16);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    container.appendChild(element);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw route on map
 | 
				
			||||||
 | 
					function drawRouteOnMap(route) {
 | 
				
			||||||
 | 
					  // Remove existing route
 | 
				
			||||||
 | 
					  if (routePolyline) {
 | 
				
			||||||
 | 
					    map.removeLayer(routePolyline);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (route.length < 2) return;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const routeCoords = route.map(point => [point.latitude, point.longitude]);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  routePolyline = L.polyline(routeCoords, {
 | 
				
			||||||
 | 
					    color: '#2563eb',
 | 
				
			||||||
 | 
					    weight: 3,
 | 
				
			||||||
 | 
					    opacity: 0.8,
 | 
				
			||||||
 | 
					    dashArray: '10, 5'
 | 
				
			||||||
 | 
					  }).addTo(map);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Fit map to show all points
 | 
				
			||||||
 | 
					  const group = new L.featureGroup(markers.concat(routePolyline));
 | 
				
			||||||
 | 
					  if (hotelMarker) {
 | 
				
			||||||
 | 
					    group.addLayer(hotelMarker);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  map.fitBounds(group.getBounds().pad(0.1));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Open sight detail modal
 | 
				
			||||||
 | 
					function openSightModal(sightId) {
 | 
				
			||||||
 | 
					  const sight = sightsData.find(s => s.id === sightId);
 | 
				
			||||||
 | 
					  if (!sight) return;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const modal = document.getElementById('sightModal');
 | 
				
			||||||
 | 
					  const title = document.getElementById('modalTitle');
 | 
				
			||||||
 | 
					  const content = document.getElementById('modalContent');
 | 
				
			||||||
 | 
					  const checkbox = document.getElementById('modalAddToRoute');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const icon = categoryIcons[sight.category] || "📍";
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  title.textContent = `${icon} ${sight.name}`;
 | 
				
			||||||
 | 
					  checkbox.checked = selectedSights.has(sight.id);
 | 
				
			||||||
 | 
					  checkbox.onchange = () => toggleSightSelection(sight.id);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  content.innerHTML = `
 | 
				
			||||||
 | 
					    <div style="margin-bottom: 16px;">
 | 
				
			||||||
 | 
					      <strong>Category:</strong> ${sight.category}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div style="margin-bottom: 16px;">
 | 
				
			||||||
 | 
					      <p>${sight.description}</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div style="margin-bottom: 12px;">
 | 
				
			||||||
 | 
					      <strong>💰 Admission:</strong> ${sight.admission}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div style="margin-bottom: 12px;">
 | 
				
			||||||
 | 
					      <strong>🕒 Hours:</strong> ${sight.hours}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div style="margin-bottom: 12px;">
 | 
				
			||||||
 | 
					      <strong>⏱️ Recommended Duration:</strong> ${sight.visit_duration}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  modal.classList.remove('hidden');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Set up zoom button
 | 
				
			||||||
 | 
					  document.getElementById('zoomToSight').onclick = () => {
 | 
				
			||||||
 | 
					    map.setView([sight.latitude, sight.longitude], 16);
 | 
				
			||||||
 | 
					    modal.classList.add('hidden');
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup event listeners
 | 
				
			||||||
 | 
					function setupEventListeners() {
 | 
				
			||||||
 | 
					  // Search
 | 
				
			||||||
 | 
					  document.getElementById('searchInput').addEventListener('input', (e) => {
 | 
				
			||||||
 | 
					    renderSightsList(getCurrentFilter(), e.target.value);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Filter buttons
 | 
				
			||||||
 | 
					  document.querySelectorAll('.filter-btn').forEach(btn => {
 | 
				
			||||||
 | 
					    btn.addEventListener('click', (e) => {
 | 
				
			||||||
 | 
					      document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
 | 
				
			||||||
 | 
					      e.target.classList.add('active');
 | 
				
			||||||
 | 
					      renderSightsList(e.target.dataset.category, getCurrentSearch());
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Hotel placement
 | 
				
			||||||
 | 
					  document.getElementById('placeHotelBtn').addEventListener('click', () => {
 | 
				
			||||||
 | 
					    isPlacingHotel = true;
 | 
				
			||||||
 | 
					    document.getElementById('placeHotelBtn').textContent = '📍 Click on map to place hotel';
 | 
				
			||||||
 | 
					    document.body.style.cursor = 'crosshair';
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  document.getElementById('removeHotelBtn').addEventListener('click', removeHotel);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Route controls
 | 
				
			||||||
 | 
					  document.getElementById('optimizeRouteBtn').addEventListener('click', updateRouteDisplay);
 | 
				
			||||||
 | 
					  document.getElementById('clearRouteBtn').addEventListener('click', clearRoute);
 | 
				
			||||||
 | 
					  document.getElementById('downloadRouteBtn').addEventListener('click', downloadRoute);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Modal controls
 | 
				
			||||||
 | 
					  document.getElementById('closeModal').addEventListener('click', () => {
 | 
				
			||||||
 | 
					    document.getElementById('sightModal').classList.add('hidden');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  document.getElementById('closeInstructions').addEventListener('click', () => {
 | 
				
			||||||
 | 
					    document.getElementById('mapInstructions').classList.add('hidden');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Click outside modal to close
 | 
				
			||||||
 | 
					  document.getElementById('sightModal').addEventListener('click', (e) => {
 | 
				
			||||||
 | 
					    if (e.target.id === 'sightModal') {
 | 
				
			||||||
 | 
					      document.getElementById('sightModal').classList.add('hidden');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Clear route
 | 
				
			||||||
 | 
					function clearRoute() {
 | 
				
			||||||
 | 
					  selectedSights.clear();
 | 
				
			||||||
 | 
					  updateRouteDisplay();
 | 
				
			||||||
 | 
					  renderSightsList(getCurrentFilter(), getCurrentSearch());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Download route as text file
 | 
				
			||||||
 | 
					function downloadRoute() {
 | 
				
			||||||
 | 
					  if (selectedSights.size === 0) return;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const selectedSightObjects = Array.from(selectedSights).map(id => 
 | 
				
			||||||
 | 
					    sightsData.find(sight => sight.id === id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const route = calculateOptimalRoute(selectedSightObjects);
 | 
				
			||||||
 | 
					  const { totalDistance, totalTime } = calculateRouteStats(route);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  let content = "BUDAPEST TRAVEL ROUTE\n";
 | 
				
			||||||
 | 
					  content += "Generated by Budapest Travel Guide\n";
 | 
				
			||||||
 | 
					  content += "=================================\n\n";
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (hotelLocation) {
 | 
				
			||||||
 | 
					    content += "Starting Point: Your Hotel\n";
 | 
				
			||||||
 | 
					    content += `Location: ${hotelLocation.lat.toFixed(6)}, ${hotelLocation.lng.toFixed(6)}\n\n`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  content += `Total Distance: ${totalDistance.toFixed(1)} km (${(totalDistance * 0.621371).toFixed(1)} mi)\n`;
 | 
				
			||||||
 | 
					  content += `Estimated Walking Time: ${Math.round(totalTime)} minutes\n`;
 | 
				
			||||||
 | 
					  content += `Number of Sights: ${selectedSights.size}\n\n`;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const sightsOnly = route.filter(item => !item.isHotel);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  sightsOnly.forEach((sight, index) => {
 | 
				
			||||||
 | 
					    content += `${index + 1}. ${sight.name}\n`;
 | 
				
			||||||
 | 
					    content += `   Category: ${sight.category}\n`;
 | 
				
			||||||
 | 
					    content += `   Duration: ${sight.visit_duration}\n`;
 | 
				
			||||||
 | 
					    content += `   Admission: ${sight.admission}\n`;
 | 
				
			||||||
 | 
					    content += `   Hours: ${sight.hours}\n`;
 | 
				
			||||||
 | 
					    content += `   Location: ${sight.latitude.toFixed(6)}, ${sight.longitude.toFixed(6)}\n`;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (index < sightsOnly.length - 1) {
 | 
				
			||||||
 | 
					      const nextSight = sightsOnly[index + 1];
 | 
				
			||||||
 | 
					      const distance = calculateDistance(
 | 
				
			||||||
 | 
					        sight.latitude, sight.longitude,
 | 
				
			||||||
 | 
					        nextSight.latitude, nextSight.longitude
 | 
				
			||||||
 | 
					      ) * STREET_FACTOR;
 | 
				
			||||||
 | 
					      content += `   → Next: ${distance.toFixed(1)} km (${Math.round((distance / WALKING_SPEED_KMH) * 60)} min walk)\n`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    content += "\n";
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  content += "\nTips:\n";
 | 
				
			||||||
 | 
					  content += "• Budapest city center is very walkable\n";
 | 
				
			||||||
 | 
					  content += "• Buda side has hills - budget extra time\n";
 | 
				
			||||||
 | 
					  content += "• Public transport available for longer distances\n";
 | 
				
			||||||
 | 
					  content += "• Distances are estimated walking routes\n";
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const blob = new Blob([content], { type: 'text/plain' });
 | 
				
			||||||
 | 
					  const url = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  const a = document.createElement('a');
 | 
				
			||||||
 | 
					  a.href = url;
 | 
				
			||||||
 | 
					  a.download = 'budapest-route.txt';
 | 
				
			||||||
 | 
					  document.body.appendChild(a);
 | 
				
			||||||
 | 
					  a.click();
 | 
				
			||||||
 | 
					  document.body.removeChild(a);
 | 
				
			||||||
 | 
					  URL.revokeObjectURL(url);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper functions
 | 
				
			||||||
 | 
					function getCurrentFilter() {
 | 
				
			||||||
 | 
					  return document.querySelector('.filter-btn.active')?.dataset.category || 'all';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getCurrentSearch() {
 | 
				
			||||||
 | 
					  return document.getElementById('searchInput').value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showInstructions() {
 | 
				
			||||||
 | 
					  // Hide instructions after 5 seconds
 | 
				
			||||||
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
					    document.getElementById('mapInstructions').classList.add('hidden');
 | 
				
			||||||
 | 
					  }, 5000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make functions available globally for HTML onclick handlers
 | 
				
			||||||
 | 
					window.toggleSightSelection = toggleSightSelection;
 | 
				
			||||||
 | 
					window.openSightModal = openSightModal;
 | 
				
			||||||
							
								
								
									
										222
									
								
								budapest_sights_data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								budapest_sights_data.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					[
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 1,
 | 
				
			||||||
 | 
					    "name": "Hungarian Parliament Building",
 | 
				
			||||||
 | 
					    "category": "Historic Landmark",
 | 
				
			||||||
 | 
					    "description": "One of Europe's most stunning government buildings, located right on the Danube. Features neo-Gothic architecture with over 40 kg of gold, 691 rooms, and 96m height commemorating Hungary's founding year 896.",
 | 
				
			||||||
 | 
					    "latitude": 47.5076,
 | 
				
			||||||
 | 
					    "longitude": 19.0458,
 | 
				
			||||||
 | 
					    "admission": "From €30, guided tours available in English",
 | 
				
			||||||
 | 
					    "hours": "Various tour times, advance booking recommended",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 2,
 | 
				
			||||||
 | 
					    "name": "Fisherman's Bastion",
 | 
				
			||||||
 | 
					    "category": "Historic Landmark",
 | 
				
			||||||
 | 
					    "description": "Fairytale-like neo-Romanesque terrace with seven towers representing the seven Magyar tribes. Offers iconic views of Parliament and the Danube. Lower terrace free 24/7, upper ramparts require ticket.",
 | 
				
			||||||
 | 
					    "latitude": 47.5023,
 | 
				
			||||||
 | 
					    "longitude": 19.035,
 | 
				
			||||||
 | 
					    "admission": "Lower terrace free, upper ramparts 1,500 HUF (€4)",
 | 
				
			||||||
 | 
					    "hours": "Lower: 24/7, Upper: 9:00-21:00 (summer), 9:00-19:00 (rest of year)",
 | 
				
			||||||
 | 
					    "visit_duration": "45-60 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 3,
 | 
				
			||||||
 | 
					    "name": "Buda Castle",
 | 
				
			||||||
 | 
					    "category": "Historic Landmark",
 | 
				
			||||||
 | 
					    "description": "UNESCO World Heritage Site, once home to Hungarian kings. Now houses the Hungarian National Gallery and Budapest History Museum. Beautiful courtyards and terraces with stunning views of Pest.",
 | 
				
			||||||
 | 
					    "latitude": 47.4967,
 | 
				
			||||||
 | 
					    "longitude": 19.0397,
 | 
				
			||||||
 | 
					    "admission": "Grounds free, museums require separate tickets",
 | 
				
			||||||
 | 
					    "hours": "Grounds 24/7, museums vary",
 | 
				
			||||||
 | 
					    "visit_duration": "90-120 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 4,
 | 
				
			||||||
 | 
					    "name": "Matthias Church",
 | 
				
			||||||
 | 
					    "category": "Religious Site",
 | 
				
			||||||
 | 
					    "description": "Stunning Gothic church with colorful Zsolnay tiled roof, originally built in 1015. Hosted royal coronations and has excellent acoustics for classical concerts.",
 | 
				
			||||||
 | 
					    "latitude": 47.5019,
 | 
				
			||||||
 | 
					    "longitude": 19.0348,
 | 
				
			||||||
 | 
					    "admission": "3,100 HUF (€8) adults, bell tower extra 3,700 HUF",
 | 
				
			||||||
 | 
					    "hours": "Mon-Sat 9:00-17:45, Sun 13:00-17:45",
 | 
				
			||||||
 | 
					    "visit_duration": "45-60 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 5,
 | 
				
			||||||
 | 
					    "name": "Chain Bridge (Széchenyi Lánchíd)",
 | 
				
			||||||
 | 
					    "category": "Historic Landmark",
 | 
				
			||||||
 | 
					    "description": "Budapest's oldest permanent bridge (1849), first to connect Buda and Pest. Iconic symbol with stone lions, suspension chains, beautiful when illuminated at night.",
 | 
				
			||||||
 | 
					    "latitude": 47.4983,
 | 
				
			||||||
 | 
					    "longitude": 19.0436,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "20-30 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 6,
 | 
				
			||||||
 | 
					    "name": "St. Stephen's Basilica",
 | 
				
			||||||
 | 
					    "category": "Religious Site",
 | 
				
			||||||
 | 
					    "description": "Neoclassical church named after Hungary's first king, whose mummified right hand is preserved here. 96m height (same as Parliament). Panoramic dome with 360° views.",
 | 
				
			||||||
 | 
					    "latitude": 47.5009,
 | 
				
			||||||
 | 
					    "longitude": 19.0534,
 | 
				
			||||||
 | 
					    "admission": "6,000 HUF (€15) including dome, 20% discount with Budapest Card",
 | 
				
			||||||
 | 
					    "hours": "Mon-Sat 9:00-17:45, Sun 13:00-17:45",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 7,
 | 
				
			||||||
 | 
					    "name": "Heroes' Square",
 | 
				
			||||||
 | 
					    "category": "Monument",
 | 
				
			||||||
 | 
					    "description": "Iconic square featuring the Millennium Monument with Archangel Gabriel atop central column, surrounded by statues of Hungary's national leaders. Flanked by Museum of Fine Arts and Műcsarnok.",
 | 
				
			||||||
 | 
					    "latitude": 47.5146,
 | 
				
			||||||
 | 
					    "longitude": 19.0777,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "30-45 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 8,
 | 
				
			||||||
 | 
					    "name": "Széchenyi Thermal Baths",
 | 
				
			||||||
 | 
					    "category": "Thermal Bath",
 | 
				
			||||||
 | 
					    "description": "Europe's largest medicinal bath complex with 18 pools (3 outdoor). Neo-baroque architecture, waters from 1,246m deep well. Famous for chess players in outdoor pools, even in winter.",
 | 
				
			||||||
 | 
					    "latitude": 47.5196,
 | 
				
			||||||
 | 
					    "longitude": 19.0815,
 | 
				
			||||||
 | 
					    "admission": "From 8,600 HUF (€22) weekdays, 9,200 HUF (€23) weekends",
 | 
				
			||||||
 | 
					    "hours": "6:00-22:00 daily",
 | 
				
			||||||
 | 
					    "visit_duration": "2-4 hours"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 9,
 | 
				
			||||||
 | 
					    "name": "Gellért Thermal Baths",
 | 
				
			||||||
 | 
					    "category": "Thermal Bath",
 | 
				
			||||||
 | 
					    "description": "Stunning Art Nouveau baths (1912-1918) at foot of Gellért Hill. Famous for blue Zsolnay tiles, marble columns, stained-glass ceilings. 10 pools with wave pool in summer.",
 | 
				
			||||||
 | 
					    "latitude": 47.4826,
 | 
				
			||||||
 | 
					    "longitude": 19.0523,
 | 
				
			||||||
 | 
					    "admission": "From 17,700 HUF (€45)",
 | 
				
			||||||
 | 
					    "hours": "9:00-19:00 daily",
 | 
				
			||||||
 | 
					    "visit_duration": "2-3 hours"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 10,
 | 
				
			||||||
 | 
					    "name": "Gellért Hill & Citadel",
 | 
				
			||||||
 | 
					    "category": "Viewpoint",
 | 
				
			||||||
 | 
					    "description": "235m high hill with panoramic city views. Features Citadel fortress (1851) and Liberty Statue. Easy hike with stunning vistas, especially at sunset.",
 | 
				
			||||||
 | 
					    "latitude": 47.4867,
 | 
				
			||||||
 | 
					    "longitude": 19.0463,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 11,
 | 
				
			||||||
 | 
					    "name": "Great Market Hall (Central Market)",
 | 
				
			||||||
 | 
					    "category": "Market",
 | 
				
			||||||
 | 
					    "description": "Largest and oldest indoor market (1897) with neo-Gothic design and Zsolnay tile roof. Three floors with fresh produce, local foods, souvenirs, and traditional Hungarian street food on upper level.",
 | 
				
			||||||
 | 
					    "latitude": 47.487,
 | 
				
			||||||
 | 
					    "longitude": 19.0583,
 | 
				
			||||||
 | 
					    "admission": "Free entry",
 | 
				
			||||||
 | 
					    "hours": "Mon 6:00-17:00, Tue-Fri 6:00-18:00, Sat 6:00-15:00, Sun closed",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 12,
 | 
				
			||||||
 | 
					    "name": "Shoes on the Danube Bank",
 | 
				
			||||||
 | 
					    "category": "Memorial",
 | 
				
			||||||
 | 
					    "description": "Poignant Holocaust memorial with 60 pairs of iron shoes honoring 3,500 Jews shot into the Danube by Arrow Cross militia (1944-45). Located 300m south of Parliament.",
 | 
				
			||||||
 | 
					    "latitude": 47.505,
 | 
				
			||||||
 | 
					    "longitude": 19.0451,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "15-30 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 13,
 | 
				
			||||||
 | 
					    "name": "Dohány Street Synagogue",
 | 
				
			||||||
 | 
					    "category": "Religious Site",
 | 
				
			||||||
 | 
					    "description": "Europe's largest synagogue (1859) in Moorish Revival style, seats 3,000. Complex includes Heroes' Temple, Jewish Museum, memorial garden. Located at former ghetto border.",
 | 
				
			||||||
 | 
					    "latitude": 47.4959,
 | 
				
			||||||
 | 
					    "longitude": 19.0618,
 | 
				
			||||||
 | 
					    "admission": "Tickets required, guided tours available",
 | 
				
			||||||
 | 
					    "hours": "Weekdays only, closed Saturdays and Jewish holidays",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 14,
 | 
				
			||||||
 | 
					    "name": "Hungarian State Opera House",
 | 
				
			||||||
 | 
					    "category": "Cultural Venue",
 | 
				
			||||||
 | 
					    "description": "Neo-Renaissance masterpiece (1884) by Miklós Ybl on Andrássy Avenue. Interior decorated with 7kg of gold. Offers ballet and opera performances, guided tours available.",
 | 
				
			||||||
 | 
					    "latitude": 47.5027,
 | 
				
			||||||
 | 
					    "longitude": 19.0586,
 | 
				
			||||||
 | 
					    "admission": "Tours and performances vary",
 | 
				
			||||||
 | 
					    "hours": "Tour times vary, performances evenings",
 | 
				
			||||||
 | 
					    "visit_duration": "45-60 minutes (tour) or 2-3 hours (performance)"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 15,
 | 
				
			||||||
 | 
					    "name": "Szimpla Kert",
 | 
				
			||||||
 | 
					    "category": "Ruin Bar",
 | 
				
			||||||
 | 
					    "description": "Original and most famous ruin bar (2002) in abandoned building. Eclectic decor with vintage items, multiple rooms, outdoor courtyard. Sunday farmers market. Free concerts.",
 | 
				
			||||||
 | 
					    "latitude": 47.4968,
 | 
				
			||||||
 | 
					    "longitude": 19.0631,
 | 
				
			||||||
 | 
					    "admission": "Free entry, pay for drinks",
 | 
				
			||||||
 | 
					    "hours": "Daily 12:00-late, Sunday market 9:00-14:00",
 | 
				
			||||||
 | 
					    "visit_duration": "90-120 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 16,
 | 
				
			||||||
 | 
					    "name": "Vajdahunyad Castle",
 | 
				
			||||||
 | 
					    "category": "Historic Landmark",
 | 
				
			||||||
 | 
					    "description": "Eclectic castle in City Park blending Romanesque, Gothic, Renaissance, and Baroque styles. Originally built for 1896 Millennium Exhibition, rebuilt in stone due to popularity.",
 | 
				
			||||||
 | 
					    "latitude": 47.5148,
 | 
				
			||||||
 | 
					    "longitude": 19.0826,
 | 
				
			||||||
 | 
					    "admission": "Grounds free, museum inside requires ticket",
 | 
				
			||||||
 | 
					    "hours": "Grounds 24/7, museum varies",
 | 
				
			||||||
 | 
					    "visit_duration": "45-60 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 17,
 | 
				
			||||||
 | 
					    "name": "Andrássy Avenue",
 | 
				
			||||||
 | 
					    "category": "Street/District",
 | 
				
			||||||
 | 
					    "description": "Budapest's most elegant boulevard, UNESCO World Heritage Site. Lined with neo-Renaissance townhouses, luxury shops, cafes. Connects city center to Heroes' Square.",
 | 
				
			||||||
 | 
					    "latitude": 47.5035,
 | 
				
			||||||
 | 
					    "longitude": 19.064,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes (walking)"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 18,
 | 
				
			||||||
 | 
					    "name": "Elizabeth Lookout Tower",
 | 
				
			||||||
 | 
					    "category": "Viewpoint",
 | 
				
			||||||
 | 
					    "description": "Fairytale stone tower on János Hill offering 360° panoramic views of Budapest and surrounding hills. On clear days, can spot Mátra mountains.",
 | 
				
			||||||
 | 
					    "latitude": 47.518,
 | 
				
			||||||
 | 
					    "longitude": 18.957,
 | 
				
			||||||
 | 
					    "admission": "Small fee",
 | 
				
			||||||
 | 
					    "hours": "Varies by season",
 | 
				
			||||||
 | 
					    "visit_duration": "45-60 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 19,
 | 
				
			||||||
 | 
					    "name": "Váci Street",
 | 
				
			||||||
 | 
					    "category": "Shopping Street",
 | 
				
			||||||
 | 
					    "description": "Famous pedestrian shopping street running through city center. Filled with shops, restaurants, cafes. Connects to Great Market Hall at southern end.",
 | 
				
			||||||
 | 
					    "latitude": 47.495,
 | 
				
			||||||
 | 
					    "longitude": 19.0525,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7 (shops vary)",
 | 
				
			||||||
 | 
					    "visit_duration": "60-90 minutes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "id": 20,
 | 
				
			||||||
 | 
					    "name": "City Park (Városliget)",
 | 
				
			||||||
 | 
					    "category": "Park",
 | 
				
			||||||
 | 
					    "description": "Large public park with lake, Vajdahunyad Castle, Széchenyi Baths, Heroes' Square. Popular for walking, boating in summer, ice skating in winter.",
 | 
				
			||||||
 | 
					    "latitude": 47.5157,
 | 
				
			||||||
 | 
					    "longitude": 19.0833,
 | 
				
			||||||
 | 
					    "admission": "Free",
 | 
				
			||||||
 | 
					    "hours": "24/7",
 | 
				
			||||||
 | 
					    "visit_duration": "90-120 minutes"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										141
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>Budapest Travel Guide - Interactive Map & Route Planner</title>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="style.css">
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="app-container">
 | 
				
			||||||
 | 
					        <!-- Header -->
 | 
				
			||||||
 | 
					        <header class="app-header">
 | 
				
			||||||
 | 
					            <div class="header-content">
 | 
				
			||||||
 | 
					                <h1><span class="icon-budapest">🏛️</span> Budapest Travel Guide</h1>
 | 
				
			||||||
 | 
					                <p>Explore the Pearl of the Danube with our interactive map & route planner</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Main Content -->
 | 
				
			||||||
 | 
					        <main class="main-content">
 | 
				
			||||||
 | 
					            <!-- Sidebar -->
 | 
				
			||||||
 | 
					            <aside class="sidebar">
 | 
				
			||||||
 | 
					                <!-- Search -->
 | 
				
			||||||
 | 
					                <div class="search-section">
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <input type="text" id="searchInput" class="form-control" placeholder="Search sights...">
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Category Filters -->
 | 
				
			||||||
 | 
					                <div class="filters-section">
 | 
				
			||||||
 | 
					                    <h3>Filter by Category</h3>
 | 
				
			||||||
 | 
					                    <div class="filter-buttons">
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn active" data-category="all">All Sights</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Historic Landmark">🏛️ Historic</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Religious Site">⛪ Religious</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Thermal Bath">🛁 Baths</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Viewpoint">🏔️ Views</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Cultural Venue">🎭 Cultural</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Market">🏪 Markets</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="Memorial">🕊️ Memorials</button>
 | 
				
			||||||
 | 
					                        <button class="btn btn--sm filter-btn" data-category="other">🔸 Other</button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Route Planning -->
 | 
				
			||||||
 | 
					                <div class="route-section">
 | 
				
			||||||
 | 
					                    <h3>My Route Planner</h3>
 | 
				
			||||||
 | 
					                    <div class="route-controls">
 | 
				
			||||||
 | 
					                        <button id="placeHotelBtn" class="btn btn--secondary btn--sm">📍 Place My Hotel</button>
 | 
				
			||||||
 | 
					                        <button id="optimizeRouteBtn" class="btn btn--primary btn--sm">⚡ Optimize Route</button>
 | 
				
			||||||
 | 
					                        <button id="clearRouteBtn" class="btn btn--outline btn--sm">🗑️ Clear Route</button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div id="hotelInfo" class="hotel-info hidden">
 | 
				
			||||||
 | 
					                        <div class="hotel-marker">🏨 Hotel Location Set</div>
 | 
				
			||||||
 | 
					                        <button id="removeHotelBtn" class="btn btn--sm btn--outline">Remove Hotel</button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div id="routeStats" class="route-stats hidden">
 | 
				
			||||||
 | 
					                        <div class="stats-row">
 | 
				
			||||||
 | 
					                            <span class="stat-label">Distance:</span>
 | 
				
			||||||
 | 
					                            <span id="totalDistance">0 km (0 mi)</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="stats-row">
 | 
				
			||||||
 | 
					                            <span class="stat-label">Walking Time:</span>
 | 
				
			||||||
 | 
					                            <span id="totalTime">0 minutes</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="stats-row">
 | 
				
			||||||
 | 
					                            <span class="stat-label">Selected:</span>
 | 
				
			||||||
 | 
					                            <span id="selectedCount">0 sights</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div id="selectedSights" class="selected-sights"></div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <button id="downloadRouteBtn" class="btn btn--primary btn--sm btn--full-width hidden">📄 Download Route</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Sights List -->
 | 
				
			||||||
 | 
					                <div class="sights-section">
 | 
				
			||||||
 | 
					                    <h3>Budapest Attractions</h3>
 | 
				
			||||||
 | 
					                    <div id="sightsList" class="sights-list"></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Getting Around Info -->
 | 
				
			||||||
 | 
					                <div class="info-section">
 | 
				
			||||||
 | 
					                    <h3>Getting Around</h3>
 | 
				
			||||||
 | 
					                    <div class="info-content">
 | 
				
			||||||
 | 
					                        <p><strong>Walking:</strong> Most attractions in city center are within 30-45 minutes walking distance.</p>
 | 
				
			||||||
 | 
					                        <p><strong>Hills:</strong> Buda side has hills - budget extra time for uphill walks.</p>
 | 
				
			||||||
 | 
					                        <p><strong>Public Transport:</strong> Efficient metro, tram, and bus system available.</p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </aside>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- Map Container -->
 | 
				
			||||||
 | 
					            <div class="map-container">
 | 
				
			||||||
 | 
					                <div id="map"></div>
 | 
				
			||||||
 | 
					                <div id="mapInstructions" class="map-instructions">
 | 
				
			||||||
 | 
					                    <div class="instruction-content">
 | 
				
			||||||
 | 
					                        <h4>🗺️ How to Use This Map</h4>
 | 
				
			||||||
 | 
					                        <ul>
 | 
				
			||||||
 | 
					                            <li>Click markers to see sight details</li>
 | 
				
			||||||
 | 
					                            <li>Check sights in sidebar to plan your route</li>
 | 
				
			||||||
 | 
					                            <li>Click "Place My Hotel" then click on map</li>
 | 
				
			||||||
 | 
					                            <li>Use filters to find specific types of attractions</li>
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
 | 
					                        <button id="closeInstructions" class="btn btn--sm btn--primary">Got it!</button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Sight Detail Modal -->
 | 
				
			||||||
 | 
					        <div id="sightModal" class="modal hidden">
 | 
				
			||||||
 | 
					            <div class="modal-content">
 | 
				
			||||||
 | 
					                <div class="modal-header">
 | 
				
			||||||
 | 
					                    <h2 id="modalTitle"></h2>
 | 
				
			||||||
 | 
					                    <button id="closeModal" class="btn-close">×</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="modal-body">
 | 
				
			||||||
 | 
					                    <div id="modalContent"></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="modal-footer">
 | 
				
			||||||
 | 
					                    <label class="checkbox-wrapper">
 | 
				
			||||||
 | 
					                        <input type="checkbox" id="modalAddToRoute">
 | 
				
			||||||
 | 
					                        <span>Add to my route</span>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <button id="zoomToSight" class="btn btn--primary">Show on Map</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Scripts -->
 | 
				
			||||||
 | 
					    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
 | 
				
			||||||
 | 
					    <script src="app.js"></script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user