/** * FLOCKRADAR - LIVE MAPPING & PROXIMITY ALERT ENGINE * Full-screen Waze-style radar map with red camera recording zones, * real-time proximity calculation, audio warning synthesizer, and drive simulator. */ (function() { 'use strict'; // --- CONFIGURATION & CAMERA CLUSTERS --- // Key metropolitan corridors with realistic camera intersection networks const DEFAULT_CAMERAS = [ // === PALMDALE & ANTELOPE VALLEY, CA (LOCAL SURVEILLANCE CORRIDORS) === { id: 'cam-plm-1', name: 'Palmdale Blvd & 10th St W (SR-14 Commercial Corridor)', lat: 34.60194, lng: -118.14847, radius: 320, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 6, captures30d: '385,000' }, { id: 'cam-plm-2', name: 'Palmdale Blvd & 20th St E (East Gateway)', lat: 34.60230, lng: -118.10305, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '295,000' }, { id: 'cam-plm-3', name: 'Ave R & 47th St E (East Palmdale Hub)', lat: 34.58081, lng: -118.04497, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '240,000' }, { id: 'cam-plm-4', name: '10th St E & Ave R (Palmdale Civic Center)', lat: 34.57985, lng: -118.11677, radius: 280, agency: 'Palmdale Sheriff Station', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '310,000' }, { id: 'cam-plm-5', name: 'Ave S & 10th St W (Pelona Vista / Retail)', lat: 34.56829, lng: -118.14702, radius: 300, agency: 'Los Angeles County Sheriff', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '330,000' }, { id: 'cam-plm-6', name: 'Ave S & 25th St E', lat: 34.55799, lng: -118.08278, radius: 280, agency: 'Flock Safety / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '215,000' }, { id: 'cam-plm-7', name: 'Ave S & 40th St E', lat: 34.55821, lng: -118.04760, radius: 280, agency: 'Flock Safety / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '190,000' }, { id: 'cam-plm-8', name: 'Rancho Vista Blvd & 15th St W (AV Mall & Amphitheater)', lat: 34.63179, lng: -118.12594, radius: 350, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 6, captures30d: '410,000' }, { id: 'cam-plm-9', name: 'Palmdale Blvd & Highland High / 30th St W', lat: 34.60164, lng: -118.18153, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '260,000' }, { id: 'cam-plm-10', name: 'Pearblossom Hwy & Sierra Hwy (South Gateway)', lat: 34.53625, lng: -118.08431, radius: 320, agency: 'Los Angeles County Sheriff', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '345,000' }, { id: 'cam-plm-11', name: 'Technology Dr & 10th St W (Auto Center Hub)', lat: 34.59852, lng: -118.14467, radius: 280, agency: 'Commercial ALPR Network', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '280,000' }, { id: 'cam-plm-12', name: 'Sierra Hwy & Palmdale Blvd (Transit Station)', lat: 34.58259, lng: -118.13690, radius: 280, agency: 'Palmdale Transit Hub', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '290,000' }, // === LANCASTER, CA (ANTELOPE VALLEY NORTH) === { id: 'cam-lan-1', name: 'Ave L & 10th St W (Lancaster Commercial)', lat: 34.69718, lng: -118.14770, radius: 320, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 6, captures30d: '390,000' }, { id: 'cam-lan-2', name: 'Ave K & 30th St W (Lancaster West)', lat: 34.68925, lng: -118.18368, radius: 300, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 4, captures30d: '275,000' }, { id: 'cam-lan-3', name: 'Ave J & 10th St W (Lancaster Central)', lat: 34.67398, lng: -118.14664, radius: 320, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 6, captures30d: '360,000' }, { id: 'cam-lan-4', name: 'The BLVD & Sierra Hwy (Lancaster Downtown)', lat: 34.69838, lng: -118.13727, radius: 280, agency: 'Lancaster Downtown BLVD', slug: 'lancaster-ca-lasd', cameras: 4, captures30d: '310,000' }, // === NATIONAL DEMO CORRIDORS (Lexington, KY) === { id: 'cam-lex-1', name: 'Lexington PD - New Circle Rd & Nicholasville Rd', lat: 37.9942, lng: -84.5268, radius: 300, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 4, captures30d: '284,500' }, { id: 'cam-lex-2', name: 'Lexington PD - Man o War Blvd & Harrodsburg Rd', lat: 37.9785, lng: -84.5582, radius: 320, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 6, captures30d: '312,000' }, { id: 'cam-lex-3', name: 'Lexington PD - Richmond Rd & New Circle', lat: 38.0125, lng: -84.4715, radius: 280, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 4, captures30d: '265,100' }, { id: 'cam-lex-4', name: 'Lexington PD - Downtown High St & S Broadway', lat: 38.0435, lng: -84.5020, radius: 250, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 2, captures30d: '198,400' }, { id: 'cam-lex-5', name: 'Lexington PD - Russell Cave Rd & I-75', lat: 38.0815, lng: -84.4710, radius: 350, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 8, captures30d: '450,200' }, // Atlanta / Cobb County / Marietta, GA { id: 'cam-atl-1', name: 'Cobb County SO - Cobb Pkwy & Windy Hill Rd', lat: 33.9052, lng: -84.4789, radius: 350, agency: 'Cobb County Sheriff', slug: 'cobb-county-ga', cameras: 6, captures30d: '420,000' }, { id: 'cam-atl-2', name: 'Marietta PD - Roswell St & Fairground St', lat: 33.9482, lng: -84.5365, radius: 300, agency: 'Marietta Police Department', slug: 'marietta-ga-pd', cameras: 4, captures30d: '290,000' }, { id: 'cam-atl-3', name: 'Sandy Springs PD - Roswell Rd & Abernathy Rd', lat: 33.9312, lng: -84.3789, radius: 320, agency: 'Sandy Springs Police', slug: 'sandy-springs-ga-pd', cameras: 4, captures30d: '340,000' }, // California - Bay Area & Los Angeles { id: 'cam-cal-1', name: 'Vallejo PD - Sonoma Blvd & Redwood St', lat: 38.1125, lng: -122.2541, radius: 320, agency: 'Vallejo Police Department', slug: 'vallejo-ca-pd', cameras: 4, captures30d: '310,000' }, { id: 'cam-cal-2', name: 'San Mateo County SO - El Camino Real & 5th Ave', lat: 37.5612, lng: -122.3215, radius: 300, agency: 'San Mateo County SO', slug: 'san-mateo-ca-so', cameras: 4, captures30d: '280,000' }, { id: 'cam-cal-3', name: 'Glendale PD - Brand Blvd & Colorado St', lat: 34.1445, lng: -118.2555, radius: 280, agency: 'Glendale Police Department', slug: 'glendale-az-pd', cameras: 4, captures30d: '260,000' }, // Texas - Fort Worth / Dallas Metro { id: 'cam-dfw-1', name: 'Fort Worth PD - W 7th St & University Dr', lat: 32.7512, lng: -97.3562, radius: 320, agency: 'Fort Worth Police Department', slug: 'fort-worth-tx-pd', cameras: 6, captures30d: '390,000' }, { id: 'cam-dfw-2', name: 'Fort Worth PD - Camp Bowie Blvd & Montgomery', lat: 32.7485, lng: -97.3685, radius: 300, agency: 'Fort Worth Police Department', slug: 'fort-worth-tx-pd', cameras: 4, captures30d: '310,000' } ]; // Simulated Drive Route: Palmdale Boulevard Corridor (Heading East past 10th St W & Sierra Hwy) const SIM_ROUTE = [ { lat: 34.60194, lng: -118.1680, speed: 45, label: 'Palmdale Blvd - Approaching 15th St W' }, { lat: 34.60194, lng: -118.1580, speed: 42, label: 'Palmdale Blvd - Approaching SR-14 Freeway' }, { lat: 34.60194, lng: -118.1525, speed: 38, label: 'ENTERING FLOCK CAMERA RECORDED ZONE' }, { lat: 34.60194, lng: -118.14847, speed: 30, label: 'AT FLOCK CAMERA INTERSECTION (Palmdale Blvd & 10th St W)' }, { lat: 34.60194, lng: -118.1420, speed: 35, label: 'Passing 5th St W' }, { lat: 34.60194, lng: -118.1360, speed: 32, label: 'Approaching Sierra Hwy & Metrolink Crossing' }, { lat: 34.60194, lng: -118.1250, speed: 40, label: 'Palmdale Blvd & 5th St E' }, { lat: 34.60210, lng: -118.1150, speed: 42, label: 'Palmdale Blvd & 10th St E' }, { lat: 34.60220, lng: -118.1080, speed: 38, label: 'Approaching East Palmdale Surveillance Grid' }, { lat: 34.60230, lng: -118.10305, speed: 32, label: 'AT 20TH ST E FLOCK RECORDING INTERSECTION' }, { lat: 34.60230, lng: -118.0950, speed: 45, label: 'Heading East towards 30th St E' } ]; // Default Location: Palmdale, California const PALMDALE_DEFAULT = { lat: 34.60194, lng: -118.14847 }; // --- STATE --- let map = null; let darkTileLayer = null; let satelliteLayer = null; let satelliteLabelsLayer = null; let isSatelliteActive = false; let allCameras = []; let cameraCircles = []; let userMarker = null; let currentPosition = { ...PALMDALE_DEFAULT }; let currentSpeed = 0; let isInZone = false; let nearestCamDistanceFt = 9999; let audioContext = null; let lastChimeTime = 0; let isSimulating = false; let simIndex = 0; let simAnimId = null; let simProgress = 0; // New 5% Driving Mode Enhancements let isFollowCarLocked = true; let radarRangeMode = 'city'; // 'city' (1,000 ft) or 'highway' (2,500 ft) let lastCautionSpokenCamId = null; // --- DOM REFERENCES --- const el = { radarView: document.getElementById('radar-view'), directoryView: document.getElementById('directory-view'), navMapBtn: document.getElementById('nav-map-btn'), navDirBtn: document.getElementById('nav-dir-btn'), driverProfileBtn: document.getElementById('driver-profile-btn'), userProfilePill: document.getElementById('user-profile-pill'), // HUD Elements hudZoneBadge: document.getElementById('hud-zone-badge'), hudSpeedVal: document.getElementById('hud-speed-val'), hudDistVal: document.getElementById('hud-dist-val'), hudDistSub: document.getElementById('hud-dist-sub'), hudCamAgency: document.getElementById('hud-cam-agency'), hudBannerAlert: document.getElementById('hud-banner-alert'), hudSpottedBadge: document.getElementById('hud-spotted-badge'), hudSpottedText: document.getElementById('hud-spotted-text'), hudGaugeState: document.getElementById('hud-gauge-state'), hudSignalMeter: document.getElementById('hud-signal-meter'), // Quick Location Buttons & Search btnLocateMe: document.getElementById('btn-locate-me'), btnLocPalmdale: document.getElementById('btn-loc-palmdale'), btnLocLancaster: document.getElementById('btn-loc-lancaster'), btnLocPalmdaleEast: document.getElementById('btn-loc-palmdale-east'), btnLocPalmdaleSouth: document.getElementById('btn-loc-palmdale-south'), btnLocRancho: document.getElementById('btn-loc-rancho'), mapSearchForm: document.getElementById('map-search-form'), mapSearchInput: document.getElementById('map-search-input'), // Sensitivity & Driving Controls btnSensCity: document.getElementById('btn-sens-city'), btnSensHwy: document.getElementById('btn-sens-hwy'), btnFollowCar: document.getElementById('btn-follow-car'), btnFullscreenMount: document.getElementById('btn-fullscreen-mount'), btnRecenterCar: document.getElementById('btn-recenter-car'), btnQuickLogCam: document.getElementById('btn-quick-log-cam'), // Controls btnLiveGps: document.getElementById('btn-live-gps'), btnMapLayer: document.getElementById('btn-map-layer'), btnTestCamera: document.getElementById('btn-test-camera'), btnTestAlert: document.getElementById('btn-test-alert'), btnSimDrive: document.getElementById('btn-sim-drive'), btnResetSim: document.getElementById('btn-reset-sim'), btnMuteSound: document.getElementById('btn-mute-sound'), btnMobileConnect: document.getElementById('btn-mobile-connect'), btnReportCameraFab: document.getElementById('btn-report-camera-fab'), // Modals loginModal: document.getElementById('login-modal'), reportModal: document.getElementById('report-modal'), loginForm: document.getElementById('login-form'), reportForm: document.getElementById('report-form'), loginCloseBtn: document.getElementById('login-close-btn'), reportCloseBtn: document.getElementById('report-close-btn'), loginGuestBtn: document.getElementById('login-guest-btn') }; // --- INITIALIZATION --- function init() { setupViewSwitching(); initLeafletMap(); loadAllCameraData(); renderCameraZones(); setupUserMarker(); setupHud(); bindEvents(); updateProfileDisplay(); // Auto-detect real location immediately (Palmdale / GPS) attemptAutoLocate(); // Start proximity ticker (runs locally every 300ms, zero API cost) setInterval(checkProximityToCameras, 300); } // --- NAVIGATION VIEW SWITCHER --- function setupViewSwitching() { el.navMapBtn.addEventListener('click', () => switchView('map')); el.navDirBtn.addEventListener('click', () => switchView('dir')); } function switchView(view) { if (view === 'map') { el.radarView.style.display = 'block'; el.directoryView.style.display = 'none'; el.navMapBtn.classList.add('active'); el.navDirBtn.classList.remove('active'); setTimeout(() => { if (map) map.invalidateSize(); }, 100); } else { el.radarView.style.display = 'none'; el.directoryView.style.display = 'block'; el.navMapBtn.classList.remove('active'); el.navDirBtn.classList.add('active'); } } // --- LEAFLET MAP SETUP (HIGH-RESOLUTION ZERO-WATERMARK RADAR DARK MODE) --- function initLeafletMap() { // Center initially on Palmdale, California map = L.map('radar-map', { center: [34.5950, -118.1350], zoom: 14, zoomControl: false, attributionControl: false }); // High-Resolution Radar Dark Tiles (Zero API Key, Zero Watermarks, Sub-Meter Street Clarity up to Zoom 19) darkTileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { subdomains: 'abc', maxZoom: 19, className: 'dark-radar-tile', attribution: '© OpenStreetMap' }).addTo(map); // Prepare optional Satellite Layer (Zero API Key, Zero Watermarks) satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { maxNativeZoom: 18, maxZoom: 19, attribution: '© Esri, Maxar, Earthstar Geographics' }); // High-contrast street labels overlay for satellite view (Zero Watermarks) satelliteLabelsLayer = L.tileLayer('https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', { maxNativeZoom: 18, maxZoom: 19 }); // Minimal zoom controls on bottom-left L.control.zoom({ position: 'bottomleft' }).addTo(map); // When the user drags the map, pause auto-centering and show Recenter button map.on('dragstart', () => { if (isFollowCarLocked) { isFollowCarLocked = false; updateFollowCarUI(); } }); } // --- LOAD CAMERA DATA (DEFAULTS + COMMUNITY + MUNICIPAL HUBS) --- function loadAllCameraData() { allCameras = [...DEFAULT_CAMERAS]; // Load crowdsourced reports from auth store if (window.FlockAuth) { const reports = window.FlockAuth.getReports(); reports.forEach(r => { allCameras.push({ id: r.id, name: `Community Report: ${r.type}`, lat: r.lat, lng: r.lng, radius: 280, agency: 'Crowdsourced Report', slug: '', cameras: 1, captures30d: 'Active Surveillance Zone', isCommunity: true, reportedBy: r.reportedBy, notes: r.notes }); }); } // Load ALL 1,439 geocoded portals from national dataset as active radar zones if (window.FLOCK_DATA && window.FLOCK_DATA.portals) { window.FLOCK_DATA.portals.forEach(p => { if (p.lat && p.lng && !allCameras.some(c => c.slug === p.slug)) { const count = p.cameras || 4; const radiusMeters = Math.min(600, Math.max(250, Math.round(Math.sqrt(count) * 45))); allCameras.push({ id: 'portal-' + p.slug, name: `${p.name} (${p.location}, ${p.state})`, lat: p.lat, lng: p.lng, radius: radiusMeters, agency: p.name, slug: p.slug, cameras: count, captures30d: (p.vehicles_captured ? p.vehicles_captured.toLocaleString() : 'Active') }); } }); } } // --- RENDER RED RADAR CIRCLES & CAMERA NODES --- function renderCameraZones() { // Clear existing cameraCircles.forEach(c => map.removeLayer(c)); cameraCircles = []; allCameras.forEach(cam => { // 1. Red Radar Recording Zone Circle (Translucent buffer) const radarCircle = L.circle([cam.lat, cam.lng], { radius: cam.radius, color: '#ef4444', weight: 1.5, opacity: 0.8, fillColor: '#ef4444', fillOpacity: 0.18, className: 'flock-radar-circle' }).addTo(map); // 2. Camera Center Node Marker const iconHtml = `
`; const customIcon = L.divIcon({ html: iconHtml, className: 'custom-cam-div-icon', iconSize: [28, 28], iconAnchor: [14, 14] }); const marker = L.marker([cam.lat, cam.lng], { icon: customIcon }).addTo(map); // Popup details const popupHtml = `
${cam.slug ? ` ` : ''}
`; marker.bindPopup(popupHtml); cameraCircles.push(radarCircle); cameraCircles.push(marker); }); } // --- VEHICLE MARKER SETUP --- function setupUserMarker() { const vehicleHtml = `
`; const vehicleIcon = L.divIcon({ html: vehicleHtml, className: 'custom-vehicle-div-icon', iconSize: [40, 40], iconAnchor: [20, 20] }); userMarker = L.marker([currentPosition.lat, currentPosition.lng], { icon: vehicleIcon, zIndexOffset: 1000 }).addTo(map); } // --- HUD SETUP --- function setupHud() { el.hudSpeedVal.textContent = '0'; el.hudDistVal.textContent = '—'; el.hudZoneBadge.className = 'hud-badge badge-clear'; el.hudZoneBadge.innerHTML = ` ALL CLEAR`; el.hudCamAgency.textContent = 'Monitoring Highway Network'; } // --- PROXIMITY CALCULATION & ALERT ENGINE --- function checkProximityToCameras() { if (!allCameras.length) return; let closestDistMeters = Infinity; let closestCam = null; allCameras.forEach(cam => { const dist = getHaversineDistanceMeters(currentPosition.lat, currentPosition.lng, cam.lat, cam.lng); if (dist < closestDistMeters) { closestDistMeters = dist; closestCam = cam; } }); const distFt = Math.round(closestDistMeters * 3.28084); nearestCamDistanceFt = distFt; // Check if inside zone radius const insideZone = closestDistMeters <= (closestCam.radius || 300); // Update HUD if (distFt < 5280) { el.hudDistVal.textContent = distFt > 1000 ? (distFt / 1000).toFixed(1) + 'k' : distFt; el.hudDistSub.textContent = 'FT'; } else { el.hudDistVal.textContent = (distFt / 5280).toFixed(1); el.hudDistSub.textContent = 'MI'; } if (closestCam) { el.hudCamAgency.textContent = closestCam.name; } // Update Graduated Radar Signal Strength Meter (Valentine One Style) updateRadarGauge(distFt, insideZone, closestCam); // State Transitions const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200; if (insideZone) { if (!isInZone) { // Just entered zone! Trigger Alert! triggerZoneAlert(closestCam, distFt); } isInZone = true; el.hudZoneBadge.className = 'hud-badge badge-danger'; el.hudZoneBadge.innerHTML = ` IN RECORDED ZONE`; el.hudBannerAlert.classList.add('visible'); el.hudBannerAlert.innerHTML = `
FLOCK CAMERA ACTIVE ZONE

${escapeHtml(closestCam.agency)} • Vehicle Profile Logging

`; } else if (distFt <= cautionThreshold) { // Approaching camera isInZone = false; el.hudZoneBadge.className = 'hud-badge badge-warning'; el.hudZoneBadge.innerHTML = ` CAMERA AHEAD`; el.hudBannerAlert.classList.remove('visible'); } else { // Clear isInZone = false; el.hudZoneBadge.className = 'hud-badge badge-clear'; el.hudZoneBadge.innerHTML = ` ALL CLEAR`; el.hudBannerAlert.classList.remove('visible'); } } // --- 3-STAGE GRADUATED RADAR DETECTOR SIGNAL GAUGE --- function updateRadarGauge(distFt, insideZone, closestCam) { if (!el.hudGaugeState || !el.hudSignalMeter) return; const isHighway = (radarRangeMode === 'highway'); const cautionDist = isHighway ? 2800 : 1500; const warnDist = isHighway ? 1400 : 800; const bars = el.hudSignalMeter.querySelectorAll('.signal-bar'); bars.forEach(b => { b.className = 'signal-bar'; }); if (insideZone || distFt <= 450) { // Level 5: Red Alert In Zone el.hudGaugeState.className = 'hud-gauge-status status-red'; el.hudGaugeState.textContent = 'IN RECORDED ZONE!'; bars.forEach(b => b.classList.add('active-red')); } else if (distFt <= warnDist) { // Level 4: Immediate Proximity el.hudGaugeState.className = 'hud-gauge-status status-red'; el.hudGaugeState.textContent = `ALERT: ${distFt} FT`; bars[0].classList.add('active-red'); bars[1].classList.add('active-red'); bars[2].classList.add('active-red'); bars[3].classList.add('active-red'); } else if (distFt <= cautionDist) { // Level 3: Approaching (Yellow Caution) el.hudGaugeState.className = 'hud-gauge-status status-yellow'; el.hudGaugeState.textContent = `APPROACHING (${Math.round(distFt / 100) * 100} FT)`; bars[0].classList.add('active-yellow'); bars[1].classList.add('active-yellow'); bars[2].classList.add('active-yellow'); // Pre-alert announcement once per camera approach if (closestCam && lastCautionSpokenCamId !== closestCam.id) { lastCautionSpokenCamId = closestCam.id; playCautionBeep(); speakVoiceAlert(`Caution. Approaching surveillance camera in ${Math.round(distFt / 100) * 100} feet.`); } } else if (distFt <= (cautionDist * 1.6)) { // Level 2: Grid Range el.hudGaugeState.className = 'hud-gauge-status status-green'; el.hudGaugeState.textContent = 'MONITORING GRID'; bars[0].classList.add('active-green'); bars[1].classList.add('active-green'); if (closestCam && lastCautionSpokenCamId === closestCam.id && distFt > cautionDist + 500) { lastCautionSpokenCamId = null; // Reset when moved away } } else { // Level 1: All Clear el.hudGaugeState.className = 'hud-gauge-status status-green'; el.hudGaugeState.textContent = 'ALL CLEAR'; bars[0].classList.add('active-green'); lastCautionSpokenCamId = null; } } // --- AUDIO SYNTHESIZERS: CHIRPS & CAUTION BEEPS --- function playLogConfirmationChirp() { try { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } if (audioContext.state === 'suspended') { audioContext.resume(); } const osc = audioContext.createOscillator(); const gain = audioContext.createGain(); osc.type = 'triangle'; osc.frequency.setValueAtTime(587.33, audioContext.currentTime); // D5 osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.1); // A5 osc.frequency.setValueAtTime(1174.66, audioContext.currentTime + 0.2); // D6 gain.gain.setValueAtTime(0.35, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.4); osc.connect(gain); gain.connect(audioContext.destination); osc.start(); osc.stop(audioContext.currentTime + 0.4); } catch (e) { console.warn('Log chirp error:', e); } } function playCautionBeep() { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user && !user.soundEnabled) return; try { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } if (audioContext.state === 'suspended') { audioContext.resume(); } const osc = audioContext.createOscillator(); const gain = audioContext.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(520, audioContext.currentTime); gain.gain.setValueAtTime(0.25, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.22); osc.connect(gain); gain.connect(audioContext.destination); osc.start(); osc.stop(audioContext.currentTime + 0.22); } catch (e) { console.warn('Caution beep error:', e); } } // --- 1-TAP DRIVER QUICK-LOG CAMERA --- function quickLogFlockCameraAtCurrentLocation() { const lat = currentPosition.lat; const lng = currentPosition.lng; const timeStr = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const camId = 'usr-pin-' + Date.now(); const newCam = { id: camId, name: `Driver Pinned Camera (${timeStr})`, lat: lat, lng: lng, radius: 280, agency: 'Driver Crowdsourced ALPR', slug: '', cameras: 1, captures30d: 'Verified Mobile Sighting', isCommunity: true, reportedBy: 'Active Driver', notes: 'Logged via 1-Tap Dashboard Button' }; allCameras.unshift(newCam); // Save to persistence store if (window.FlockAuth) { const reports = window.FlockAuth.getReports(); reports.unshift({ id: camId, type: 'Fixed ALPR Camera', lat: lat, lng: lng, reportedBy: 'Active Driver', timestamp: new Date().toISOString(), notes: '1-Tap Mobile Driver Pin' }); localStorage.setItem('flock_community_reports', JSON.stringify(reports)); } // Immediately draw the new red perimeter circle renderCameraZones(); // Positive audio feedback + voice announcement playLogConfirmationChirp(); speakVoiceAlert("Camera logged at current location. Radar grid updated."); // Animate the button briefly if (el.btnQuickLogCam) { el.btnQuickLogCam.style.transform = 'scale(1.1)'; setTimeout(() => { if (el.btnQuickLogCam) el.btnQuickLogCam.style.transform = ''; }, 300); } showToast("📸 Camera logged at current location & synced to radar!"); } // --- AUTO-CENTER / FOLLOW CAR HANDLER --- function toggleFollowCar() { isFollowCarLocked = !isFollowCarLocked; updateFollowCarUI(); if (isFollowCarLocked) { map.setView([currentPosition.lat, currentPosition.lng], Math.max(map.getZoom(), 15), { animate: true }); } } function recenterOnCar() { isFollowCarLocked = true; updateFollowCarUI(); map.setView([currentPosition.lat, currentPosition.lng], Math.max(map.getZoom(), 15), { animate: true }); } function updateFollowCarUI() { if (el.btnFollowCar) { el.btnFollowCar.classList.toggle('active', isFollowCarLocked); el.btnFollowCar.innerHTML = isFollowCarLocked ? `🎯Follow Car: ON` : `🎯Follow Car: OFF`; } if (el.btnRecenterCar) { el.btnRecenterCar.classList.toggle('hidden', isFollowCarLocked); } } // --- ALERT SENSITIVITY RANGE (CITY vs HIGHWAY) --- function setRadarSensitivity(mode) { radarRangeMode = mode; if (el.btnSensCity) el.btnSensCity.classList.toggle('active', mode === 'city'); if (el.btnSensHwy) el.btnSensHwy.classList.toggle('active', mode === 'highway'); playCautionBeep(); showToast(mode === 'highway' ? '🛣️ Highway Range Active (2,500 ft advance alert)' : '🏙️ City Range Active (1,000 ft alert)'); } // --- DASHBOARD FULLSCREEN MOUNT MODE --- function toggleFullscreenMount() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen().then(() => { if (el.btnFullscreenMount) el.btnFullscreenMount.classList.add('active'); showToast("📱 Fullscreen Dashboard Mode Active"); }).catch(err => { console.warn('Fullscreen request failed:', err); }); } else { if (document.exitFullscreen) { document.exitFullscreen(); if (el.btnFullscreenMount) el.btnFullscreenMount.classList.remove('active'); } } } // --- AUDIO SYNTHESIZER & SPOKEN VOICE ALERTS --- function playRadarChime() { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user && !user.soundEnabled) return; const now = Date.now(); if (now - lastChimeTime < 3000) return; lastChimeTime = now; try { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } if (audioContext.state === 'suspended') { audioContext.resume(); } // Synthesize clean two-tone radar alert const osc = audioContext.createOscillator(); const gain = audioContext.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(880, audioContext.currentTime); // High tone osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.12); // Tone 2 osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.24); // Return tone gain.gain.setValueAtTime(0.35, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.45); osc.connect(gain); gain.connect(audioContext.destination); osc.start(); osc.stop(audioContext.currentTime + 0.45); } catch (e) { console.warn('Audio chime error:', e); } } function speakVoiceAlert(text) { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user && !user.soundEnabled) return; if (!window.speechSynthesis) return; try { window.speechSynthesis.cancel(); const utter = new SpeechSynthesisUtterance(text); utter.rate = 1.05; utter.pitch = 1.0; utter.volume = 1.0; window.speechSynthesis.speak(utter); } catch (e) { console.warn('Speech error:', e); } } function triggerZoneAlert(cam, distFt) { playRadarChime(); speakVoiceAlert("Warning. Flock camera detected ahead. You are entering a recorded surveillance zone."); } // --- TEST ALERT SOUND & VOICE NOW --- function testAlertSoundAndVoice() { playRadarChime(); speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded."); el.hudBannerAlert.classList.add('visible'); el.hudBannerAlert.innerHTML = `
FLOCK CAMERA ACTIVE ZONE

Test Alert • Vehicle Profile Logging Active

`; setTimeout(() => { el.hudBannerAlert.classList.remove('visible'); }, 4500); } // --- PLACE TEST CAMERA 400FT AHEAD OF USER --- function placeTestCameraAhead() { // Offset approx 120m (400 ft) north-east of current location const testCam = { id: 'cam-test-' + Date.now(), name: 'Test Flock Camera (400ft Ahead)', lat: currentPosition.lat + 0.0011, lng: currentPosition.lng + 0.0003, radius: 260, agency: 'Local Test Camera Deployment', slug: '', cameras: 2, captures30d: 'Active Demonstration Zone', isCommunity: true, notes: 'Manually dropped test camera to verify real-time driving alerts.' }; allCameras.push(testCam); renderCameraZones(); map.flyTo([testCam.lat, testCam.lng], 16, { duration: 1 }); playRadarChime(); speakVoiceAlert('Test camera placed 400 feet ahead. Move forward into the red circle to test the alert.'); alert('📍 Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.'); } // --- LIVE GPS CONTINUOUS TRACKING --- let liveGpsWatchId = null; function toggleLiveGps() { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser.'); return; } if (liveGpsWatchId !== null) { navigator.geolocation.clearWatch(liveGpsWatchId); liveGpsWatchId = null; el.btnLiveGps.classList.remove('active'); el.btnLiveGps.innerHTML = `🎯Live GPS Drive Mode`; el.hudSpeedVal.textContent = '0'; return; } if (isSimulating) stopDriveSimulation(); el.btnLiveGps.classList.add('active'); el.btnLiveGps.innerHTML = `🛑Stop Live GPS`; speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.'); liveGpsWatchId = navigator.geolocation.watchPosition( pos => { currentPosition = { lat: pos.coords.latitude, lng: pos.coords.longitude }; // Speed in mph if (pos.coords.speed !== null && !isNaN(pos.coords.speed)) { currentSpeed = Math.round(pos.coords.speed * 2.23694); } else { currentSpeed = 0; } el.hudSpeedVal.textContent = currentSpeed; // Heading if (pos.coords.heading !== null && !isNaN(pos.coords.heading)) { const carElem = document.getElementById('user-vehicle-marker'); if (carElem) carElem.style.transform = `rotate(${pos.coords.heading}deg)`; } if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]); if (isFollowCarLocked) { map.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.8 }); if (currentSpeed > 15 && map.getZoom() < 16) { map.setZoom(16); } } checkProximityToCameras(); }, err => { console.warn('GPS Error:', err); alert('GPS Location Error: ' + err.message + '\nPlease allow location access in your browser.'); }, { enableHighAccuracy: true, maximumAge: 1000, timeout: 10000 } ); } // --- MOBILE PHONE CONNECT INSTRUCTIONS --- function showMobileConnectModal() { const httpUrl = 'http://192.168.1.249:8085'; const httpsUrl = 'https://192.168.1.249:8443'; alert( "📱 HOW TO USE FLOCKRADAR ON YOUR PHONE IN ANTELOPE VALLEY:\n\n" + "1. Connect your phone to your Wi-Fi or hotspot.\n\n" + "2. FOR CONTINUOUS DRIVING GPS (iOS Safari & Android Chrome):\n" + " Open: " + httpsUrl + "\n" + " (Tap 'Show Details' / 'Advanced' -> 'Visit this website' to allow local SSL, then allow location access!)\n\n" + "3. FOR INSTANT OVERVIEW: Open " + httpUrl + "\n\n" + "4. Tap 'Auto-Detect (GPS)' and your device will track and alert you across all Antelope Valley Flock cameras!" ); } // --- DRIVE SIMULATOR ENGINE --- function toggleDriveSimulation() { if (isSimulating) { stopDriveSimulation(); } else { startDriveSimulation(); } } function startDriveSimulation() { isSimulating = true; el.btnSimDrive.innerHTML = ` Pause Simulation`; el.btnSimDrive.classList.add('active'); simIndex = 0; simProgress = 0; currentPosition = { ...SIM_ROUTE[0] }; map.flyTo([currentPosition.lat, currentPosition.lng], 15, { animate: true, duration: 1 }); stepSimulation(); } function stopDriveSimulation() { isSimulating = false; el.btnSimDrive.innerHTML = ` Simulate Drive`; el.btnSimDrive.classList.remove('active'); if (simAnimId) cancelAnimationFrame(simAnimId); } function resetDriveSimulation() { stopDriveSimulation(); simIndex = 0; simProgress = 0; currentPosition = { ...SIM_ROUTE[0] }; currentSpeed = 0; el.hudSpeedVal.textContent = '0'; if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]); map.panTo([currentPosition.lat, currentPosition.lng]); checkProximityToCameras(); } function stepSimulation() { if (!isSimulating) return; const p1 = SIM_ROUTE[simIndex]; const p2 = SIM_ROUTE[simIndex + 1]; if (!p2) { // Reached end of demo route, loop smoothly simIndex = 0; simProgress = 0; simAnimId = requestAnimationFrame(stepSimulation); return; } // Step rate simProgress += 0.004; if (simProgress >= 1) { simProgress = 0; simIndex++; } else { // Interpolate lat / lng currentPosition.lat = p1.lat + (p2.lat - p1.lat) * simProgress; currentPosition.lng = p1.lng + (p2.lng - p1.lng) * simProgress; currentSpeed = Math.round(p1.speed + (p2.speed - p1.speed) * simProgress); // Update marker position if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]); // Rotate car icon based on heading angle const angle = getBearing(p1.lat, p1.lng, p2.lat, p2.lng); const carElem = document.getElementById('user-vehicle-marker'); if (carElem) { carElem.style.transform = `rotate(${angle}deg)`; } // Smoothly pan map along if follow car is locked if (isFollowCarLocked) { map.panTo([currentPosition.lat, currentPosition.lng], { animate: false }); } // Update Speedometer el.hudSpeedVal.textContent = currentSpeed; } simAnimId = requestAnimationFrame(stepSimulation); } // --- HAVERSINE FORMULA --- function getHaversineDistanceMeters(lat1, lon1, lat2, lon2) { const R = 6371e3; // Earth radius in meters const phi1 = lat1 * Math.PI / 180; const phi2 = lat2 * Math.PI / 180; const deltaPhi = (lat2 - lat1) * Math.PI / 180; const deltaLambda = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) + Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function getBearing(startLat, startLng, destLat, destLng) { const startLatRad = startLat * Math.PI / 180; const startLngRad = startLng * Math.PI / 180; const destLatRad = destLat * Math.PI / 180; const destLngRad = destLng * Math.PI / 180; const y = Math.sin(destLngRad - startLngRad) * Math.cos(destLatRad); const x = Math.cos(startLatRad) * Math.sin(destLatRad) - Math.sin(startLatRad) * Math.cos(destLatRad) * Math.cos(destLngRad - startLngRad); let brng = Math.atan2(y, x) * 180 / Math.PI; return (brng + 360) % 360; } // --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) --- async function attemptAutoLocate() { updateSpottedBadge('🛰️ Spotting Antelope Valley Location...'); // Tier 1: Instant IP-based Network Geolocation // Works 100% reliably even when browser GPS is blocked, disabled, or accessed over HTTP let ipFound = false; try { const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' }); const ipData = await resp.json(); if (ipData && ipData.latitude && ipData.longitude) { const ipLat = parseFloat(ipData.latitude); const ipLng = parseFloat(ipData.longitude); const city = ipData.city || 'Palmdale'; const region = ipData.region || 'California'; console.log('IP Network Geolocation spotted:', city, region, ipLat, ipLng); // Center directly on Antelope Valley (Palmdale / Lancaster corridor) let targetLat = ipLat; let targetLng = ipLng; const isAntelopeValley = city.toLowerCase().includes('palmdale') || city.toLowerCase().includes('lancaster') || (ipLat > 34.2 && ipLat < 34.85 && ipLng > -118.45 && ipLng < -117.8); if (isAntelopeValley) { // Snap directly to Palmdale central corridor (Palmdale Blvd & 10th St W) targetLat = 34.60194; targetLng = -118.14847; } updateUserCoordinates(targetLat, targetLng, 0, 0); map.flyTo([targetLat, targetLng], 14, { duration: 1.2 }); updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley (Network)`); updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`); speakVoiceAlert(`Location spotted: Antelope Valley, California.`); checkProximityToCameras(); ipFound = true; } } catch (e) { console.warn('Primary geojs IP lookup failed, trying backup:', e); try { const resp2 = await fetch('https://ipwho.is/', { cache: 'no-cache' }); const ipData2 = await resp2.json(); if (ipData2 && ipData2.success && ipData2.latitude) { updateUserCoordinates(34.60194, -118.14847, 0, 0); map.flyTo([34.60194, -118.14847], 14, { duration: 1.2 }); updateSpottedBadge(`📍 Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`); checkProximityToCameras(); ipFound = true; } } catch (err2) { console.warn('Backup IP lookup failed:', err2); } } if (!ipFound) { updateSpottedBadge('📍 Antelope Valley: Palmdale, CA (Default)'); updateHudLocationStatus('Monitoring Antelope Valley Corridors'); checkProximityToCameras(); } // Tier 2: Hardware Device GPS (Sub-meter accuracy if granted) if (navigator.geolocation && (window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) { navigator.geolocation.getCurrentPosition( pos => { const lat = pos.coords.latitude; const lng = pos.coords.longitude; const acc = Math.round(pos.coords.accuracy || 10); console.log('Hardware GPS lock acquired:', lat, lng, '±' + acc + 'm'); updateUserCoordinates(lat, lng, pos.coords.speed, pos.coords.heading); map.flyTo([lat, lng], 15, { duration: 1.2 }); updateSpottedBadge(`🎯 High-Precision GPS (±${acc}m) • Antelope Valley`); updateHudLocationStatus('📍 Live Driving GPS Active'); checkProximityToCameras(); }, gpsErr => { console.log('Hardware GPS not granted/pending:', gpsErr.message); // Retain the confirmed Antelope Valley IP location smoothly }, { enableHighAccuracy: true, timeout: 6000, maximumAge: 30000 } ); } } function updateSpottedBadge(text) { if (el.hudSpottedText) { el.hudSpottedText.textContent = text; } } function updateUserCoordinates(lat, lng, speed, heading) { currentPosition.lat = lat; currentPosition.lng = lng; if (speed !== null && !isNaN(speed)) { currentSpeed = Math.round(speed * 2.23694); } else { currentSpeed = 0; } el.hudSpeedVal.textContent = currentSpeed; if (userMarker) { userMarker.setLatLng([lat, lng]); } if (heading !== null && !isNaN(heading)) { const carElem = document.getElementById('user-vehicle-marker'); if (carElem) carElem.style.transform = `rotate(${heading}deg)`; } } function updateHudLocationStatus(statusText) { if (el.hudCamAgency) { el.hudCamAgency.textContent = statusText; } } async function useLiveGps() { if (isSimulating) stopDriveSimulation(); updateSpottedBadge('🛰️ Re-detecting location in Antelope Valley...'); updateHudLocationStatus('Acquiring high-precision location...'); // 1. First ensure IP location is refreshed try { const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' }); const ipData = await resp.json(); if (ipData && ipData.latitude) { const city = ipData.city || 'Palmdale'; jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`); updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley`); } } catch (e) {} // 2. Hardware GPS Attempt if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( pos => { updateUserCoordinates(pos.coords.latitude, pos.coords.longitude, pos.coords.speed, pos.coords.heading); map.flyTo([pos.coords.latitude, pos.coords.longitude], 16, { duration: 1.2 }); const acc = Math.round(pos.coords.accuracy || 10); updateSpottedBadge(`🎯 Live GPS Active (±${acc}m)`); updateHudLocationStatus('📍 High-Precision GPS Locked'); playRadarChime(); speakVoiceAlert('GPS position locked in Antelope Valley.'); checkProximityToCameras(); }, err => { console.warn('GPS hardware notice:', err.message); // Show non-blocking informative banner if (err.code === 1) { // PERMISSION_DENIED el.hudBannerAlert.classList.add('visible'); el.hudBannerAlert.innerHTML = `
ANTELOPE VALLEY NETWORK LOCATION ACTIVE

To enable turn-by-turn driving GPS, click the lock icon 🔒 in your browser address bar and select 'Allow Location'.

`; setTimeout(() => { el.hudBannerAlert.classList.remove('visible'); }, 6000); } }, { enableHighAccuracy: true, timeout: 8000 } ); } } // --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) --- function jumpToLocation(lat, lng, label, zoom = 15) { if (isSimulating) stopDriveSimulation(); updateUserCoordinates(lat, lng, 0, 0); map.flyTo([lat, lng], zoom, { duration: 1.2 }); updateHudLocationStatus(`📍 ${label}`); updateSpottedBadge(`📍 Set to: ${label}`); checkProximityToCameras(); } // --- FREE NOMINATIM ADDRESS SEARCH (100% UNMETERED $0.00) --- async function searchLocation(query) { if (!query) return; try { updateHudLocationStatus(`Searching "${query}"...`); const resp = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(query)}`); const data = await resp.json(); if (data && data.length > 0) { const item = data[0]; const lat = parseFloat(item.lat); const lng = parseFloat(item.lon); const shortName = item.display_name.split(',').slice(0, 2).join(','); jumpToLocation(lat, lng, shortName); speakVoiceAlert(`Navigated to ${shortName}`); } else { alert('Location not found. Please try a street or city name (e.g. Palmdale Blvd, Ave S, Lancaster).'); } } catch (e) { console.warn('Search error:', e); alert('Could not complete address search. Please check network connection.'); } } // --- MAP LAYER TOGGLE (DARK CANVAS vs SATELLITE HYBRID) --- function toggleMapLayer() { if (!map) return; isSatelliteActive = !isSatelliteActive; if (isSatelliteActive) { if (darkTileLayer && map.hasLayer(darkTileLayer)) map.removeLayer(darkTileLayer); if (satelliteLayer) satelliteLayer.addTo(map); if (satelliteLabelsLayer) satelliteLabelsLayer.addTo(map); el.btnMapLayer.innerHTML = `🗺️Dark Canvas`; el.btnMapLayer.classList.add('active'); } else { if (satelliteLayer && map.hasLayer(satelliteLayer)) map.removeLayer(satelliteLayer); if (satelliteLabelsLayer && map.hasLayer(satelliteLabelsLayer)) map.removeLayer(satelliteLabelsLayer); if (darkTileLayer) darkTileLayer.addTo(map); el.btnMapLayer.innerHTML = `🛰️Satellite / Dark`; el.btnMapLayer.classList.remove('active'); } } // --- USER PROFILE & LOGIN MODAL --- function updateProfileDisplay() { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user && user.isLoggedIn) { el.userProfilePill.innerHTML = ` 🚘 ${escapeHtml(user.username)} ${user.points} pts `; } else { el.userProfilePill.innerHTML = ` 👤 Driver Login `; } } function openLoginModal() { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user && user.isLoggedIn) { // Pre-fill document.getElementById('login-username').value = user.username; document.getElementById('login-vehicle').value = user.vehicle; } if (typeof el.loginModal.showModal === 'function') { el.loginModal.showModal(); } else { el.loginModal.setAttribute('open', ''); } } function closeLoginModal() { if (typeof el.loginModal.close === 'function') { el.loginModal.close(); } else { el.loginModal.removeAttribute('open'); } } // --- CROWDSOURCED REPORT MODAL --- function openReportModal() { // Fill current coordinates document.getElementById('report-lat').value = currentPosition.lat.toFixed(5); document.getElementById('report-lng').value = currentPosition.lng.toFixed(5); if (typeof el.reportModal.showModal === 'function') { el.reportModal.showModal(); } else { el.reportModal.setAttribute('open', ''); } } function closeReportModal() { if (typeof el.reportModal.close === 'function') { el.reportModal.close(); } else { el.reportModal.removeAttribute('open'); } } // --- EVENT BINDINGS --- function bindEvents() { // Live GPS Continuous Tracking el.btnLiveGps.addEventListener('click', toggleLiveGps); // Map Layer Switcher (Dark Canvas / Satellite) if (el.btnMapLayer) { el.btnMapLayer.addEventListener('click', toggleMapLayer); } // Quick Location Buttons if (el.btnLocateMe) { el.btnLocateMe.addEventListener('click', useLiveGps); } if (el.btnLocPalmdale) { el.btnLocPalmdale.addEventListener('click', () => { jumpToLocation(34.60194, -118.14847, 'Palmdale Blvd & 10th St W'); }); } if (el.btnLocPalmdaleEast) { el.btnLocPalmdaleEast.addEventListener('click', () => { jumpToLocation(34.58081, -118.04497, 'East Palmdale (Ave R & 47th St E)'); }); } if (el.btnLocPalmdaleSouth) { el.btnLocPalmdaleSouth.addEventListener('click', () => { jumpToLocation(34.56829, -118.14702, 'South Palmdale (Ave S & 10th St W)'); }); } if (el.btnLocLancaster) { el.btnLocLancaster.addEventListener('click', () => { jumpToLocation(34.69718, -118.14770, 'Lancaster (Ave L & 10th St W)'); }); } if (el.btnLocRancho) { el.btnLocRancho.addEventListener('click', () => { jumpToLocation(34.63179, -118.12594, 'West AV (Rancho Vista & AV Mall)'); }); } // Address & City Search if (el.mapSearchForm) { el.mapSearchForm.addEventListener('submit', e => { e.preventDefault(); const query = el.mapSearchInput.value.trim(); if (query) searchLocation(query); }); } el.btnTestCamera.addEventListener('click', placeTestCameraAhead); el.btnTestAlert.addEventListener('click', testAlertSoundAndVoice); el.btnSimDrive.addEventListener('click', toggleDriveSimulation); el.btnResetSim.addEventListener('click', resetDriveSimulation); el.btnMobileConnect.addEventListener('click', showMobileConnectModal); // New 5% Driving Enhancement Listeners if (el.btnFollowCar) el.btnFollowCar.addEventListener('click', toggleFollowCar); if (el.btnRecenterCar) el.btnRecenterCar.addEventListener('click', recenterOnCar); if (el.btnQuickLogCam) el.btnQuickLogCam.addEventListener('click', quickLogFlockCameraAtCurrentLocation); if (el.btnFullscreenMount) el.btnFullscreenMount.addEventListener('click', toggleFullscreenMount); if (el.btnSensCity) el.btnSensCity.addEventListener('click', () => setRadarSensitivity('city')); if (el.btnSensHwy) el.btnSensHwy.addEventListener('click', () => setRadarSensitivity('highway')); // Mute toggle el.btnMuteSound.addEventListener('click', () => { const user = window.FlockAuth ? window.FlockAuth.getUser() : null; if (user) { user.soundEnabled = !user.soundEnabled; window.FlockAuth.saveUser(); el.btnMuteSound.innerHTML = user.soundEnabled ? '🔔 Sound: ON' : '🔕 Sound: OFF'; el.btnMuteSound.classList.toggle('active', !user.soundEnabled); } }); // Report Camera FAB if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal); // Profile button click el.driverProfileBtn.addEventListener('click', openLoginModal); // Login Form Submit el.loginForm.addEventListener('submit', e => { e.preventDefault(); const username = document.getElementById('login-username').value.trim(); const vehicle = document.getElementById('login-vehicle').value.trim(); const soundEnabled = document.getElementById('login-sound').checked; if (window.FlockAuth) { window.FlockAuth.login(username, vehicle, soundEnabled); updateProfileDisplay(); } closeLoginModal(); }); // Login as Guest button el.loginGuestBtn.addEventListener('click', () => { if (window.FlockAuth) { window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true); updateProfileDisplay(); } closeLoginModal(); }); // Report Form Submit el.reportForm.addEventListener('submit', e => { e.preventDefault(); const type = document.getElementById('report-mount-type').value; const intersection = document.getElementById('report-intersection').value.trim(); const notes = document.getElementById('report-notes').value.trim(); const lat = parseFloat(document.getElementById('report-lat').value); const lng = parseFloat(document.getElementById('report-lng').value); if (window.FlockAuth) { const newRpt = window.FlockAuth.addReport({ lat, lng, type, intersection, notes }); loadAllCameraData(); renderCameraZones(); updateProfileDisplay(); alert(`Camera report added! +50 Scout Points. Zone is now active on radar.`); } closeReportModal(); }); // Modal Close buttons el.loginCloseBtn.addEventListener('click', closeLoginModal); el.reportCloseBtn.addEventListener('click', closeReportModal); // Global listener for portal inspection from popup window.inspectPortal = function(slug) { switchView('dir'); setTimeout(() => { window.location.hash = slug; }, 150); }; } function escapeHtml(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // --- PWA SERVICE WORKER & MOBILE WAKE LOCK --- if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('./sw.js').catch(err => { console.log('Service Worker registration skipped/failed:', err); }); }); } // Screen Wake Lock to prevent phone screen from going to sleep while driving let wakeLock = null; async function requestWakeLock() { if ('wakeLock' in navigator) { try { wakeLock = await navigator.wakeLock.request('screen'); } catch (err) { console.warn('Wake Lock error:', err); } } } // Auto-unlock Web Audio and Wake Lock on first touch (critical for mobile iOS/Android) function unlockMobileFeatures() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } requestWakeLock(); document.removeEventListener('touchstart', unlockMobileFeatures); document.removeEventListener('click', unlockMobileFeatures); } document.addEventListener('touchstart', unlockMobileFeatures, { passive: true }); document.addEventListener('click', unlockMobileFeatures, { passive: true }); // --- START ENGINE ON DOM READY --- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();