/** * 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; // Photo Evidence & Consensus State let activeModalCamera = null; let photoUploadTarget = 'detail'; // 'detail' | 'report' | 'contest' let reportPhotoPendingDataUrl = null; let contestPhotoPendingDataUrl = 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'), // Hardware Proof & Verification Modals cameraDetailModal: document.getElementById('camera-detail-modal'), camDetailTitle: document.getElementById('cam-detail-title'), camDetailVerifyBadge: document.getElementById('cam-detail-verify-badge'), camDetailVerifyText: document.getElementById('cam-detail-verify-text'), camDetailMeta: document.getElementById('cam-detail-meta'), camProofContainer: document.getElementById('cam-proof-container'), specMountType: document.getElementById('spec-mount-type'), specAgency: document.getElementById('spec-agency'), specCaptures: document.getElementById('spec-captures'), specCoords: document.getElementById('spec-coords'), btnVoteConfirmCam: document.getElementById('btn-vote-confirm-cam'), btnVoteConfirmText: document.getElementById('btn-vote-confirm-text'), btnProofAddPhoto: document.getElementById('btn-proof-add-photo'), btnProofPhotoText: document.getElementById('btn-proof-photo-text'), btnProofContest: document.getElementById('btn-proof-contest'), btnProofNavigate: document.getElementById('btn-proof-navigate'), btnProofOpenPortal: document.getElementById('btn-proof-open-portal'), camDetailCloseBtn: document.getElementById('cam-detail-close-btn'), // Contest Modal contestModal: document.getElementById('contest-modal'), contestCloseBtn: document.getElementById('contest-close-btn'), contestForm: document.getElementById('contest-form'), btnContestAddPhoto: document.getElementById('btn-contest-add-photo'), contestPhotoBtnText: document.getElementById('contest-photo-btn-text'), contestPhotoPreviewBox: document.getElementById('contest-photo-preview-box'), contestPhotoPreviewImg: document.getElementById('contest-photo-preview-img'), btnContestRemovePhoto: document.getElementById('btn-contest-remove-photo'), // Report Form Photo Proof btnReportAddPhoto: document.getElementById('btn-report-add-photo'), reportPhotoBtnText: document.getElementById('report-photo-btn-text'), reportPhotoPreviewBox: document.getElementById('report-photo-preview-box'), reportPhotoPreviewImg: document.getElementById('report-photo-preview-img'), btnReportRemovePhoto: document.getElementById('btn-report-remove-photo'), // Native Camera / File Input cameraPhotoFileInput: document.getElementById('camera-photo-file-input'), // Admin Placed Camera Controls camAdminControlBox: document.getElementById('cam-admin-control-box'), btnAdminDeleteCam: document.getElementById('btn-admin-delete-cam'), adminPlacedCount: document.getElementById('admin-placed-count'), adminPlacedCamsList: document.getElementById('admin-placed-cams-list'), btnAdminClearAllPlaced: document.getElementById('btn-admin-clear-all-placed'), btnAdminManagePins: document.getElementById('btn-admin-manage-pins'), // Mobile Driving Controls & Drawer driverHud: document.getElementById('driver-hud'), btnToggleTools: document.getElementById('btn-toggle-tools'), btnToggleHudDetails: document.getElementById('btn-toggle-hud-details'), hudDetailsArrow: document.getElementById('hud-details-arrow'), mapTopControls: document.getElementById('map-top-controls'), btnCloseTools: document.getElementById('btn-close-tools'), toolsOverlay: document.getElementById('tools-overlay') }; // --- 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 + VERIFIED COMMUNITY + MUNICIPAL HUBS) --- function loadAllCameraData() { // Purge any unverified test marks or pins without photo proof if (window.FlockAuth && typeof window.FlockAuth.clearUnverifiedReports === 'function') { window.FlockAuth.clearUnverifiedReports(); } // Filter out any unverified user pins or accidental test marks allCameras = [...DEFAULT_CAMERAS].filter(c => !c.id.startsWith('usr-pin-') && c.notes !== '1-Tap Mobile Driver Pin' && c.name !== 'Community Report: Fixed ALPR Camera' ); // Load crowdsourced reports from auth store (ONLY if photo verified!) if (window.FlockAuth) { const reports = window.FlockAuth.getReports(); reports.forEach(r => { // Strict verification rule: Sighting MUST have verified photo proof! if (!r || !r.photoUrl || r.id.startsWith('usr-pin-')) return; allCameras.push({ id: r.id, name: `Verified Sighting: ${r.type || 'Solar Pole Mount'}`, lat: r.lat, lng: r.lng, radius: 280, agency: 'Community Verified ALPR', slug: '', cameras: 1, captures30d: 'Photo Verified Hardware', 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 => { const meta = window.FlockAuth ? window.FlockAuth.getCameraMeta(cam.id) : { confirmations: 1, hasPhoto: false, isContested: false }; const hasPhoto = meta.hasPhoto && !!meta.photoUrl; // 1. Red Radar Recording Zone Circle (Translucent buffer) const radarCircle = L.circle([cam.lat, cam.lng], { radius: cam.radius, color: meta.isContested ? '#f59e0b' : '#ef4444', weight: 1.5, opacity: 0.8, fillColor: meta.isContested ? '#f59e0b' : '#ef4444', fillOpacity: meta.isContested ? 0.12 : 0.18, className: 'flock-radar-circle' }).addTo(map); // 2. Camera Center Node Marker const iconHtml = `
${hasPhoto ? '
πŸ“Έ
' : ''}
`; 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 badgeText = meta.isContested ? '🟠 CONTESTED SIGHTING' : (hasPhoto ? '🟒 PHOTO VERIFIED HARDWARE' : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE')); const popupHtml = `
${cam.slug ? ` ` : ''} ${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? ` ` : ''}
`; marker.bindPopup(popupHtml); // Clicking marker directly opens the full hardware proof modal marker.on('click', () => { // Leaflet will open popup, but user can also click proof button directly }); 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 (IMMEDIATELY OPENS CAMERA FOR PHOTO PROOF) --- function quickLogFlockCameraAtCurrentLocation() { photoUploadTarget = 'quicklog'; if (el.cameraPhotoFileInput) { el.cameraPhotoFileInput.value = ''; el.cameraPhotoFileInput.click(); } } // --- 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; } renderAdminPlacedList(); 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'); } } // --- SURVEILLANCE ZONE HARDWARE PROOF MODAL --- function openCameraDetailModal(cam) { if (!cam) return; activeModalCamera = cam; const meta = window.FlockAuth ? window.FlockAuth.getCameraMeta(cam.id) : { confirmations: 1, hasPhoto: false, isContested: false }; const hasPhoto = meta.hasPhoto && !!meta.photoUrl; // Title & Agency if (el.camDetailTitle) el.camDetailTitle.textContent = cam.name || 'Flock ALPR Surveillance Installation'; if (el.camDetailMeta) el.camDetailMeta.textContent = `${cam.agency || 'Law Enforcement'} β€’ Monitored Highway Grid`; // Verification Badge if (el.camDetailVerifyBadge && el.camDetailVerifyText) { if (meta.isContested) { el.camDetailVerifyBadge.className = 'cam-verification-badge badge-contested'; el.camDetailVerifyText.textContent = `🟠 CONTESTED SIGHTING (${meta.contests ? meta.contests.length : 1} REPORTS)`; } else if (hasPhoto) { el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified'; el.camDetailVerifyText.textContent = `🟒 VERIFIED PHOTO PROOF β€’ ${meta.confirmations || 1} CONFIRMATIONS`; } else { el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified'; el.camDetailVerifyText.textContent = `🟑 UNVERIFIED PIN β€’ NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)`; } } // Photo Container if (el.camProofContainer) { if (hasPhoto) { el.camProofContainer.innerHTML = `
Flock camera mounted on pole
πŸ“Έ Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}
Logged: ${new Date(meta.photoDate || Date.now()).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
VERIFIED HARDWARE
`; if (el.btnProofPhotoText) el.btnProofPhotoText.textContent = 'Update Photo'; } else { el.camProofContainer.innerHTML = `
πŸ“Έ
No Physical Hardware Photo Yet
Flock cameras stay permanently bolted to utility poles and traffic signals with small solar panels. Snap a photo of the pole to lock this camera into the permanent verified registry.
`; if (el.btnProofPhotoText) el.btnProofPhotoText.textContent = 'Add Photo Proof'; } } // Specs if (el.specMountType) el.specMountType.textContent = cam.type || 'Solar Pole Mount (Falcon)'; if (el.specAgency) el.specAgency.textContent = cam.agency || 'Local Law Enforcement'; if (el.specCaptures) el.specCaptures.textContent = cam.captures30d ? `${cam.captures30d} Reads` : 'Active Corridor'; if (el.specCoords) el.specCoords.textContent = `${cam.lat.toFixed(4)}, ${cam.lng.toFixed(4)}`; // Confirm button state const userConfirmed = window.FlockAuth ? window.FlockAuth.hasUserConfirmed(cam.id) : false; if (el.btnVoteConfirmCam && el.btnVoteConfirmText) { if (userConfirmed) { el.btnVoteConfirmCam.classList.add('voted'); el.btnVoteConfirmText.textContent = `πŸ‘ Confirmed (${meta.confirmations || 1})`; } else { el.btnVoteConfirmCam.classList.remove('voted'); el.btnVoteConfirmText.textContent = `Confirm Active (${meta.confirmations || 1})`; } } // Navigate link if (el.btnProofNavigate) { el.btnProofNavigate.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`; } // Transparency Portal Link const portalBox = document.getElementById('proof-portal-link-box'); if (portalBox) { if (cam.slug) { portalBox.style.display = 'block'; if (el.btnProofOpenPortal) { el.btnProofOpenPortal.onclick = () => { closeCameraDetailModal(); window.inspectPortal(cam.slug); }; } } else { portalBox.style.display = 'none'; } } // Admin Delete Control: ONLY visible for custom placed pins, NEVER for official scraped municipal portals const isPlaced = cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-'); if (el.camAdminControlBox) { el.camAdminControlBox.style.display = isPlaced ? 'block' : 'none'; } // Show modal if (el.cameraDetailModal) { if (typeof el.cameraDetailModal.showModal === 'function') { el.cameraDetailModal.showModal(); } else { el.cameraDetailModal.setAttribute('open', ''); } } } function closeCameraDetailModal() { if (el.cameraDetailModal) { if (typeof el.cameraDetailModal.close === 'function') { el.cameraDetailModal.close(); } else { el.cameraDetailModal.removeAttribute('open'); } } } // --- ADMIN PLACED CAMERA MANAGEMENT --- function renderAdminPlacedList() { if (!el.adminPlacedCamsList) return; const reports = window.FlockAuth ? window.FlockAuth.getReports() : []; // Find all placed cameras from active radar memory (including test cameras) const placedCams = allCameras.filter(c => c.isCommunity || c.id.startsWith('rpt-') || c.id.startsWith('usr-pin-') || c.id.startsWith('cam-test-') || c.id.startsWith('test-') ); // Combine into unique map const combinedMap = new Map(); reports.forEach(r => { combinedMap.set(r.id, { id: r.id, name: r.intersection || r.type || 'Placed Camera', lat: r.lat, lng: r.lng, dateStr: new Date(r.timestamp || Date.now()).toLocaleDateString(), photoUrl: r.photoUrl || null }); }); placedCams.forEach(c => { if (!combinedMap.has(c.id)) { combinedMap.set(c.id, { id: c.id, name: c.name || 'Test Placed Camera', lat: c.lat, lng: c.lng, dateStr: 'Active Session', photoUrl: null }); } }); const items = Array.from(combinedMap.values()); if (el.adminPlacedCount) el.adminPlacedCount.textContent = `${items.length} Placed`; if (items.length === 0) { el.adminPlacedCamsList.innerHTML = `
No custom placed cameras. All active radar cameras are official municipal portals.
`; if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none'; return; } if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block'; el.adminPlacedCamsList.innerHTML = items.map(r => `
${r.photoUrl ? ` Thumbnail ` : ` πŸ“ `}
${escapeHtml(r.name)} ${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} β€’ ${r.dateStr}
`).join(''); } window.adminDeleteCamera = function(camId) { // SECURITY GUARD: Protect official scraped municipal transparency portals if (camId.startsWith('portal-') || (!camId.startsWith('rpt-') && !camId.startsWith('usr-pin-') && !camId.startsWith('test-') && !camId.startsWith('cam-test-'))) { const targetCam = allCameras.find(c => c.id === camId); if (targetCam && !targetCam.isCommunity) { alert("Protected Municipal Portal: Official city and police department transparency records cannot be deleted. Only custom placed cameras can be removed."); return; } } const cam = allCameras.find(c => c.id === camId) || { name: 'Placed Camera', id: camId }; if (!confirm(`Admin Action: Permanently delete placed camera "${cam.name || cam.id}" from radar?`)) { return; } if (window.FlockAuth) { window.FlockAuth.removeReport(camId); } allCameras = allCameras.filter(c => c.id !== camId); renderCameraZones(); renderAdminPlacedList(); closeCameraDetailModal(); playLogConfirmationChirp(); speakVoiceAlert("Placed camera deleted from radar."); showToast("πŸ—‘οΈ Placed camera permanently removed."); }; // --- CONTEST CAMERA MODAL --- function openContestModal(cam) { activeModalCamera = cam; const reasonEl = document.getElementById('contest-reason'); if (reasonEl) reasonEl.selectedIndex = 0; const notesEl = document.getElementById('contest-notes'); if (notesEl) notesEl.value = ''; contestPhotoPendingDataUrl = null; if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.add('hidden'); if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Snap Empty Pole'; if (el.contestModal) { if (typeof el.contestModal.showModal === 'function') { el.contestModal.showModal(); } else { el.contestModal.setAttribute('open', ''); } } } function closeContestModal() { if (el.contestModal) { if (typeof el.contestModal.close === 'function') { el.contestModal.close(); } else { el.contestModal.removeAttribute('open'); } } } // --- PHOTO CAPTURE TRIGGERS --- window.triggerPhotoCaptureForActiveCam = function() { photoUploadTarget = 'detail'; if (el.cameraPhotoFileInput) { el.cameraPhotoFileInput.value = ''; el.cameraPhotoFileInput.click(); } }; function triggerPhotoCaptureForReport() { photoUploadTarget = 'report'; if (el.cameraPhotoFileInput) { el.cameraPhotoFileInput.value = ''; el.cameraPhotoFileInput.click(); } } function triggerPhotoCaptureForContest() { photoUploadTarget = 'contest'; if (el.cameraPhotoFileInput) { el.cameraPhotoFileInput.value = ''; el.cameraPhotoFileInput.click(); } } async function handlePhotoFileSelected(e) { const file = e.target.files && e.target.files[0]; if (!file) return; try { showToast("πŸ“Έ Processing & compressing photo proof..."); const compressedDataUrl = await window.FlockAuth.compressImage(file, 1000, 0.72); if (photoUploadTarget === 'quicklog') { const timeStr = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const newRpt = window.FlockAuth.addReport({ lat: currentPosition.lat, lng: currentPosition.lng, type: 'Solar Pole Mount (Falcon)', intersection: `Driver Sighting (${timeStr})`, notes: 'Physical photo evidence captured by scout on pole', photoUrl: compressedDataUrl }); loadAllCameraData(); renderCameraZones(); updateProfileDisplay(); playLogConfirmationChirp(); speakVoiceAlert("Camera photo proof verified and logged to radar grid. +150 Scout Points!"); showToast("πŸ“Έ Camera photo proof verified & logged! +150 Scout Points!"); const cam = allCameras.find(c => c.id === newRpt.id); if (cam) openCameraDetailModal(cam); } else if (photoUploadTarget === 'detail' && activeModalCamera) { window.FlockAuth.addPhotoProof(activeModalCamera.id, compressedDataUrl, 'Physical pole photo uploaded by scout'); openCameraDetailModal(activeModalCamera); renderCameraZones(); playLogConfirmationChirp(); speakVoiceAlert("Photo proof verified and locked into radar database. +100 Scout Points awarded!"); showToast("πŸ“Έ Photo proof verified & locked! +100 Scout Points!"); } else if (photoUploadTarget === 'report') { reportPhotoPendingDataUrl = compressedDataUrl; if (el.reportPhotoPreviewImg) el.reportPhotoPreviewImg.src = compressedDataUrl; if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.remove('hidden'); if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Change Photo Proof'; showToast("πŸ“Έ Photo proof attached to camera report!"); } else if (photoUploadTarget === 'contest') { contestPhotoPendingDataUrl = compressedDataUrl; if (el.contestPhotoPreviewImg) el.contestPhotoPreviewImg.src = compressedDataUrl; if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.remove('hidden'); if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Change Empty Pole Photo'; showToast("πŸ“Έ Empty pole photo attached to contest report!"); } } catch (err) { console.error('Photo processing error:', err); alert('Could not process photo: ' + (err.message || 'Unknown error')); } } window.openCameraHardwareProof = function(camId) { const cam = allCameras.find(c => c.id === camId); if (cam) openCameraDetailModal(cam); }; // --- 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')); // Camera Hardware Proof & Voting Events if (el.camDetailCloseBtn) el.camDetailCloseBtn.addEventListener('click', closeCameraDetailModal); if (el.btnVoteConfirmCam) { el.btnVoteConfirmCam.addEventListener('click', () => { if (!activeModalCamera) return; const res = window.FlockAuth.confirmCamera(activeModalCamera.id); if (res.success) { playLogConfirmationChirp(); speakVoiceAlert("Camera confirmed active on pole. +25 Scout Points!"); showToast(`πŸ‘ Confirmed active on pole! Total: ${res.count} confirmations.`); openCameraDetailModal(activeModalCamera); renderCameraZones(); updateProfileDisplay(); } else { showToast(res.message || "Already confirmed!"); } }); } if (el.btnProofAddPhoto) { el.btnProofAddPhoto.addEventListener('click', () => { window.triggerPhotoCaptureForActiveCam(); }); } if (el.btnProofContest) { el.btnProofContest.addEventListener('click', () => { if (!activeModalCamera) return; closeCameraDetailModal(); openContestModal(activeModalCamera); }); } // Admin Delete Camera Button (Detail Modal) if (el.btnAdminDeleteCam) { el.btnAdminDeleteCam.addEventListener('click', () => { if (!activeModalCamera) return; window.adminDeleteCamera(activeModalCamera.id); }); } // Contest Modal Events if (el.contestCloseBtn) el.contestCloseBtn.addEventListener('click', closeContestModal); if (el.btnContestAddPhoto) el.btnContestAddPhoto.addEventListener('click', triggerPhotoCaptureForContest); if (el.btnContestRemovePhoto) { el.btnContestRemovePhoto.addEventListener('click', () => { contestPhotoPendingDataUrl = null; if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.add('hidden'); if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Snap Empty Pole'; }); } if (el.contestForm) { el.contestForm.addEventListener('submit', e => { e.preventDefault(); if (!activeModalCamera) return; const reason = document.getElementById('contest-reason').value; const notes = document.getElementById('contest-notes').value.trim(); const res = window.FlockAuth.contestCamera(activeModalCamera.id, reason, notes); closeContestModal(); if (res.success) { if (res.isContested) { speakVoiceAlert("Camera flagged as contested by community consensus."); showToast("⚠️ Location marked Contested by community consensus."); } else { showToast(`⚠️ Contest submitted (${res.contestCount}/3 flags for consensus review).`); } renderCameraZones(); } else { showToast(res.message || "Already reported!"); } }); } // Report Form Photo Proof Events if (el.btnReportAddPhoto) el.btnReportAddPhoto.addEventListener('click', triggerPhotoCaptureForReport); if (el.btnReportRemovePhoto) { el.btnReportRemovePhoto.addEventListener('click', () => { reportPhotoPendingDataUrl = null; if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.add('hidden'); if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Snap Camera on Pole (+100 pts)'; }); } // Native Camera / File Input Change Event if (el.cameraPhotoFileInput) { el.cameraPhotoFileInput.addEventListener('change', handlePhotoFileSelected); } // Nearest Camera HUD tap opens details const hudLocationBar = document.querySelector('.hud-location-bar'); if (hudLocationBar) { hudLocationBar.style.cursor = 'pointer'; hudLocationBar.title = 'Click to inspect nearest camera hardware proof & vote'; hudLocationBar.addEventListener('click', () => { if (allCameras.length > 0) { let closest = null; let minDist = Infinity; allCameras.forEach(cam => { const d = getHaversineDistanceMeters(currentPosition.lat, currentPosition.lng, cam.lat, cam.lng); if (d < minDist) { minDist = d; closest = cam; } }); if (closest) openCameraDetailModal(closest); } }); } // 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(); }); // Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals) if (el.btnAdminClearAllPlaced) { el.btnAdminClearAllPlaced.addEventListener('click', () => { if (!confirm("Admin Action: Clear ALL custom placed pins and radar test markers? (The 1,439 official municipal portals will remain permanent and protected).")) { return; } if (window.FlockAuth) { window.FlockAuth.clearAllPlacedReports(); } allCameras = allCameras.filter(c => !c.isCommunity && !c.id.startsWith('rpt-') && !c.id.startsWith('usr-pin-') && !c.id.startsWith('test-') && !c.id.startsWith('cam-test-')); renderCameraZones(); renderAdminPlacedList(); playLogConfirmationChirp(); speakVoiceAlert("All custom placed pins cleared from radar."); showToast("πŸ—‘οΈ All custom placed pins cleared."); }); } // Admin Placed Pins Shortcut in Map Top Controls if (el.btnAdminManagePins) { el.btnAdminManagePins.addEventListener('click', () => { openLoginModal(); setTimeout(() => { const adminSec = document.querySelector('.admin-profile-section'); if (adminSec) adminSec.scrollIntoView({ behavior: 'smooth' }); }, 120); }); } // Mobile Driving Controls Drawer function openToolsDrawer() { if (el.mapTopControls) el.mapTopControls.classList.add('open-drawer'); if (el.toolsOverlay) el.toolsOverlay.classList.add('visible'); } function closeToolsDrawer() { if (el.mapTopControls) el.mapTopControls.classList.remove('open-drawer'); if (el.toolsOverlay) el.toolsOverlay.classList.remove('visible'); } if (el.btnToggleTools) { el.btnToggleTools.addEventListener('click', (e) => { e.stopPropagation(); if (el.mapTopControls && el.mapTopControls.classList.contains('open-drawer')) { closeToolsDrawer(); } else { openToolsDrawer(); } }); } if (el.btnCloseTools) el.btnCloseTools.addEventListener('click', closeToolsDrawer); if (el.toolsOverlay) el.toolsOverlay.addEventListener('click', closeToolsDrawer); // Auto-close tools drawer on mobile when clicking any action button if (el.mapTopControls) { el.mapTopControls.querySelectorAll('.btn-hud-action').forEach(btn => { btn.addEventListener('click', () => { if (window.innerWidth <= 768) { closeToolsDrawer(); } }); }); } // Toggle HUD Expanded Details on Mobile if (el.btnToggleHudDetails) { el.btnToggleHudDetails.addEventListener('click', (e) => { e.stopPropagation(); if (el.driverHud) { const isExpanded = el.driverHud.classList.toggle('is-expanded'); if (el.hudDetailsArrow) el.hudDetailsArrow.textContent = isExpanded ? 'β–²' : 'πŸ”'; } }); } // Sync radar when reports updated window.addEventListener('flock_reports_updated', () => { loadAllCameraData(); renderCameraZones(); renderAdminPlacedList(); }); // 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, photoUrl: reportPhotoPendingDataUrl }); reportPhotoPendingDataUrl = null; if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.add('hidden'); if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Snap Camera on Pole (+100 pts)'; loadAllCameraData(); renderCameraZones(); updateProfileDisplay(); playLogConfirmationChirp(); speakVoiceAlert("Camera sighting broadcast to radar network!"); showToast(`Camera report added! +${newRpt.photoUrl ? '150' : '50'} Scout Points.`); } 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(); } })();