diff --git a/css/cyberpunk.css b/css/cyberpunk.css new file mode 100644 index 0000000..d636a75 --- /dev/null +++ b/css/cyberpunk.css @@ -0,0 +1,228 @@ +:root { + --neon-blue: #00FFFF; + --neon-purple: #FF00FF; + --neon-green: #00FF00; + --dark-bg: #0B1A2F; + --glow: 0 0 10px var(--neon-blue); + --glow-purple: 0 0 10px var(--neon-purple); + --glow-strong: 0 0 20px var(--neon-blue); + } + + @keyframes neonGlow { + 0% { box-shadow: 0 0 5px var(--neon-blue); } + 50% { box-shadow: 0 0 20px var(--neon-blue); } + 100% { box-shadow: 0 0 5px var(--neon-blue); } + } + + @keyframes textGlow { + 0% { text-shadow: 0 0 5px var(--neon-blue); } + 50% { text-shadow: 0 0 20px var(--neon-blue); } + 100% { text-shadow: 0 0 5px var(--neon-blue); } + } + + .cyberpunk-button { + background: transparent; + border: 1px solid var(--neon-blue); + color: var(--neon-blue); + padding: 10px 20px; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + font-size: 14px; + letter-spacing: 2px; + position: relative; + overflow: hidden; + margin: 5px; + border-radius: 3px; + } + + .cyberpunk-button:hover { + background: var(--neon-blue); + color: var(--dark-bg); + box-shadow: var(--glow-strong); + transform: translateY(-2px); + } + + .cyberpunk-button:active { + transform: translateY(1px); + } + + .cyberpunk-button.cancel { + border-color: var(--neon-purple); + color: var(--neon-purple); + } + + .cyberpunk-button.cancel:hover { + background: var(--neon-purple); + color: var(--dark-bg); + box-shadow: var(--glow-purple); + } + + + .cyberpunk-panel { + box-shadow: var(--glow); + backdrop-filter: blur(5px); + animation: neonGlow 2s infinite; + } + + .cyberpunk-form { + box-shadow: var(--glow); + backdrop-filter: blur(10px); + } + + .cyberpunk-form h2 { + color: var(--neon-blue); + text-transform: uppercase; + letter-spacing: 3px; + text-align: center; + margin-bottom: 20px; + animation: textGlow 2s infinite; + font-size: 24px; + } + + + .cyberpunk-select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background: rgba(0, 0, 0, 0.7); + border: 1px solid var(--neon-blue); + border-radius: 4px; + padding: 10px 35px 10px 10px; + color: #fff; + width: 100%; + cursor: pointer; + font-size: 14px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2300FFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 16px; + transition: all 0.3s ease; + } + + .cyberpunk-select:hover { + border-color: var(--neon-blue); + box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); + } + + .cyberpunk-select:focus { + outline: none; + border-color: var(--neon-purple); + box-shadow: 0 0 15px rgba(0, 255, 255, 0.5); + } + + /* Description textarea styles */ + .cyberpunk-textarea { + background: rgba(0, 0, 0, 0.7) !important; + color: #fff !important; + transition: all 0.3s ease; + border: 1px solid var(--neon-blue); + padding: 10px; + border-radius: 4px; + width: 100%; + min-height: 100px; + resize: vertical; + font-family: 'Arial', sans-serif; + } + + .cyberpunk-textarea[readonly] { + cursor: not-allowed; + opacity: 0.7; + border-color: rgba(0, 255, 255, 0.3); + } + + .cyberpunk-textarea:not([readonly]) { + cursor: text; + opacity: 1; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); + } + + .cyberpunk-textarea:focus { + outline: none; + border-color: var(--neon-purple); + box-shadow: 0 0 15px rgba(0, 255, 255, 0.5); + } + + .cyberpunk-textarea::placeholder { + color: rgba(0, 255, 255, 0.5); + } + + .cyberpunk-textarea[readonly]::placeholder { + color: rgba(0, 255, 255, 0.3); + } + + .cyberpunk-select option { + background-color: rgba(11, 26, 47, 0.95); + color: #fff; + padding: 10px; + } + + + .marker-item { + border: 1px solid var(--neon-blue); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + } + + .marker-item:hover { + box-shadow: var(--glow); + transform: translateX(5px); + } + + .marker-item::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 2px; + background: linear-gradient(90deg, transparent, var(--neon-blue)); + animation: animate 2s linear infinite; + } + + + @keyframes animate { + 0% { left: -100%; } + 50%, 100% { left: 100%; } + } + + + .notification-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 9999; + } + + .notification { + background: rgba(11, 26, 47, 0.95); + border-left: 4px solid var(--neon-blue); + color: #fff; + padding: 15px 20px; + margin-bottom: 10px; + border-radius: 4px; + box-shadow: var(--glow); + transform: translateX(120%); + transition: transform 0.3s ease; + min-width: 300px; + } + + .notification.show { + transform: translateX(0); + } + + .notification.error { + border-left-color: var(--neon-purple); + } + + + .marker-icon.temporary { + animation: pulseGlow 1s infinite alternate; + } + + @keyframes pulseGlow { + 0% { box-shadow: 0 0 5px rgba(0, 255, 255, 0.5); } + 100% { box-shadow: 0 0 20px rgba(0, 255, 255, 0.8); } + } + \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..0df549f --- /dev/null +++ b/css/main.css @@ -0,0 +1,249 @@ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + margin: 0; + padding: 0; + background-color: #0B1A2F; + color: #ffffff; + font-family: 'Arial', sans-serif; + overflow: hidden; + } + + + #map { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100vh; + } + + + .cyberpunk-panel { + position: absolute; + background: rgba(11, 26, 47, 0.9); + padding: 20px; + border: 1px solid #00FFFF; + border-radius: 5px; + z-index: 1000; + } + + #control-panel { + top: 20px; + left: 20px; + min-width: 250px; + } + + .layer-toggle { + top: 20px; + right: 20px; + min-width: 200px; + } + + + .marker-list { + margin-top: 20px; + max-height: 400px; + overflow-y: auto; + padding-right: 10px; + } + + .marker-item { + border: 1px solid #00FFFF; + margin: 5px 0; + padding: 10px; + border-radius: 3px; + background: rgba(0, 255, 255, 0.1); + cursor: pointer; + transition: all 0.3s ease; + } + + .marker-item:hover { + transform: translateX(5px); + background: rgba(0, 255, 255, 0.2); + } + + + .cyberpunk-form { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(11, 26, 47, 0.95); + padding: 25px; + border: 1px solid #00FFFF; + border-radius: 5px; + z-index: 1000; + min-width: 350px; + display: none; + backdrop-filter: blur(10px); + } + + .form-group { + margin: 15px 0; + } + + .form-group label { + display: block; + margin-bottom: 8px; + color: #00FFFF; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 1px; + } + + .form-group input, + .form-group textarea { + width: 100%; + padding: 10px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid #00FFFF; + color: #fff; + border-radius: 3px; + font-size: 14px; + } + + .form-group textarea { + height: 100px; + resize: vertical; + } + + + .cyberpunk-select { + width: 100%; + padding: 10px 35px 10px 10px; + background: rgba(0, 0, 0, 0.7); + border: 1px solid #00FFFF; + color: #fff; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + } + + + .cyberpunk-textarea { + background: rgba(0, 0, 0, 0.7) !important; + color: #fff !important; + transition: all 0.3s ease; + border: 1px solid var(--neon-blue); + padding: 10px; + border-radius: 4px; + width: 100%; + min-height: 100px; + resize: vertical; + } + + .cyberpunk-textarea[readonly] { + cursor: not-allowed; + opacity: 0.7; + border-color: rgba(0, 255, 255, 0.3); + } + + .cyberpunk-textarea:not([readonly]) { + cursor: text; + opacity: 1; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); + } + + .cyberpunk-textarea:focus { + outline: none; + border-color: var(--neon-purple); + box-shadow: 0 0 15px rgba(0, 255, 255, 0.5); + } + + .cyberpunk-textarea::placeholder { + color: rgba(0, 255, 255, 0.5); + } + + .cyberpunk-textarea[readonly]::placeholder { + color: rgba(0, 255, 255, 0.3); + } + + + .cyberpunk-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2300FFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 16px; + } + + + .cyberpunk-select option { + background-color: rgba(11, 26, 47, 0.95); + color: #fff; + padding: 10px; + } + + + .form-buttons { + display: flex; + justify-content: space-between; + margin-top: 20px; + gap: 10px; + } + + + ::-webkit-scrollbar { + width: 8px; + } + + ::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + } + + ::-webkit-scrollbar-thumb { + background: #00FFFF; + border-radius: 4px; + } + + + .marker-icon { + width: 20px; + height: 20px; + border-radius: 50%; + cursor: pointer; + border: 2px solid #000; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.5); + } + + + .marker-icon.roadChange { background-color: #00FFFF; } + .marker-icon.construction { background-color: #FF00FF; } + .marker-icon.traffic { background-color: #FFFF00; } + .marker-icon.incident { background-color: #FF0000; } + .marker-icon.hazard { background-color: #FFA500; } + .marker-icon.closure { background-color: #FF0000; } + .marker-icon.newRoad { background-color: #00FF00; } + .marker-icon.infrastructure { background-color: #4169E1; } + .marker-icon.accident { background-color: #FF4500; } + .marker-icon.testing { background-color: #9400D3; } + .marker-icon.parking { background-color: #1E90FF; } + .marker-icon.charging { background-color: #32CD32; } + + /* Layer toggle styles */ + .layer-toggle-item { + margin: 10px 0; + display: flex; + align-items: center; + } + + .layer-toggle-item input[type="checkbox"] { + margin-right: 10px; + } + + .layer-toggle h3 { + margin-bottom: 15px; + color: #00FFFF; + font-size: 16px; + letter-spacing: 1px; + } + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..ad18b1c --- /dev/null +++ b/index.html @@ -0,0 +1,101 @@ + + + + + +Auto Driving Map + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+

Map Layers

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

ADD NEW MARKER

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + diff --git a/js/map.js b/js/map.js new file mode 100644 index 0000000..bf65557 --- /dev/null +++ b/js/map.js @@ -0,0 +1,162 @@ + +const mapboxToken = 'pk.eyJ1IjoiZ3NodXlhIiwiYSI6ImNtMHk2bjRodTBrNmIya29obmc2OG5kZTUifQ.TM9gfDCRHU_yorUj8CQHSA'; + + +mapboxgl.accessToken = mapboxToken; +const map = new mapboxgl.Map({ +container: 'map', +style: 'mapbox://styles/mapbox/dark-v10', +center: [-122.4194, 37.7749], +zoom: 12, +minZoom: 2, +maxZoom: 18 +}); + + +map.on('load', () => { + +map.addLayer({ +'id': 'traffic-flow', +'type': 'line', +'source': { +'type': 'vector', +'url': 'mapbox://mapbox.mapbox-traffic-v1' +}, +'source-layer': 'traffic', +'paint': { +'line-color': [ +'match', +['get', 'congestion'], +'low', '#00FF00', +'moderate', '#FFFF00', +'heavy', '#FF0000', +'severe', '#FF00FF', +'#FFFFFF' +], +'line-width': 2, +'line-opacity': 0.7 +} +}); + + +map.addLayer({ +'id': '3d-buildings', +'source': 'composite', +'source-layer': 'building', +'filter': ['==', 'extrude', 'true'], +'type': 'fill-extrusion', +'minzoom': 15, +'paint': { +'fill-extrusion-color': [ +'interpolate', +['linear'], +['get', 'height'], +0, '#0B1A2F', +50, '#00FFFF', +100, '#FF00FF', +200, '#0080FF' +], +'fill-extrusion-height': ['get', 'height'], +'fill-extrusion-base': ['get', 'min_height'], +'fill-extrusion-opacity': 0.5, +'fill-extrusion-vertical-gradient': true +} +}); + + +map.addLayer({ +'id': 'road-network', +'type': 'line', +'source': { +'type': 'vector', +'url': 'mapbox://mapbox.mapbox-streets-v8' +}, +'source-layer': 'road', +'paint': { +'line-color': '#00FFFF', +'line-width': [ +'interpolate', +['linear'], +['zoom'], +12, 1, +16, 3 +], +'line-opacity': 0.6 +} +}); + + +map.addLayer({ +'id': 'poi-labels', +'type': 'symbol', +'source': { +'type': 'vector', +'url': 'mapbox://mapbox.mapbox-streets-v8' +}, +'source-layer': 'poi_label', +'layout': { +'text-field': ['get', 'name'], +'text-size': 12, +'text-anchor': 'top', +'text-offset': [0, 1], +'icon-image': ['get', 'maki'], +'icon-size': 1 +}, +'paint': { +'text-color': '#00FFFF', +'text-halo-color': '#000000', +'text-halo-width': 1 +} +}); + + +map.addLayer({ +'id': 'transit-lines', +'type': 'line', +'source': { +'type': 'vector', +'url': 'mapbox://mapbox.mapbox-streets-v8' +}, +'source-layer': 'transit_line', +'paint': { +'line-color': '#FF00FF', +'line-width': 2, +'line-opacity': 0.8 +} +}); +}); + + +map.addControl(new mapboxgl.NavigationControl(), 'top-right'); +map.addControl(new mapboxgl.ScaleControl({ +maxWidth: 100, +unit: 'metric' +}), 'bottom-left'); + + +document.addEventListener('DOMContentLoaded', () => { +const layers = { +'traffic-flow': 'traffic-flow-toggle', +'poi-labels': 'poi-labels-toggle', +'3d-buildings': '3d-buildings-toggle', +'transit-lines': 'transit-lines-toggle' +}; + +Object.entries(layers).forEach(([layerId, toggleId]) => { +const toggle = document.getElementById(toggleId); +if (toggle) { +toggle.addEventListener('change', (e) => { +const visibility = e.target.checked ? 'visible' : 'none'; +map.setLayoutProperty(layerId, 'visibility', visibility); +}); +} +}); +}); + + +map.on('error', (e) => { +console.error('Map error:', e.error); +}); + + +window.map = map; diff --git a/js/markers.js b/js/markers.js new file mode 100644 index 0000000..59a4d81 --- /dev/null +++ b/js/markers.js @@ -0,0 +1,232 @@ + +let markers = []; + + +const markerStyles = { +roadChange: { color: '#00FFFF', icon: '🛣️' }, +construction: { color: '#FF00FF', icon: '🚧' }, +traffic: { color: '#FFFF00', icon: '🚦' }, +incident: { color: '#FF0000', icon: '⚠️' }, +hazard: { color: '#FFA500', icon: '⚠️' }, +closure: { color: '#FF0000', icon: '🚫' }, +newRoad: { color: '#00FF00', icon: '🛣️' }, +infrastructure: { color: '#4169E1', icon: '🏗️' }, +accident: { color: '#FF4500', icon: '💥' }, +testing: { color: '#9400D3', icon: '🚗' }, +parking: { color: '#1E90FF', icon: '🅿️' }, +charging: { color: '#32CD32', icon: '⚡' } +}; + + +function initializeMarkers() { +const addMarkerBtn = document.getElementById('add-marker-btn'); +const markerForm = document.getElementById('marker-form'); +const addMarkerForm = document.getElementById('add-marker-form'); +const descriptionField = document.getElementById('marker-description'); + +let selectedLocation = null; + + +addMarkerBtn.addEventListener('click', () => { +markerForm.style.display = 'block'; +descriptionField.readOnly = true; +}); + + +map.on('click', (e) => { +selectedLocation = e.lngLat; +showTemporaryMarker(selectedLocation); +markerForm.style.display = 'block'; +descriptionField.readOnly = true; + + +const ripple = document.createElement('div'); +ripple.className = 'map-click-ripple'; +ripple.style.left = e.point.x + 'px'; +ripple.style.top = e.point.y + 'px'; +document.body.appendChild(ripple); +setTimeout(() => ripple.remove(), 1000); +}); + + +document.querySelector('.cancel').addEventListener('click', () => { +markerForm.style.display = 'none'; +removeTemporaryMarker(); +selectedLocation = null; +addMarkerForm.reset(); +descriptionField.readOnly = true; +}); + + +addMarkerForm.addEventListener('submit', (e) => { +e.preventDefault(); + + +if (!selectedLocation) { +showNotification('Please select a location on the map', 'error'); +return; +} + + +if (!descriptionField.value.trim()) { +if (descriptionField.readOnly) { +descriptionField.readOnly = false; +descriptionField.focus(); +showNotification('You can now enter description', 'info'); +return; +} +showNotification('Please enter a description', 'error'); +descriptionField.focus(); +return; +} + +if (descriptionField.readOnly) { +descriptionField.readOnly = false; +descriptionField.focus(); +showNotification('You can now enter description', 'info'); +return; +} + +const newMarker = { +id: Date.now(), +type: document.getElementById('marker-type').value, +description: descriptionField.value, +duration: document.getElementById('marker-duration').value, +location: selectedLocation, +timestamp: new Date().toISOString() +}; + + +addMarkerToMap(newMarker); + + +markers.push(newMarker); + + +updateMarkerList(); + + +saveMarkers(); + + +addMarkerForm.reset(); +markerForm.style.display = 'none'; +removeTemporaryMarker(); +selectedLocation = null; +descriptionField.readOnly = true; + + +showNotification('Marker added successfully', 'success'); +}); +} + + +let temporaryMarker = null; + + +function showTemporaryMarker(location) { +removeTemporaryMarker(); + +const el = document.createElement('div'); +el.className = 'marker-icon temporary'; +el.style.backgroundColor = '#FFFFFF'; +el.style.opacity = '0.7'; + +temporaryMarker = new mapboxgl.Marker(el) +.setLngLat(location) +.addTo(map); +} + + +function removeTemporaryMarker() { +if (temporaryMarker) { +temporaryMarker.remove(); +temporaryMarker = null; +} +} + + +function addMarkerToMap(markerData) { +const style = markerStyles[markerData.type]; + + +const el = document.createElement('div'); +el.className = 'marker-icon'; +el.classList.add(markerData.type); + + +const popup = new mapboxgl.Popup({ +offset: 25, +closeButton: true, +closeOnClick: false +}).setHTML(` + +`); + + +new mapboxgl.Marker(el) +.setLngLat(markerData.location) +.setPopup(popup) +.addTo(map); +} + + +function updateMarkerList() { +const markerList = document.getElementById('marker-list'); +markerList.innerHTML = markers.map(marker => { +const style = markerStyles[marker.type]; +return ` +
+

${style.icon} ${marker.type.toUpperCase()}

+

${marker.description}

+ +Added: ${new Date(marker.timestamp).toLocaleString()}
+Duration: ${marker.duration} +
+
+`; +}).join(''); + + +document.querySelectorAll('.marker-item').forEach(item => { +item.addEventListener('click', () => { +const marker = markers.find(m => m.id === parseInt(item.dataset.id)); +if (marker) { +map.flyTo({ +center: [marker.location.lng, marker.location.lat], +zoom: 15, +duration: 1000 +}); +} +}); +}); +} + + +function loadSavedMarkers() { +const savedMarkers = localStorage.getItem('markers'); +if (savedMarkers) { +markers = JSON.parse(savedMarkers); +markers.forEach(marker => addMarkerToMap(marker)); +updateMarkerList(); +} +} + + +function saveMarkers() { +localStorage.setItem('markers', JSON.stringify(markers)); +} + + +document.addEventListener('DOMContentLoaded', () => { +initializeMarkers(); +loadSavedMarkers(); +}); + + +window.addEventListener('beforeunload', saveMarkers); diff --git a/js/ui.js b/js/ui.js new file mode 100644 index 0000000..59a4d81 --- /dev/null +++ b/js/ui.js @@ -0,0 +1,232 @@ + +let markers = []; + + +const markerStyles = { +roadChange: { color: '#00FFFF', icon: '🛣️' }, +construction: { color: '#FF00FF', icon: '🚧' }, +traffic: { color: '#FFFF00', icon: '🚦' }, +incident: { color: '#FF0000', icon: '⚠️' }, +hazard: { color: '#FFA500', icon: '⚠️' }, +closure: { color: '#FF0000', icon: '🚫' }, +newRoad: { color: '#00FF00', icon: '🛣️' }, +infrastructure: { color: '#4169E1', icon: '🏗️' }, +accident: { color: '#FF4500', icon: '💥' }, +testing: { color: '#9400D3', icon: '🚗' }, +parking: { color: '#1E90FF', icon: '🅿️' }, +charging: { color: '#32CD32', icon: '⚡' } +}; + + +function initializeMarkers() { +const addMarkerBtn = document.getElementById('add-marker-btn'); +const markerForm = document.getElementById('marker-form'); +const addMarkerForm = document.getElementById('add-marker-form'); +const descriptionField = document.getElementById('marker-description'); + +let selectedLocation = null; + + +addMarkerBtn.addEventListener('click', () => { +markerForm.style.display = 'block'; +descriptionField.readOnly = true; +}); + + +map.on('click', (e) => { +selectedLocation = e.lngLat; +showTemporaryMarker(selectedLocation); +markerForm.style.display = 'block'; +descriptionField.readOnly = true; + + +const ripple = document.createElement('div'); +ripple.className = 'map-click-ripple'; +ripple.style.left = e.point.x + 'px'; +ripple.style.top = e.point.y + 'px'; +document.body.appendChild(ripple); +setTimeout(() => ripple.remove(), 1000); +}); + + +document.querySelector('.cancel').addEventListener('click', () => { +markerForm.style.display = 'none'; +removeTemporaryMarker(); +selectedLocation = null; +addMarkerForm.reset(); +descriptionField.readOnly = true; +}); + + +addMarkerForm.addEventListener('submit', (e) => { +e.preventDefault(); + + +if (!selectedLocation) { +showNotification('Please select a location on the map', 'error'); +return; +} + + +if (!descriptionField.value.trim()) { +if (descriptionField.readOnly) { +descriptionField.readOnly = false; +descriptionField.focus(); +showNotification('You can now enter description', 'info'); +return; +} +showNotification('Please enter a description', 'error'); +descriptionField.focus(); +return; +} + +if (descriptionField.readOnly) { +descriptionField.readOnly = false; +descriptionField.focus(); +showNotification('You can now enter description', 'info'); +return; +} + +const newMarker = { +id: Date.now(), +type: document.getElementById('marker-type').value, +description: descriptionField.value, +duration: document.getElementById('marker-duration').value, +location: selectedLocation, +timestamp: new Date().toISOString() +}; + + +addMarkerToMap(newMarker); + + +markers.push(newMarker); + + +updateMarkerList(); + + +saveMarkers(); + + +addMarkerForm.reset(); +markerForm.style.display = 'none'; +removeTemporaryMarker(); +selectedLocation = null; +descriptionField.readOnly = true; + + +showNotification('Marker added successfully', 'success'); +}); +} + + +let temporaryMarker = null; + + +function showTemporaryMarker(location) { +removeTemporaryMarker(); + +const el = document.createElement('div'); +el.className = 'marker-icon temporary'; +el.style.backgroundColor = '#FFFFFF'; +el.style.opacity = '0.7'; + +temporaryMarker = new mapboxgl.Marker(el) +.setLngLat(location) +.addTo(map); +} + + +function removeTemporaryMarker() { +if (temporaryMarker) { +temporaryMarker.remove(); +temporaryMarker = null; +} +} + + +function addMarkerToMap(markerData) { +const style = markerStyles[markerData.type]; + + +const el = document.createElement('div'); +el.className = 'marker-icon'; +el.classList.add(markerData.type); + + +const popup = new mapboxgl.Popup({ +offset: 25, +closeButton: true, +closeOnClick: false +}).setHTML(` + +`); + + +new mapboxgl.Marker(el) +.setLngLat(markerData.location) +.setPopup(popup) +.addTo(map); +} + + +function updateMarkerList() { +const markerList = document.getElementById('marker-list'); +markerList.innerHTML = markers.map(marker => { +const style = markerStyles[marker.type]; +return ` +
+

${style.icon} ${marker.type.toUpperCase()}

+

${marker.description}

+ +Added: ${new Date(marker.timestamp).toLocaleString()}
+Duration: ${marker.duration} +
+
+`; +}).join(''); + + +document.querySelectorAll('.marker-item').forEach(item => { +item.addEventListener('click', () => { +const marker = markers.find(m => m.id === parseInt(item.dataset.id)); +if (marker) { +map.flyTo({ +center: [marker.location.lng, marker.location.lat], +zoom: 15, +duration: 1000 +}); +} +}); +}); +} + + +function loadSavedMarkers() { +const savedMarkers = localStorage.getItem('markers'); +if (savedMarkers) { +markers = JSON.parse(savedMarkers); +markers.forEach(marker => addMarkerToMap(marker)); +updateMarkerList(); +} +} + + +function saveMarkers() { +localStorage.setItem('markers', JSON.stringify(markers)); +} + + +document.addEventListener('DOMContentLoaded', () => { +initializeMarkers(); +loadSavedMarkers(); +}); + + +window.addEventListener('beforeunload', saveMarkers);