| 
									
										
										
										
											2025-09-26 15:08:33 +02:00
										 |  |  | // Initialize the map
 | 
					
						
							|  |  |  | const map = new maplibregl.Map({ | 
					
						
							|  |  |  |     container: 'map', | 
					
						
							|  |  |  |     style: 'https://tiles.openfreemap.org/styles/liberty', | 
					
						
							|  |  |  |     center: [2.3522, 48.8566], // Default center (Paris)
 | 
					
						
							| 
									
										
										
										
											2025-10-02 22:53:50 +02:00
										 |  |  |     zoom: 3 | 
					
						
							| 
									
										
										
										
											2025-09-26 15:08:33 +02:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add navigation controls
 | 
					
						
							|  |  |  | map.addControl(new maplibregl.NavigationControl()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add draw controls for polygon
 | 
					
						
							|  |  |  | let drawnPolygon = null; | 
					
						
							|  |  |  | let drawingMode = false; | 
					
						
							|  |  |  | let points = []; | 
					
						
							|  |  |  | let lineString = null; | 
					
						
							|  |  |  | let polygonFill = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add a button to toggle drawing mode
 | 
					
						
							|  |  |  | const drawButton = document.createElement('button'); | 
					
						
							|  |  |  | drawButton.textContent = 'Draw Polygon'; | 
					
						
							|  |  |  | drawButton.style.position = 'absolute'; | 
					
						
							|  |  |  | drawButton.style.top = '10px'; | 
					
						
							|  |  |  | drawButton.style.right = '10px'; | 
					
						
							|  |  |  | drawButton.style.zIndex = '1'; | 
					
						
							|  |  |  | drawButton.style.padding = '5px 10px'; | 
					
						
							|  |  |  | drawButton.style.backgroundColor = '#0078ff'; | 
					
						
							|  |  |  | drawButton.style.color = 'white'; | 
					
						
							|  |  |  | drawButton.style.border = 'none'; | 
					
						
							|  |  |  | drawButton.style.borderRadius = '3px'; | 
					
						
							|  |  |  | drawButton.style.cursor = 'pointer'; | 
					
						
							|  |  |  | document.getElementById('map').appendChild(drawButton); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | drawButton.addEventListener('click', () => { | 
					
						
							|  |  |  |     drawingMode = !drawingMode; | 
					
						
							|  |  |  |     drawButton.textContent = drawingMode ? 'Cancel Drawing' : 'Draw Polygon'; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!drawingMode) { | 
					
						
							|  |  |  |         // Clear the drawing
 | 
					
						
							|  |  |  |         points = []; | 
					
						
							|  |  |  |         if (lineString) { | 
					
						
							|  |  |  |             map.removeLayer('line-string'); | 
					
						
							|  |  |  |             map.removeSource('line-string'); | 
					
						
							|  |  |  |             lineString = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (polygonFill) { | 
					
						
							|  |  |  |             map.removeLayer('polygon-fill'); | 
					
						
							|  |  |  |             map.removeSource('polygon-fill'); | 
					
						
							|  |  |  |             polygonFill = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle map click events for drawing
 | 
					
						
							|  |  |  | map.on('click', (e) => { | 
					
						
							|  |  |  |     if (!drawingMode) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const coords = [e.lngLat.lng, e.lngLat.lat]; | 
					
						
							|  |  |  |     points.push(coords); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // If we have at least 3 points, create a polygon
 | 
					
						
							|  |  |  |     if (points.length >= 3) { | 
					
						
							|  |  |  |         const polygonCoords = [...points, points[0]]; // Close the polygon
 | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Create or update the line string
 | 
					
						
							|  |  |  |         if (lineString) { | 
					
						
							|  |  |  |             map.removeLayer('line-string'); | 
					
						
							|  |  |  |             map.removeSource('line-string'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         lineString = { | 
					
						
							|  |  |  |             type: 'Feature', | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: 'LineString', | 
					
						
							|  |  |  |                 coordinates: polygonCoords | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         map.addSource('line-string', { | 
					
						
							|  |  |  |             type: 'geojson', | 
					
						
							|  |  |  |             data: lineString | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         map.addLayer({ | 
					
						
							|  |  |  |             id: 'line-string', | 
					
						
							|  |  |  |             type: 'line', | 
					
						
							|  |  |  |             source: 'line-string', | 
					
						
							|  |  |  |             paint: { | 
					
						
							|  |  |  |                 'line-color': '#0078ff', | 
					
						
							|  |  |  |                 'line-width': 2 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Create or update the polygon fill
 | 
					
						
							|  |  |  |         if (polygonFill) { | 
					
						
							|  |  |  |             map.removeLayer('polygon-fill'); | 
					
						
							|  |  |  |             map.removeSource('polygon-fill'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         polygonFill = { | 
					
						
							|  |  |  |             type: 'Feature', | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: 'Polygon', | 
					
						
							|  |  |  |                 coordinates: [polygonCoords] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         map.addSource('polygon-fill', { | 
					
						
							|  |  |  |             type: 'geojson', | 
					
						
							|  |  |  |             data: polygonFill | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         map.addLayer({ | 
					
						
							|  |  |  |             id: 'polygon-fill', | 
					
						
							|  |  |  |             type: 'fill', | 
					
						
							|  |  |  |             source: 'polygon-fill', | 
					
						
							|  |  |  |             paint: { | 
					
						
							|  |  |  |                 'fill-color': '#0078ff', | 
					
						
							|  |  |  |                 'fill-opacity': 0.2 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Store the drawn polygon for search
 | 
					
						
							|  |  |  |         drawnPolygon = { | 
					
						
							|  |  |  |             type: 'Polygon', | 
					
						
							|  |  |  |             coordinates: [polygonCoords] | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle custom date range selection
 | 
					
						
							|  |  |  | document.getElementById('when').addEventListener('change', function() { | 
					
						
							|  |  |  |     const customDateGroup = document.getElementById('customDateGroup'); | 
					
						
							|  |  |  |     const customDateEndGroup = document.getElementById('customDateEndGroup'); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (this.value === 'custom') { | 
					
						
							|  |  |  |         customDateGroup.style.display = 'block'; | 
					
						
							|  |  |  |         customDateEndGroup.style.display = 'block'; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         customDateGroup.style.display = 'none'; | 
					
						
							|  |  |  |         customDateEndGroup.style.display = 'none'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle form submission
 | 
					
						
							|  |  |  | document.getElementById('searchForm').addEventListener('submit', function(e) { | 
					
						
							|  |  |  |     e.preventDefault(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Show loading message
 | 
					
						
							|  |  |  |     const resultElement = document.getElementById('result'); | 
					
						
							|  |  |  |     resultElement.textContent = 'Searching...'; | 
					
						
							|  |  |  |     resultElement.className = ''; | 
					
						
							|  |  |  |     resultElement.style.display = 'block'; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get form values
 | 
					
						
							|  |  |  |     const formData = new FormData(this); | 
					
						
							|  |  |  |     const params = new URLSearchParams(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Add form fields to params
 | 
					
						
							|  |  |  |     for (const [key, value] of formData.entries()) { | 
					
						
							|  |  |  |         if (value) { | 
					
						
							|  |  |  |             params.append(key, value); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Handle custom date range
 | 
					
						
							|  |  |  |     if (formData.get('when') === 'custom') { | 
					
						
							|  |  |  |         params.delete('when'); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         params.delete('start'); | 
					
						
							|  |  |  |         params.delete('stop'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Prepare the request
 | 
					
						
							|  |  |  |     let url = '/event/search'; | 
					
						
							|  |  |  |     let method = 'POST'; | 
					
						
							|  |  |  |     let body = null; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // If we have a drawn polygon, use it for the search
 | 
					
						
							|  |  |  |     if (drawnPolygon) { | 
					
						
							|  |  |  |         body = JSON.stringify({ | 
					
						
							|  |  |  |             geometry: drawnPolygon | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } else if (formData.get('near') || formData.get('bbox')) { | 
					
						
							|  |  |  |         // If we have near or bbox parameters, use GET request
 | 
					
						
							|  |  |  |         url = '/event?' + params.toString(); | 
					
						
							|  |  |  |         method = 'GET'; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         // Default to a simple point search in Paris if no spatial filter is provided
 | 
					
						
							|  |  |  |         body = JSON.stringify({ | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: 'Point', | 
					
						
							|  |  |  |                 coordinates: [2.3522, 48.8566] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Make the request
 | 
					
						
							|  |  |  |     fetch(url + (method === 'GET' ? '' : '?' + params.toString()), { | 
					
						
							|  |  |  |         method: method, | 
					
						
							|  |  |  |         headers: { | 
					
						
							|  |  |  |             'Content-Type': 'application/json' | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         body: method === 'POST' ? body : null | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .then(response => { | 
					
						
							|  |  |  |         if (response.ok) { | 
					
						
							|  |  |  |             return response.json(); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             return response.text().then(text => { | 
					
						
							|  |  |  |                 throw new Error(text || response.statusText); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .then(data => { | 
					
						
							|  |  |  |         // Show success message
 | 
					
						
							|  |  |  |         resultElement.textContent = `Found ${data.features ? data.features.length : 0} events`; | 
					
						
							|  |  |  |         resultElement.className = 'success'; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Display results
 | 
					
						
							|  |  |  |         displayResults(data); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .catch(error => { | 
					
						
							|  |  |  |         // Show error message
 | 
					
						
							|  |  |  |         resultElement.textContent = `Error: ${error.message}`; | 
					
						
							|  |  |  |         resultElement.className = 'error'; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Hide results container
 | 
					
						
							|  |  |  |         document.getElementById('resultsContainer').style.display = 'none'; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Function to display search results
 | 
					
						
							|  |  |  | function displayResults(data) { | 
					
						
							|  |  |  |     // Show results container
 | 
					
						
							|  |  |  |     document.getElementById('resultsContainer').style.display = 'block'; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Initialize results map
 | 
					
						
							|  |  |  |     const resultsMap = new maplibregl.Map({ | 
					
						
							|  |  |  |         container: 'resultsMap', | 
					
						
							|  |  |  |         style: 'https://tiles.openfreemap.org/styles/liberty', | 
					
						
							|  |  |  |         center: [2.3522, 48.8566], // Default center (Paris)
 | 
					
						
							|  |  |  |         zoom: 4 | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Add navigation controls to results map
 | 
					
						
							|  |  |  |     resultsMap.addControl(new maplibregl.NavigationControl()); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Add events to the map
 | 
					
						
							|  |  |  |     resultsMap.on('load', function() { | 
					
						
							|  |  |  |         // Add events as a source
 | 
					
						
							|  |  |  |         resultsMap.addSource('events', { | 
					
						
							|  |  |  |             type: 'geojson', | 
					
						
							|  |  |  |             data: data | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Add a circle layer for events
 | 
					
						
							|  |  |  |         resultsMap.addLayer({ | 
					
						
							|  |  |  |             id: 'events-circle', | 
					
						
							|  |  |  |             type: 'circle', | 
					
						
							|  |  |  |             source: 'events', | 
					
						
							|  |  |  |             paint: { | 
					
						
							|  |  |  |                 'circle-radius': 8, | 
					
						
							|  |  |  |                 'circle-color': '#FF5722', | 
					
						
							|  |  |  |                 'circle-stroke-width': 2, | 
					
						
							|  |  |  |                 'circle-stroke-color': '#FFFFFF' | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Add popups for events
 | 
					
						
							|  |  |  |         if (data.features) { | 
					
						
							|  |  |  |             data.features.forEach(feature => { | 
					
						
							|  |  |  |                 const coordinates = feature.geometry.coordinates.slice(); | 
					
						
							|  |  |  |                 const properties = feature.properties; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Create popup content
 | 
					
						
							|  |  |  |                 let popupContent = '<div class="event-popup">'; | 
					
						
							|  |  |  |                 popupContent += `<h3>${properties.label || 'Event'}</h3>`; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Display key properties
 | 
					
						
							|  |  |  |                 if (properties.what) { | 
					
						
							|  |  |  |                     popupContent += `<p><strong>Type:</strong> ${properties.what}</p>`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (properties.where) { | 
					
						
							|  |  |  |                     popupContent += `<p><strong>Where:</strong> ${properties.where}</p>`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (properties.start) { | 
					
						
							|  |  |  |                     popupContent += `<p><strong>Start:</strong> ${properties.start}</p>`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (properties.stop) { | 
					
						
							|  |  |  |                     popupContent += `<p><strong>End:</strong> ${properties.stop}</p>`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Add link to view full event
 | 
					
						
							|  |  |  |                 popupContent += `<p><a href="/event/${properties.id}" >View Event</a></p>`; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 popupContent += '</div>'; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Create popup
 | 
					
						
							|  |  |  |                 const popup = new maplibregl.Popup({ | 
					
						
							|  |  |  |                     closeButton: true, | 
					
						
							|  |  |  |                     closeOnClick: true | 
					
						
							|  |  |  |                 }).setHTML(popupContent); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Add marker with popup
 | 
					
						
							|  |  |  |                 new maplibregl.Marker({ | 
					
						
							|  |  |  |                     color: '#FF5722' | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .setLngLat(coordinates) | 
					
						
							|  |  |  |                 .setPopup(popup) | 
					
						
							|  |  |  |                 .addTo(resultsMap); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Fit map to events bounds
 | 
					
						
							|  |  |  |             if (data.features.length > 0) { | 
					
						
							|  |  |  |                 const bounds = new maplibregl.LngLatBounds(); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 data.features.forEach(feature => { | 
					
						
							|  |  |  |                     bounds.extend(feature.geometry.coordinates); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 resultsMap.fitBounds(bounds, { | 
					
						
							|  |  |  |                     padding: 50, | 
					
						
							|  |  |  |                     maxZoom: 12 | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Populate table with results
 | 
					
						
							|  |  |  |     const tableBody = document.getElementById('resultsTable').getElementsByTagName('tbody')[0]; | 
					
						
							|  |  |  |     tableBody.innerHTML = ''; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (data.features) { | 
					
						
							|  |  |  |         data.features.forEach(feature => { | 
					
						
							|  |  |  |             const properties = feature.properties; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             const row = tableBody.insertRow(); | 
					
						
							|  |  |  |             row.insertCell(0).textContent = properties.id || ''; | 
					
						
							|  |  |  |             row.insertCell(1).textContent = properties.label || ''; | 
					
						
							|  |  |  |             row.insertCell(2).textContent = properties.type || ''; | 
					
						
							|  |  |  |             row.insertCell(3).textContent = properties.what || ''; | 
					
						
							|  |  |  |             row.insertCell(4).textContent = properties.where || ''; | 
					
						
							|  |  |  |             row.insertCell(5).textContent = properties.start || ''; | 
					
						
							|  |  |  |             row.insertCell(6).textContent = properties.stop || ''; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Store the data for download
 | 
					
						
							|  |  |  |     window.searchResults = data; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle tab switching
 | 
					
						
							|  |  |  | document.querySelectorAll('.tab-button').forEach(button => { | 
					
						
							|  |  |  |     button.addEventListener('click', () => { | 
					
						
							|  |  |  |         // Remove active class from all buttons and content
 | 
					
						
							|  |  |  |         document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); | 
					
						
							|  |  |  |         document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Add active class to clicked button and corresponding content
 | 
					
						
							|  |  |  |         button.classList.add('active'); | 
					
						
							|  |  |  |         document.getElementById(button.dataset.tab).classList.add('active'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle CSV download
 | 
					
						
							|  |  |  | document.getElementById('downloadCsv').addEventListener('click', () => { | 
					
						
							|  |  |  |     if (!window.searchResults || !window.searchResults.features) { | 
					
						
							|  |  |  |         alert('No search results to download'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Convert GeoJSON to CSV
 | 
					
						
							|  |  |  |     let csv = 'id,label,type,what,where,start,stop,longitude,latitude\n'; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     window.searchResults.features.forEach(feature => { | 
					
						
							|  |  |  |         const p = feature.properties; | 
					
						
							|  |  |  |         const coords = feature.geometry.coordinates; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         csv += `"${p.id || ''}","${p.label || ''}","${p.type || ''}","${p.what || ''}","${p.where || ''}","${p.start || ''}","${p.stop || ''}",${coords[0]},${coords[1]}\n`; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create download link
 | 
					
						
							|  |  |  |     const blob = new Blob([csv], { type: 'text/csv' }); | 
					
						
							|  |  |  |     const url = URL.createObjectURL(blob); | 
					
						
							|  |  |  |     const a = document.createElement('a'); | 
					
						
							|  |  |  |     a.href = url; | 
					
						
							|  |  |  |     a.download = 'search_results.csv'; | 
					
						
							|  |  |  |     document.body.appendChild(a); | 
					
						
							|  |  |  |     a.click(); | 
					
						
							|  |  |  |     document.body.removeChild(a); | 
					
						
							|  |  |  |     URL.revokeObjectURL(url); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Handle JSON download
 | 
					
						
							|  |  |  | document.getElementById('downloadJson').addEventListener('click', () => { | 
					
						
							|  |  |  |     if (!window.searchResults) { | 
					
						
							|  |  |  |         alert('No search results to download'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create download link
 | 
					
						
							|  |  |  |     const blob = new Blob([JSON.stringify(window.searchResults, null, 2)], { type: 'application/json' }); | 
					
						
							|  |  |  |     const url = URL.createObjectURL(blob); | 
					
						
							|  |  |  |     const a = document.createElement('a'); | 
					
						
							|  |  |  |     a.href = url; | 
					
						
							|  |  |  |     a.download = 'search_results.json'; | 
					
						
							|  |  |  |     document.body.appendChild(a); | 
					
						
							|  |  |  |     a.click(); | 
					
						
							|  |  |  |     document.body.removeChild(a); | 
					
						
							|  |  |  |     URL.revokeObjectURL(url); | 
					
						
							|  |  |  | }); |