/**
* 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)', crossStreets: 'Palmdale Blvd & 10th St W', city: 'Palmdale', state: 'CA', dept_type: 'SO', lat: 34.60194, lng: -118.14847, radius: 320, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 6, captures30d: '385,000', photoUrl: 'assets/img/flock_pole_cam_palmdale.jpg' },
{ id: 'cam-plm-2', name: 'Palmdale Blvd & 20th St E (East Gateway)', crossStreets: 'Palmdale Blvd & 20th St E', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Ave R & 47th St E', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: '10th St E & Ave R', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Ave S & 10th St W', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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', crossStreets: 'Ave S & 25th St E', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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', crossStreets: 'Ave S & 40th St E', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Rancho Vista Blvd & 15th St W', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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', crossStreets: 'Palmdale Blvd & Highland High / 30th St W', city: 'Palmdale', state: 'CA', dept_type: 'SO', lat: 34.60164, lng: -118.18153, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '260,000', photoUrl: 'assets/img/flock_pole_cam_palmdale.jpg' },
{ id: 'cam-plm-10', name: 'Pearblossom Hwy & Sierra Hwy (South Gateway)', crossStreets: 'Pearblossom Hwy & Sierra Hwy', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Technology Dr & 10th St W', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Sierra Hwy & Palmdale Blvd', city: 'Palmdale', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Ave L & 10th St W', city: 'Lancaster', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Ave K & 30th St W', city: 'Lancaster', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'Ave J & 10th St W', city: 'Lancaster', state: 'CA', dept_type: 'SO', 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)', crossStreets: 'The BLVD & Sierra Hwy', city: 'Lancaster', state: 'CA', dept_type: 'SO', 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', crossStreets: 'New Circle Rd & Nicholasville Rd', city: 'Lexington', state: 'KY', dept_type: 'PD', 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', crossStreets: 'Man o War Blvd & Harrodsburg Rd', city: 'Lexington', state: 'KY', dept_type: 'PD', 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', crossStreets: 'Richmond Rd & New Circle', city: 'Lexington', state: 'KY', dept_type: 'PD', 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', crossStreets: 'High St & S Broadway', city: 'Lexington', state: 'KY', dept_type: 'PD', 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', crossStreets: 'Russell Cave Rd & I-75', city: 'Lexington', state: 'KY', dept_type: 'PD', 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', crossStreets: 'Cobb Pkwy & Windy Hill Rd', city: 'Atlanta', state: 'GA', dept_type: 'SO', 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', crossStreets: 'Roswell St & Fairground St', city: 'Marietta', state: 'GA', dept_type: 'PD', 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', crossStreets: 'Roswell Rd & Abernathy Rd', city: 'Sandy Springs', state: 'GA', dept_type: 'PD', 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', crossStreets: 'Sonoma Blvd & Redwood St', city: 'Vallejo', state: 'CA', dept_type: 'PD', 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', crossStreets: 'El Camino Real & 5th Ave', city: 'San Mateo', state: 'CA', dept_type: 'SO', 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', crossStreets: 'Brand Blvd & Colorado St', city: 'Glendale', state: 'CA', dept_type: 'PD', 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', crossStreets: 'W 7th St & University Dr', city: 'Fort Worth', state: 'TX', dept_type: 'PD', 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', crossStreets: 'Camp Bowie Blvd & Montgomery', city: 'Fort Worth', state: 'TX', dept_type: 'PD', 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 isUserInteractingWithMap = false;
let touchInteractionTimer = null;
let radarRangeMode = 'city'; // 'city' (1,000 ft) or 'highway' (2,500 ft)
let radarAudioMode = localStorage.getItem('flock_radar_audio_mode') || 'full'; // 'full' | 'chime' | 'silent'
let deferredInstallPrompt = null;
let lastCautionSpokenCamId = null;
// Photo Evidence & Consensus State
let activeModalCamera = null;
let photoUploadTarget = 'detail'; // 'detail' | 'report' | 'contest'
let reportPhotoPendingDataUrl = null;
let contestPhotoPendingDataUrl = null;
// --- V26 CIVIC UTILITY FEATURES STATE & CONSTANTS ---
let isCorridorHeatmapActive = false;
let heatmapLayerGroup = null;
let isBackgroundAudioActive = false;
let bgAudioElement = null;
let activeDetourLayerGroup = null;
const SURVEILLANCE_CORRIDORS = {
'palmdale-blvd': {
name: 'Palmdale Blvd Corridor (SR-14 to 50th St E)',
exposure: 'high',
exposureLabel: 'EXPOSURE: HIGH',
cams: 10,
summary: 'Heavy Flock ALPR density every 0.4 miles. Maximum surveillance exposure with real-time LASD hot-list scanning at 5th W, 10th W, 20th E, 30th E, and 47th E.',
detourTitle: '🟢 RECOMMENDED BYPASS: AVENUE R',
detourDesc: 'Take Avenue R eastbound instead — 0 Flock cameras detected, zero delay, connects directly from SR-14 to 50th St E.',
center: [34.5828, -118.0875],
zoom: 13,
corridorPath: [
[34.5828, -118.1525],
[34.5828, -118.1484],
[34.5828, -118.1360],
[34.5828, -118.1150],
[34.5828, -118.0900],
[34.5828, -118.0350]
],
bypassPath: [
[34.5700, -118.1525],
[34.5700, -118.0350]
]
},
'10th-st-west': {
name: '10th St West (Ave S to Ave L)',
exposure: 'high',
exposureLabel: 'EXPOSURE: HIGH',
cams: 8,
summary: 'Primary commercial arterial connecting Palmdale and Lancaster shopping centers. Monitored at Ave S, Rancho Vista Blvd, Technology Dr, Palmdale Blvd, Ave M, and Ave L.',
detourTitle: '🟢 RECOMMENDED BYPASS: 15TH ST WEST',
detourDesc: 'Use 15th St West for residential bypass or SR-14 Freeway for high-speed transit between Palmdale and Lancaster.',
center: [34.6325, -118.1470],
zoom: 12,
corridorPath: [
[34.5680, -118.1470],
[34.6019, -118.1470],
[34.6380, -118.1470],
[34.6970, -118.1470]
],
bypassPath: [
[34.5680, -118.1580],
[34.6019, -118.1580],
[34.6380, -118.1580],
[34.6970, -118.1580]
]
},
'sierra-hwy': {
name: 'Sierra Highway (Palmdale to Lancaster)',
exposure: 'medium',
exposureLabel: 'EXPOSURE: MODERATE',
cams: 5,
summary: 'Historical arterial monitored along railway crossings, Palmdale Blvd, Ave P, and Ave J.',
detourTitle: '🟢 RECOMMENDED BYPASS: DIVISION ST / 10TH ST EAST',
detourDesc: 'Division Street parallel run has minimal commercial surveillance and zero Flock pole hardware.',
center: [34.6325, -118.1250],
zoom: 12,
corridorPath: [
[34.5680, -118.1250],
[34.6019, -118.1250],
[34.6400, -118.1250],
[34.6970, -118.1250]
],
bypassPath: [
[34.5680, -118.1150],
[34.6019, -118.1150],
[34.6400, -118.1150],
[34.6970, -118.1150]
]
},
'avenue-s': {
name: 'Avenue S (Cross-Town Arterial)',
exposure: 'medium',
exposureLabel: 'EXPOSURE: MODERATE',
cams: 4,
summary: 'South Palmdale arterial monitored at 10th St W, 25th St E, and 47th St E.',
detourTitle: '🟢 RECOMMENDED BYPASS: AVENUE T / FORT TEJON RD',
detourDesc: 'Avenue T provides a scenic, camera-free southern cross-town connector with zero ALPR hardware.',
center: [34.5535, -118.1000],
zoom: 13,
corridorPath: [
[34.5535, -118.1700],
[34.5535, -118.0350]
],
bypassPath: [
[34.5420, -118.1700],
[34.5420, -118.0350]
]
},
'avenue-r-clean': {
name: 'Avenue R (Surveillance-Free Bypass)',
exposure: 'clean',
exposureLabel: 'EXPOSURE: CLEAN (0 CAMS)',
cams: 0,
summary: '🟢 Verified 0 detected Flock cameras. Optimal community-vetted clean civic route between West Palmdale and East Palmdale.',
detourTitle: '🟢 OPTIMAL DIRECT ROUTE',
detourDesc: 'No detour needed! You are currently viewing the designated surveillance-free bypass corridor.',
center: [34.5700, -118.0900],
zoom: 13,
corridorPath: [
[34.5700, -118.1700],
[34.5700, -118.0350]
],
bypassPath: null
}
};
const AGENCY_PRA_CONTACTS = {
palmdale: {
name: "City of Palmdale & Los Angeles County Sheriff's Department",
recordsDept: "Records & Public Information Officer / City Clerk",
email: "cityclerk@cityofpalmdale.org",
address: "38300 Sierra Hwy, Palmdale, CA 93550"
},
lancaster: {
name: "City of Lancaster & LASD Lancaster Station",
recordsDept: "City Clerk / Public Records Division",
email: "cityclerk@cityoflancasterca.gov",
address: "44933 Fern Ave, Lancaster, CA 93534"
},
bakersfield: {
name: "Bakersfield Police Department",
recordsDept: "BPD Records Management Division",
email: "bpdrecords@bakersfieldcity.us",
address: "1601 Trujillo Ave, Bakersfield, CA 93301"
},
chp: {
name: "California Highway Patrol (Headquarters)",
recordsDept: "Office of Legal Affairs / CPRA Unit",
email: "pra@chp.ca.gov",
address: "P.O. Box 942898, Sacramento, CA 94298"
},
riverside: {
name: "Riverside County Sheriff's Office",
recordsDept: "Sheriff's Public Records Coordinator",
email: "sheriffrecords@riversidesheriff.org",
address: "4095 Lemon St, Riverside, CA 92501"
},
other: {
name: "Public Law Enforcement Agency",
recordsDept: "Public Records Act Officer",
email: "records@agency.gov",
address: "Municipal Police Department / Sheriff Office"
}
};
// --- 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'),
btnLocOverview: document.getElementById('btn-loc-overview'),
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'),
btnHudSoundToggle: document.getElementById('btn-hud-sound-toggle'),
btnMobileConnect: document.getElementById('btn-mobile-connect'),
btnReportCameraFab: document.getElementById('btn-report-camera-fab'),
// Modals
loginModal: document.getElementById('login-modal'),
reportModal: document.getElementById('report-modal'),
supportModal: document.getElementById('support-modal'),
loginForm: document.getElementById('login-form'),
reportForm: document.getElementById('report-form'),
loginCloseBtn: document.getElementById('login-close-btn'),
reportCloseBtn: document.getElementById('report-close-btn'),
supportCloseBtn: document.getElementById('support-close-btn'),
loginGuestBtn: document.getElementById('login-guest-btn'),
navSupportBtn: document.getElementById('nav-support-btn'),
btnDrawerSupport: document.getElementById('btn-drawer-support'),
// 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 & Desktop 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'),
btnDesktopToolsMenu: document.getElementById('btn-desktop-tools-menu'),
controlsToolsMenu: document.getElementById('controls-tools-menu'),
// Civic Utility Features (v26)
btnCorridorHeatmap: document.getElementById('btn-corridor-heatmap'),
routeCorridorSelect: document.getElementById('route-corridor-select'),
routeScanResult: document.getElementById('route-scan-result'),
scanScoreBadge: document.getElementById('scan-score-badge'),
scanCameraCount: document.getElementById('scan-camera-count'),
scanSummaryText: document.getElementById('scan-summary-text'),
scanDetourBox: document.getElementById('scan-detour-box'),
scanDetourText: document.getElementById('scan-detour-text'),
btnApplyRouteBypass: document.getElementById('btn-apply-route-bypass'),
btnOpenPraGenerator: document.getElementById('btn-open-pra-generator'),
btnToggleBgAudio: document.getElementById('btn-toggle-bg-audio'),
bgAudioLabel: document.getElementById('bg-audio-label'),
btnExportGeoJson: document.getElementById('btn-export-geojson'),
btnExportCsv: document.getElementById('btn-export-csv'),
praModal: document.getElementById('pra-modal'),
praModalCloseBtn: document.getElementById('pra-modal-close-btn'),
praPlateInput: document.getElementById('pra-plate-input'),
praStateInput: document.getElementById('pra-state-input'),
praAgencySelect: document.getElementById('pra-agency-select'),
praRequesterName: document.getElementById('pra-requester-name'),
btnGeneratePra: document.getElementById('btn-generate-pra'),
btnCopyPraLetter: document.getElementById('btn-copy-pra-letter'),
btnEmailPraLetter: document.getElementById('btn-email-pra-letter'),
praLetterText: document.getElementById('pra-letter-text')
};
// --- INITIALIZATION ---
function init() {
setupViewSwitching();
initLeafletMap();
loadAllCameraData();
renderCameraZones();
setupUserMarker();
setupHud();
bindEvents();
updateProfileDisplay();
checkSupporterReturn();
// 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 with full responsive touch zoom
map = L.map('radar-map', {
center: [34.5950, -118.1350],
zoom: 14,
minZoom: 3,
maxZoom: 19,
zoomControl: false,
attributionControl: false,
touchZoom: true,
bounceAtZoomLimits: false,
doubleClickZoom: true,
scrollWheelZoom: true,
dragging: true
});
window.flockMap = map;
// 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',
minZoom: 3,
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,
minZoom: 3,
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,
minZoom: 3,
maxZoom: 19
});
// Tactile radar zoom controls (+ / -) on bottom-left
L.control.zoom({ position: 'bottomleft' }).addTo(map);
function setMapInteracting(active, delay = 0) {
clearTimeout(touchInteractionTimer);
if (delay > 0) {
touchInteractionTimer = setTimeout(() => {
isUserInteractingWithMap = false;
}, delay);
} else {
isUserInteractingWithMap = active;
}
}
// Direct multi-touch gesture detection on map container
const mapEl = document.getElementById('radar-map');
if (mapEl) {
mapEl.addEventListener('touchstart', (e) => {
setMapInteracting(true);
if (e.touches && e.touches.length >= 2) {
// Pinch-to-zoom gesture in progress
if (isFollowCarLocked) {
isFollowCarLocked = false;
updateFollowCarUI();
}
}
}, { passive: true });
mapEl.addEventListener('touchmove', (e) => {
setMapInteracting(true);
if (e.touches && e.touches.length >= 2) {
if (isFollowCarLocked) {
isFollowCarLocked = false;
updateFollowCarUI();
}
}
}, { passive: true });
mapEl.addEventListener('touchend', () => {
setMapInteracting(false, 600);
}, { passive: true });
mapEl.addEventListener('touchcancel', () => {
setMapInteracting(false, 600);
}, { passive: true });
}
// Leaflet drag and zoom gesture hooks
map.on('dragstart', () => {
setMapInteracting(true);
if (isFollowCarLocked) {
isFollowCarLocked = false;
updateFollowCarUI();
}
});
map.on('dragend', () => {
setMapInteracting(false, 500);
});
map.on('zoomstart', () => {
setMapInteracting(true);
// Unlock auto-center so user can freely inspect cameras at whatever zoom level they pick
if (isFollowCarLocked) {
isFollowCarLocked = false;
updateFollowCarUI();
}
});
map.on('zoomend', () => {
setMapInteracting(false, 500);
});
}
// --- 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 = `
`;
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
? `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} CONTESTED SIGHTING`
: (hasPhoto ? `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} PHOTO VERIFIED HARDWARE` : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
const popupHtml = `
`;
marker.bindPopup(popupHtml);
// Clicking radar circle zone directly opens the camera intelligence sheet
radarCircle.on('click', () => {
openCameraHardwareProof(cam.id);
});
cameraCircles.push(radarCircle);
cameraCircles.push(marker);
});
}
// --- VEHICLE MARKER SETUP ---
function setupUserMarker() {
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
const supporterClass = isSupporter ? ' vip-supporter' : '';
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);
}
function updateVehicleSupporterAura() {
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) {
carElem.classList.toggle('vip-supporter', isSupporter);
}
}
function getCardinalDirection(deg) {
if (deg === null || deg === undefined || isNaN(deg)) return 'IDLE';
const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
const normalized = ((deg % 360) + 360) % 360;
const index = Math.round(normalized / 45) % 8;
return directions[index];
}
function updateHudSpeedAndHeading(speed, headingDeg) {
if (el.hudSpeedVal) el.hudSpeedVal.textContent = speed;
const headingElem = document.getElementById('hud-heading-val');
const compassNeedle = document.getElementById('hud-compass-needle');
if (headingElem) {
if (speed > 1 && headingDeg !== null && !isNaN(headingDeg)) {
const cardinal = getCardinalDirection(headingDeg);
headingElem.textContent = `${cardinal} ${Math.round(headingDeg)}°`;
if (compassNeedle) {
compassNeedle.style.transform = `rotate(${Math.round(headingDeg)}deg)`;
compassNeedle.style.opacity = '1';
}
} else {
headingElem.textContent = 'IDLE';
if (compassNeedle) compassNeedle.style.opacity = '0.4';
}
}
}
// --- HUD SETUP ---
function setupHud() {
updateHudSpeedAndHeading(0, null);
el.hudDistVal.textContent = '—';
el.hudZoneBadge.className = 'hud-badge badge-clear';
el.hudZoneBadge.innerHTML = ` ALL CLEAR`;
el.hudCamAgency.textContent = 'Monitoring Highway Network';
setAudioMode(radarAudioMode, false);
updateOfflineStatus();
updateVehicleSupporterAura();
}
// --- 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);
// Audio Proximity Geiger-Counter Chirps
handleProximityChirps(distFt);
// 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 = `
${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}
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.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.5 });
}
}
function recenterOnCar() {
isFollowCarLocked = true;
updateFollowCarUI();
if (!isContinuousTracking && navigator.geolocation) {
startContinuousGpsTracking({ forceRecenter: true, centerMap: true });
} else {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.5 });
}
}
function updateFollowCarUI() {
if (el.btnFollowCar) {
el.btnFollowCar.classList.toggle('active', isFollowCarLocked);
const icon = window.FlockIcons ? FlockIcons.target() : '';
el.btnFollowCar.innerHTML = isFollowCarLocked
? `${icon}Follow Car: ON `
: `${icon}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 ---
let lastChirpTime = 0;
let stationaryChirpCount = 0;
function playRadarTick(freq = 800, volume = 0.22) {
if (radarAudioMode === 'silent') return;
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(freq, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 1.15, audioContext.currentTime + 0.045);
gain.gain.setValueAtTime(volume, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.05);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 0.05);
} catch (e) {
// AudioContext policy
}
}
function handleProximityChirps(distFt) {
if (radarAudioMode === 'silent') return;
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
const maxChirpDist = (radarRangeMode === 'highway') ? 2200 : 1200;
if (distFt > maxChirpDist || distFt <= 0) {
stationaryChirpCount = 0;
return;
}
// Auto-quiet when stopped at a red light or parked
if (currentSpeed < 2) {
if (stationaryChirpCount >= 2) return;
} else {
stationaryChirpCount = 0;
}
let intervalMs = 2400;
let pitchHz = 640;
let chirpVol = 0.16;
if (distFt <= 120) {
intervalMs = 200;
pitchHz = 1100;
chirpVol = 0.30;
} else if (distFt <= 300) {
intervalMs = 420;
pitchHz = 950;
chirpVol = 0.26;
} else if (distFt <= 600) {
intervalMs = 800;
pitchHz = 820;
chirpVol = 0.22;
} else if (distFt <= 1000) {
intervalMs = 1400;
pitchHz = 720;
chirpVol = 0.18;
} else {
intervalMs = 2400;
pitchHz = 640;
chirpVol = 0.15;
}
const now = Date.now();
if (now - lastChirpTime < intervalMs) return;
lastChirpTime = now;
if (currentSpeed < 2) {
stationaryChirpCount++;
}
playRadarTick(pitchHz, chirpVol);
}
function setAudioMode(mode, showFeedback = true) {
radarAudioMode = mode;
localStorage.setItem('flock_radar_audio_mode', mode);
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user) {
user.soundEnabled = (mode !== 'silent');
window.FlockAuth.saveUser();
}
document.querySelectorAll('.btn-audio-mode').forEach(btn => {
btn.classList.toggle('active', btn.dataset.mode === mode);
});
if (el.btnMuteSound) {
const volIcon = mode !== 'silent'
? (window.FlockIcons ? FlockIcons.volume('flock-svg-sm') : '')
: (window.FlockIcons ? FlockIcons.volumeX('flock-svg-sm') : '');
el.btnMuteSound.innerHTML = `${volIcon}Sound: ${mode.toUpperCase()} `;
el.btnMuteSound.classList.toggle('active', mode === 'silent');
}
if (el.btnHudSoundToggle) {
el.btnHudSoundToggle.classList.toggle('is-muted', mode === 'silent');
const iconWrap = document.getElementById('hud-sound-icon');
const label = el.btnHudSoundToggle.querySelector('.capsule-sound-label');
if (mode === 'full') {
if (iconWrap) iconWrap.innerHTML = ` `;
if (label) label.textContent = 'Audio';
el.btnHudSoundToggle.title = 'Audio Mode: Full Voice + Radar Chimes • Tap to switch to Chime Only';
if (showFeedback) {
playRadarTick(880, 0.25);
showToast('🔊 Audio Mode: Full Voice + Radar Chimes');
}
} else if (mode === 'chime') {
if (iconWrap) iconWrap.innerHTML = ` `;
if (label) label.textContent = 'Chime';
el.btnHudSoundToggle.title = 'Audio Mode: Discreet Chime Only (No Speech) • Tap to Mute';
if (showFeedback) {
playRadarTick(1000, 0.25);
showToast('🔔 Audio Mode: Discreet Radar Chimes Only');
}
} else {
if (iconWrap) iconWrap.innerHTML = ` `;
if (label) label.textContent = 'Muted';
el.btnHudSoundToggle.title = 'Audio Mode: Silent (Visual HUD Only) • Tap to Unmute';
if (showFeedback) {
showToast('🔇 Audio Mode: Silent HUD Alerts Only');
}
}
}
}
function cycleAudioMode() {
if (radarAudioMode === 'full') {
setAudioMode('chime', true);
} else if (radarAudioMode === 'chime') {
setAudioMode('silent', true);
} else {
setAudioMode('full', true);
}
}
function playRadarChime() {
if (radarAudioMode === 'silent') return;
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);
osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.12);
osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.24);
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) {
if (radarAudioMode !== 'full') return;
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.");
}
// --- VIRAL SHARING HELPER ---
async function shareApp(customTitle, customText, customUrl) {
const shareData = {
title: customTitle || 'FlockRadar • Live Surveillance Camera Detector',
text: customText || 'Check out FlockRadar — real-time live map & radar detector for Flock Safety surveillance cameras with driving alerts:',
url: customUrl || 'https://flockradar.org/'
};
if (navigator.share) {
try {
await navigator.share(shareData);
showToast('Thanks for sharing FlockRadar!');
return;
} catch (err) {
if (err.name === 'AbortError') return;
}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(shareData.url);
showToast('Link copied to clipboard! Share with drivers.');
return;
} catch (err) {
// Fallback below
}
}
prompt('Copy FlockRadar link to share:', shareData.url);
}
// --- OFFLINE ROAD-READINESS & NETWORK MONITOR ---
function updateOfflineStatus() {
const pill = document.getElementById('tools-offline-status-pill');
const dot = document.getElementById('offline-dot');
const text = document.getElementById('tools-offline-status-text');
if (!pill || !text) return;
if (navigator.onLine) {
pill.classList.remove('offline-mode');
if (dot) dot.className = 'status-indicator-dot dot-online';
text.textContent = '36 Cameras Cached • Ready for Dead Zones';
} else {
pill.classList.add('offline-mode');
if (dot) dot.className = 'status-indicator-dot dot-offline';
text.textContent = '⚡ Offline Driving Active • 36 Cameras in Local Cache';
}
}
// --- PWA CAR HUD APP INSTALLATION ---
function handleInstallApp() {
if (deferredInstallPrompt) {
deferredInstallPrompt.prompt();
deferredInstallPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
showToast('FlockRadar added to your Home Screen!');
}
deferredInstallPrompt = null;
});
} else {
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (isIos) {
alert("To Install on iPhone:\n\n1. Tap the Share button in Safari (square with up arrow at bottom)\n2. Tap 'Add to Home Screen'\n\nFlockRadar will launch fullscreen on your car dashboard!");
} else {
showToast("To install, tap your browser's menu (⋮) and select 'Install app' or 'Add to Home Screen'.");
}
}
}
// --- TEST ALERT SOUND & VOICE NOW ---
function testAlertSoundAndVoice() {
playRadarTick(950, 0.3);
setTimeout(() => playRadarTick(1100, 0.35), 180);
setTimeout(() => {
playRadarChime();
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
}, 400);
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}
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.');
}
// --- REAL-TIME CONTINUOUS GPS TRACKING ENGINE ---
let liveGpsWatchId = null;
let isContinuousTracking = false;
let lastGpsPosition = null; // { lat, lng, timestamp, accuracy, speed, heading }
let lastGpsHeading = 0; // Smooth heading in degrees (0-360)
function updateTrackingUI(isActive) {
if (el.btnLocateMe) {
el.btnLocateMe.classList.toggle('active', isActive);
if (isActive) {
const chips = document.querySelectorAll('.location-quick-chips .loc-chip');
chips.forEach(c => {
if (c !== el.btnLocateMe) c.classList.remove('active');
});
}
}
if (el.btnLiveGps) {
el.btnLiveGps.classList.toggle('active', isActive);
const radioIcon = window.FlockIcons ? FlockIcons.radio() : '';
const stopIcon = window.FlockIcons ? FlockIcons.stopCircle() : '';
el.btnLiveGps.innerHTML = isActive
? `${stopIcon}Stop Live GPS `
: `${radioIcon}Live GPS Drive `;
}
}
function handleGpsPositionUpdate(pos, options = {}) {
if (!pos || !pos.coords) return;
const curLat = pos.coords.latitude;
const curLng = pos.coords.longitude;
const accuracy = Math.round(pos.coords.accuracy || 10);
const timestamp = pos.timestamp || Date.now();
// Initial fix
if (!lastGpsPosition) {
currentPosition.lat = curLat;
currentPosition.lng = curLng;
currentSpeed = (pos.coords.speed !== null && !isNaN(pos.coords.speed) && pos.coords.speed > 0)
? Math.round(pos.coords.speed * 2.23694)
: 0;
lastGpsHeading = (pos.coords.heading !== null && !isNaN(pos.coords.heading) && pos.coords.heading >= 0)
? pos.coords.heading
: 0;
if (userMarker) userMarker.setLatLng([curLat, curLng]);
const carElem = document.getElementById('user-vehicle-marker');
if (carElem && lastGpsHeading) {
carElem.style.transform = `rotate(${Math.round(lastGpsHeading)}deg)`;
}
if (options.flyTo) {
map.flyTo([curLat, curLng], map.getZoom() || 15, { duration: 1.0 });
} else if (isFollowCarLocked && !isUserInteractingWithMap) {
map.panTo([curLat, curLng], { animate: true, duration: 0.5 });
}
el.hudSpeedVal.textContent = currentSpeed;
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
updateSpottedBadge(`Live GPS (±${accuracy}m) • Locked`);
updateHudLocationStatus('Live GPS Active');
checkProximityToCameras();
lastGpsPosition = {
lat: curLat,
lng: curLng,
timestamp,
accuracy,
speed: currentSpeed,
heading: lastGpsHeading
};
return;
}
// Subsequent updates: calculate delta distance & time
const distMeters = getHaversineDistanceMeters(lastGpsPosition.lat, lastGpsPosition.lng, curLat, curLng);
const timeDeltaMs = timestamp - lastGpsPosition.timestamp;
const timeDeltaSec = Math.max(0.1, timeDeltaMs / 1000);
// Skip redundant identical frames if timeDeltaMs < 200ms and dist < 0.4m
if (timeDeltaMs < 200 && distMeters < 0.4) return;
// --- JITTER FILTER ---
// If movement is under 1.5m (GPS drift while stationary or stopped at traffic signal)
if (distMeters < 1.5) {
currentSpeed = 0;
updateHudSpeedAndHeading(0, lastGpsHeading);
updateSpottedBadge(`Live GPS (±${accuracy}m) • Stopped`);
updateHudLocationStatus('Stopped • Monitoring Cameras');
checkProximityToCameras();
lastGpsPosition.timestamp = timestamp;
lastGpsPosition.accuracy = accuracy;
return;
}
// --- ACTIVE MOVEMENT DETECTED (Walking or Driving) ---
let calculatedSpeedMph = 0;
if (pos.coords.speed !== null && !isNaN(pos.coords.speed) && pos.coords.speed >= 0) {
calculatedSpeedMph = pos.coords.speed * 2.23694;
} else if (timeDeltaSec > 0.2 && timeDeltaSec < 45) {
// 1 m/s = 2.23694 mph
calculatedSpeedMph = (distMeters / timeDeltaSec) * 2.23694;
}
// Ignore cellular triangulation jumps (> 120 mph when accuracy is low)
if (calculatedSpeedMph > 120 && accuracy > 30) {
console.warn('Ignoring cellular triangulation jump:', distMeters, 'm');
return;
}
// Smooth speed: responsive yet stable
currentSpeed = Math.round(0.7 * calculatedSpeedMph + 0.3 * currentSpeed);
if (currentSpeed < 1) currentSpeed = 0;
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
// Calculate Heading / Compass Bearing
let targetHeading = lastGpsHeading;
if (pos.coords.heading !== null && !isNaN(pos.coords.heading) && pos.coords.heading >= 0) {
targetHeading = pos.coords.heading;
} else if (distMeters >= 2.0) {
// Compute bearing from movement vector
targetHeading = getBearing(lastGpsPosition.lat, lastGpsPosition.lng, curLat, curLng);
}
lastGpsHeading = targetHeading;
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) {
carElem.style.transform = `rotate(${Math.round(targetHeading)}deg)`;
}
// Update actual coordinates
currentPosition.lat = curLat;
currentPosition.lng = curLng;
if (userMarker) {
userMarker.setLatLng([curLat, curLng]);
}
// Map Following (smooth tracking at user's chosen zoom level, paused during active pinch/drag)
if (isFollowCarLocked && !isUserInteractingWithMap) {
map.panTo([curLat, curLng], { animate: true, duration: 0.5 });
}
// Recalculate camera proximity immediately on every movement step
checkProximityToCameras();
// Update HUD status badges
const statusLabel = currentSpeed >= 15
? `Driving (${currentSpeed} MPH)`
: currentSpeed >= 2
? `Walking (${currentSpeed} MPH)`
: `Moving (${currentSpeed} MPH)`;
updateSpottedBadge(`Live GPS (±${accuracy}m) • ${statusLabel}`);
updateHudLocationStatus(`Live GPS: ${statusLabel}`);
// Update state record
lastGpsPosition = {
lat: curLat,
lng: curLng,
timestamp,
accuracy,
speed: currentSpeed,
heading: lastGpsHeading
};
}
function handleGpsError(err) {
console.warn('GPS watchPosition event:', err.code, err.message);
if (err.code === 1) { // PERMISSION_DENIED
updateSpottedBadge('GPS Permission Denied');
updateHudLocationStatus('Location access blocked in browser');
if (el.hudBannerAlert) {
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}
LOCATION PERMISSION REQUIRED
Please tap the permissions icon in your address bar and select 'Allow Location' to enable live GPS tracking.
`;
setTimeout(() => {
el.hudBannerAlert.classList.remove('visible');
}, 6000);
}
stopContinuousGpsTracking();
} else if (err.code === 2) { // POSITION_UNAVAILABLE
updateSpottedBadge('Acquiring GPS Signal...');
updateHudLocationStatus('Searching for GPS satellites...');
} else if (err.code === 3) { // TIMEOUT
// Do not popup modal alert; seamlessly continue waiting for the next fix
console.log('GPS fix timeout, awaiting next satellite packet...');
}
}
function startContinuousGpsTracking(options = {}) {
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser.');
return;
}
if (isSimulating) stopDriveSimulation();
if (options.forceRecenter || options.centerMap) {
isFollowCarLocked = true;
updateFollowCarUI();
}
requestWakeLock();
// If watch is already active, recenter if requested and return
if (liveGpsWatchId !== null) {
isContinuousTracking = true;
updateTrackingUI(true);
if (lastGpsPosition && (options.forceRecenter || options.centerMap)) {
map.panTo([lastGpsPosition.lat, lastGpsPosition.lng], { animate: true, duration: 0.5 });
}
return;
}
isContinuousTracking = true;
updateTrackingUI(true);
updateSpottedBadge('Acquiring Continuous Live GPS...');
updateHudLocationStatus('Connecting to GPS Satellites...');
const gpsOptions = {
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 20000
};
liveGpsWatchId = navigator.geolocation.watchPosition(
pos => handleGpsPositionUpdate(pos, options),
handleGpsError,
gpsOptions
);
}
function stopContinuousGpsTracking() {
if (liveGpsWatchId !== null) {
navigator.geolocation.clearWatch(liveGpsWatchId);
liveGpsWatchId = null;
}
isContinuousTracking = false;
currentSpeed = 0;
el.hudSpeedVal.textContent = '0';
updateTrackingUI(false);
updateSpottedBadge('Live GPS Paused');
updateHudLocationStatus('Live GPS Paused');
}
function toggleLiveGps() {
if (isContinuousTracking) {
stopContinuousGpsTracking();
speakVoiceAlert('Live GPS tracking paused.');
} else {
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
}
}
// --- 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;
const pauseIcon = window.FlockIcons ? FlockIcons.pause('flock-svg-sm') : '';
el.btnSimDrive.innerHTML = `${pauseIcon}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;
const playIcon = window.FlockIcons ? FlockIcons.play('flock-svg-sm') : '';
el.btnSimDrive.innerHTML = `${playIcon}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 and user is not gesturing
if (isFollowCarLocked && !isUserInteractingWithMap) {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: false });
}
// Update Speedometer & Heading
updateHudSpeedAndHeading(currentSpeed, angle);
}
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 Location...');
// Tier 1: Instant IP-based Network Geolocation
// Provides immediate visual orientation while GPS satellites lock
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);
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) {
targetLat = 34.60194;
targetLng = -118.14847;
}
if (!lastGpsPosition) {
updateUserCoordinates(targetLat, targetLng, 0, 0);
map.flyTo([targetLat, targetLng], 14, { duration: 1.0 });
updateSpottedBadge(`Spotted: ${city} (Network)`);
updateHudLocationStatus(`Monitoring ${city} Corridors`);
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 && !lastGpsPosition) {
updateUserCoordinates(34.60194, -118.14847, 0, 0);
map.flyTo([34.60194, -118.14847], 14, { duration: 1.0 });
updateSpottedBadge(`Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
checkProximityToCameras();
ipFound = true;
}
} catch (err2) {
console.warn('Backup IP lookup failed:', err2);
}
}
if (!ipFound && !lastGpsPosition) {
updateSpottedBadge('Palmdale / Antelope Valley');
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
checkProximityToCameras();
}
// Tier 2: CONTINUOUS HARDWARE GPS TRACKING
// Seamlessly engages watchPosition so your vehicle/walking dot updates continuously
if (navigator.geolocation && (window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
startContinuousGpsTracking({ flyTo: true, centerMap: true });
}
}
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;
}
updateHudSpeedAndHeading(currentSpeed, heading);
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('Activating High-Precision GPS...');
updateHudLocationStatus('Acquiring live satellite coordinates...');
// If we already have a live GPS fix, fly directly to it
if (lastGpsPosition) {
isFollowCarLocked = true;
updateFollowCarUI();
map.flyTo([lastGpsPosition.lat, lastGpsPosition.lng], map.getZoom() || 15, { duration: 1.0 });
checkProximityToCameras();
}
// Engage continuous high-accuracy tracking
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
speakVoiceAlert('Live GPS tracking engaged.');
}
// --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) ---
function jumpToLocation(lat, lng, label, zoom = 15) {
if (isSimulating) stopDriveSimulation();
if (isContinuousTracking) {
stopContinuousGpsTracking();
}
isFollowCarLocked = false;
updateFollowCarUI();
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);
const mapIcon = window.FlockIcons ? FlockIcons.map() : '';
el.btnMapLayer.innerHTML = `${mapIcon}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);
const layersIcon = window.FlockIcons ? FlockIcons.layers() : '';
el.btnMapLayer.innerHTML = `${layersIcon}Satellite / Dark `;
el.btnMapLayer.classList.remove('active');
}
}
// --- USER PROFILE & LOGIN MODAL ---
function updateProfileDisplay() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
const supporterBadge = isSupporter ? `⭐ Supporter ` : '';
if (user && user.isLoggedIn) {
const carSvg = window.FlockIcons ? FlockIcons.car('flock-svg-sm') : '';
el.userProfilePill.innerHTML = `
${carSvg}
${escapeHtml(user.username)}
${user.points} pts
${supporterBadge}
`;
} else {
const userSvg = ` `;
el.userProfilePill.innerHTML = `
${userSvg}
Driver Login
${supporterBadge}
`;
}
updateVehicleSupporterAura();
}
function checkSupporterReturn() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('support') === 'success') {
localStorage.setItem('flock_is_supporter', 'true');
updateProfileDisplay();
setTimeout(() => {
playRadarChime();
speakVoiceAlert('Thank you so much for supporting FlockRadar development!');
showToast('🎉 Thank you for your contribution! You are now a Verified Supporter.');
}, 500);
window.history.replaceState({}, document.title, window.location.pathname);
} else if (urlParams.get('modal') === 'support' || window.location.hash === '#support') {
setTimeout(openSupportModal, 300);
} else if (urlParams.get('cam')) {
setTimeout(() => openCameraHardwareProof(urlParams.get('cam')), 350);
}
}
// --- DEVELOPER SUPPORT MODAL ---
function openSupportModal() {
if (typeof el.supportModal.showModal === 'function') {
el.supportModal.showModal();
} else {
el.supportModal.setAttribute('open', '');
}
}
function closeSupportModal() {
if (typeof el.supportModal.close === 'function') {
el.supportModal.close();
} else {
el.supportModal.removeAttribute('open');
}
}
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');
}
}
// ========================================================================
// V26 CIVIC UTILITY FEATURES: SB 34 PRA, COMMUTE SCANNER, AUDIO GUARD, EXPORT
// ========================================================================
// --- SB 34 PUBLIC RECORDS ACT (CPRA) AUDIT GENERATOR ---
function openPraModal() {
if (!el.praModal) return;
generatePraLetter();
if (typeof el.praModal.showModal === 'function') {
el.praModal.showModal();
} else {
el.praModal.setAttribute('open', '');
}
}
function closePraModal() {
if (!el.praModal) return;
if (typeof el.praModal.close === 'function') {
el.praModal.close();
} else {
el.praModal.removeAttribute('open');
}
}
function generatePraLetter() {
const plate = (el.praPlateInput ? el.praPlateInput.value.trim().toUpperCase() : '') || '[INSERT PLATE #]';
const state = (el.praStateInput ? el.praStateInput.value.trim().toUpperCase() : 'CA') || 'CA';
const requester = (el.praRequesterName ? el.praRequesterName.value.trim() : '') || 'Registered Vehicle Owner / Concerned Citizen';
const agencyKey = el.praAgencySelect ? el.praAgencySelect.value : 'palmdale';
const agency = AGENCY_PRA_CONTACTS[agencyKey] || AGENCY_PRA_CONTACTS.palmdale;
const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const letter = `Date: ${today}
TO:
${agency.recordsDept}
${agency.name}
${agency.address}
Email: ${agency.email}
FROM:
${requester}
Registered Vehicle Owner / Citizen Requester
SUBJECT: PUBLIC RECORDS ACT DEMAND & SB 34 ALPR AUDIT TRAIL DISCLOSURE
STATUTORY AUTHORITIES:
- California Public Records Act (Gov. Code § 7920.000 et seq.)
- California Automated License Plate Recognition Privacy Act (SB 34 / Civ. Code § 1798.90.5 et seq.)
- California Constitution, Article I, Section 3(b)
Dear Records Custodian,
Pursuant to the California Public Records Act (Cal. Gov. Code § 7920 et seq.) and California Civil Code § 1798.90.52, I hereby formally request inspection and copies of all public records and automated license plate reader (ALPR) data logs pertaining to the following vehicle:
VEHICLE LICENSE PLATE: ${plate}
REGISTRATION JURISDICTION: ${state}
TIME PERIOD: All records within the maximum statutory or contractual retention window (including but not limited to the past 30 to 365 days).
SPECIFIC RECORDS REQUESTED:
1. DETECTIONS & SCANS: All automated license plate reader (ALPR) records, timestamps, GPS coordinates, optical character recognition reads, and contextual camera photographs captured by any fixed pole-mounted or mobile camera operated, contracted, or leased by ${agency.name} (including all cameras manufactured by Flock Group Inc. / Flock Safety) containing the license plate ${plate}.
2. SYSTEM SEARCH & AUDIT LOGS: Under Cal. Civil Code § 1798.90.52, an ALPR operator MUST maintain a record of all system queries. Please produce the complete, unredacted audit trail and query log indicating:
(a) The date and time of every query or hot-list search performed for license plate ${plate};
(b) The name, agency, and badge/operator ID of each individual who initiated the search or query;
(c) The specific, articulated law enforcement purpose or case number entered by the operator for each search.
3. DATA SHARING & THIRD-PARTY TRANSFERS: Under California Senate Bill 34 (Civ. Code § 1798.90.55), public agencies are strictly prohibited from selling, sharing, or transferring ALPR data to unauthorized, out-of-state, or federal entities. Please provide all records showing whether reads of plate ${plate} have been shared with, accessed by, or transmitted to any external law enforcement agency, regional intelligence center, federal agency, or out-of-state entity.
4. ALPR USAGE & PRIVACY POLICY: The current, adopted ALPR usage and privacy policy required under Cal. Civil Code § 1798.90.51, including confirmation of public posting and date of last compliance review.
STATUTORY TIMELINE:
Under California Government Code § 7922.535, you have ten (10) calendar days from receipt of this request to notify me whether this request seeks copies of disclosable public records. If any portion of this request is denied, please cite the specific statutory exemption justifying the withholding and provide all non-exempt portions.
Please provide all responsive records in electronic format (CSV, PDF, or spreadsheet) to my contact email or mailing address.
Respectfully submitted,
${requester}
Vehicle Owner / CPRA Requester
Generated via FlockRadar Civic Transparency Network (flockradar.org)`;
if (el.praLetterText) {
el.praLetterText.value = letter;
}
if (el.btnEmailPraLetter) {
const mailSubject = encodeURIComponent(`CPRA Demand & SB 34 ALPR Audit Request: Plate ${plate}`);
const mailBody = encodeURIComponent(letter);
el.btnEmailPraLetter.href = `mailto:${agency.email}?subject=${mailSubject}&body=${mailBody}`;
}
return letter;
}
// --- PRE-TRIP ROUTE SCANNER LOGIC ---
function handleRouteCorridorChange() {
if (!el.routeCorridorSelect || !el.routeScanResult) return;
const corridorId = el.routeCorridorSelect.value;
if (!corridorId || !SURVEILLANCE_CORRIDORS[corridorId]) {
el.routeScanResult.classList.add('hidden');
if (activeDetourLayerGroup && map && map.hasLayer(activeDetourLayerGroup)) {
map.removeLayer(activeDetourLayerGroup);
}
return;
}
const c = SURVEILLANCE_CORRIDORS[corridorId];
el.routeScanResult.classList.remove('hidden');
if (el.scanScoreBadge) {
el.scanScoreBadge.className = `scan-score-badge exposure-${c.exposure}`;
el.scanScoreBadge.textContent = c.exposureLabel;
}
if (el.scanCameraCount) {
el.scanCameraCount.textContent = `${c.cams} Cameras Detected`;
}
if (el.scanSummaryText) {
el.scanSummaryText.textContent = c.summary;
}
if (el.scanDetourBox && el.scanDetourText) {
el.scanDetourBox.style.display = 'flex';
el.scanDetourText.textContent = c.detourDesc;
}
if (el.btnApplyRouteBypass) {
el.btnApplyRouteBypass.textContent = c.bypassPath ? 'Show Clean Bypass on Map' : 'Center Clean Corridor on Map';
}
}
function applyRouteBypassDetour() {
if (!el.routeCorridorSelect || !map) return;
const corridorId = el.routeCorridorSelect.value;
const c = SURVEILLANCE_CORRIDORS[corridorId];
if (!c) return;
if (activeDetourLayerGroup) {
map.removeLayer(activeDetourLayerGroup);
}
activeDetourLayerGroup = L.layerGroup();
// Red or amber surveillance corridor line
const color = c.exposure === 'high' ? '#ef4444' : (c.exposure === 'medium' ? '#f59e0b' : '#10b981');
const corridorLine = L.polyline(c.corridorPath, {
color: color,
weight: 7,
opacity: 0.9,
lineCap: 'round',
lineJoin: 'round',
className: c.exposure === 'high' ? 'leaflet-corridor-high' : ''
});
corridorLine.bindPopup(`${c.name} ${c.exposureLabel} ${c.cams} Cameras Monitored`);
activeDetourLayerGroup.addLayer(corridorLine);
// Green clean bypass line
if (c.bypassPath) {
const bypassLine = L.polyline(c.bypassPath, {
color: '#10b981',
weight: 6,
opacity: 0.95,
lineCap: 'round',
lineJoin: 'round'
});
bypassLine.bindPopup(`${c.detourTitle} ${c.detourDesc}`);
activeDetourLayerGroup.addLayer(bypassLine);
}
activeDetourLayerGroup.addTo(map);
map.flyTo(c.center, c.zoom, { duration: 1.2 });
// Close tools drawer on mobile
if (window.innerWidth <= 900 && el.mapTopControls) {
el.mapTopControls.classList.remove('open-drawer');
if (el.toolsOverlay) el.toolsOverlay.classList.remove('visible');
}
showToast(`🟢 Route Bypass Map updated for ${c.name}`);
}
// --- BACKGROUND AUDIO DRIVING GUARD ---
function toggleBackgroundAudio() {
if (!isBackgroundAudioActive) {
if (!bgAudioElement) {
bgAudioElement = new Audio();
// 1-second inaudible 8kHz silent WAV loop
bgAudioElement.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';
bgAudioElement.loop = true;
}
bgAudioElement.play().then(() => {
isBackgroundAudioActive = true;
if (el.bgAudioLabel) el.bgAudioLabel.textContent = 'Background Audio Guard: ON';
if (el.btnToggleBgAudio) el.btnToggleBgAudio.classList.add('btn-bg-audio-active');
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: 'FlockRadar Driving Guard: Active',
artist: 'FlockRadar Civic Radar',
album: 'Continuous ALPR Audio Alerts Active'
});
navigator.mediaSession.playbackState = 'playing';
}
requestWakeLock();
speakVoiceAlert("Background driving audio guard engaged. Audible radar alerts active over navigation.");
showToast("🟢 Background Audio Guard ON: Radar alerts active over Apple/Google Maps");
}).catch(err => {
console.warn("Background audio start error:", err);
showToast("Audio permission required to start background guard.");
});
} else {
if (bgAudioElement) {
bgAudioElement.pause();
}
isBackgroundAudioActive = false;
if (el.bgAudioLabel) el.bgAudioLabel.textContent = 'Background Audio Guard: OFF';
if (el.btnToggleBgAudio) el.btnToggleBgAudio.classList.remove('btn-bg-audio-active');
if ('mediaSession' in navigator) {
navigator.mediaSession.playbackState = 'none';
}
showToast("Background Audio Guard disengaged");
}
}
// --- CORRIDOR SURVEILLANCE HEATMAP & DENSITY OVERLAY ---
function toggleCorridorHeatmap() {
if (!map) return;
isCorridorHeatmapActive = !isCorridorHeatmapActive;
if (isCorridorHeatmapActive) {
if (!heatmapLayerGroup) {
heatmapLayerGroup = L.layerGroup();
Object.keys(SURVEILLANCE_CORRIDORS).forEach(key => {
const c = SURVEILLANCE_CORRIDORS[key];
const color = c.exposure === 'high' ? '#ef4444' : (c.exposure === 'medium' ? '#f59e0b' : '#10b981');
const weight = c.exposure === 'high' ? 8 : (c.exposure === 'medium' ? 6 : 5);
const opacity = 0.85;
const polyline = L.polyline(c.corridorPath, {
color: color,
weight: weight,
opacity: opacity,
lineCap: 'round',
lineJoin: 'round',
className: c.exposure === 'high' ? 'leaflet-corridor-high' : ''
});
polyline.bindPopup(`
${c.name}
Status: ${c.exposureLabel}
${c.cams} Flock Cameras Monitored
${c.summary}
`);
heatmapLayerGroup.addLayer(polyline);
if (c.bypassPath) {
const bypassLine = L.polyline(c.bypassPath, {
color: '#10b981',
weight: 4,
opacity: 0.7,
dashArray: '5, 8'
});
bypassLine.bindPopup(`
Clean Bypass Corridor
${c.detourDesc}
`);
heatmapLayerGroup.addLayer(bypassLine);
}
});
}
heatmapLayerGroup.addTo(map);
if (el.btnCorridorHeatmap) el.btnCorridorHeatmap.classList.add('active');
showToast("🔥 Surveillance Corridor Density Heatmap: ON");
} else {
if (heatmapLayerGroup && map.hasLayer(heatmapLayerGroup)) {
map.removeLayer(heatmapLayerGroup);
}
if (el.btnCorridorHeatmap) el.btnCorridorHeatmap.classList.remove('active');
showToast("Corridor Density Heatmap: OFF");
}
}
// --- OPEN CIVIC DATA EXPORT (GEOJSON & CSV) ---
function exportCameraData(format) {
if (!allCameras || allCameras.length === 0) {
showToast("No camera data loaded to export!");
return;
}
if (format === 'geojson') {
const geojson = {
type: "FeatureCollection",
metadata: {
title: "FlockRadar Verified Camera Database",
generated: new Date().toISOString(),
jurisdiction: "Antelope Valley, California",
totalCameras: allCameras.length,
license: "Open Civic Data / Public Records Act"
},
features: allCameras.map(cam => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [cam.lng, cam.lat]
},
properties: {
id: cam.id,
name: cam.name || cam.crossStreets || "Flock Safety ALPR",
crossStreets: cam.crossStreets || "",
city: cam.city || "Palmdale",
state: cam.state || "CA",
agency: cam.agency || "City of Palmdale / LASD",
mountType: cam.mountType || "Pole Mounted Solar Falcon",
status: cam.isContested ? "Contested" : (cam.isCommunity ? "Community Reported" : "Verified Active"),
confirmations: cam.confirmedCount || 1,
captures30d: cam.captures30d || "N/A",
latitude: cam.lat,
longitude: cam.lng
}
}))
};
const blob = new Blob([JSON.stringify(geojson, null, 2)], { type: 'application/geo+json' });
downloadBlob(blob, `flockradar_cameras_${new Date().toISOString().slice(0, 10)}.geojson`);
showToast(`Exported ${allCameras.length} cameras as GeoJSON!`);
} else if (format === 'csv') {
const headers = ["Camera_ID", "Location_Name", "Agency", "City", "State", "Latitude", "Longitude", "Mount_Type", "Status", "Confirmations"];
const rows = allCameras.map(cam => [
`"${(cam.id || '').replace(/"/g, '""')}"`,
`"${(cam.name || cam.crossStreets || '').replace(/"/g, '""')}"`,
`"${(cam.agency || '').replace(/"/g, '""')}"`,
`"${(cam.city || 'Palmdale').replace(/"/g, '""')}"`,
`"${(cam.state || 'CA').replace(/"/g, '""')}"`,
cam.lat,
cam.lng,
`"${(cam.mountType || 'Pole Mounted').replace(/"/g, '""')}"`,
`"${(cam.isContested ? 'Contested' : 'Verified Active').replace(/"/g, '""')}"`,
cam.confirmedCount || 1
]);
const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\r\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
downloadBlob(blob, `flockradar_cameras_${new Date().toISOString().slice(0, 10)}.csv`);
showToast(`Exported ${allCameras.length} cameras as CSV spreadsheet!`);
}
}
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
}
// --- 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.innerHTML = `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} CONTESTED LOCATION • RE-VERIFICATION PENDING `;
} else if (hasPhoto) {
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified';
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} VERIFIED PHOTO PROOF • ${meta.confirmations || 1} CONFIRMATIONS `;
} else {
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified';
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''} UNVERIFIED PIN • NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED) `;
}
}
// Photo Container
if (el.camProofContainer) {
if (hasPhoto) {
el.camProofContainer.innerHTML = `
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
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' })}
${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''}
VERIFIED HARDWARE
`;
if (el.btnProofPhotoText) el.btnProofPhotoText.textContent = 'Update Photo';
} else {
el.camProofContainer.innerHTML = `
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-hero') : ''}
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.
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
Snap Camera on Pole (+100 pts)
`;
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.innerHTML = `${window.FlockIcons ? window.FlockIcons.check('flock-svg-sm') : ''} Confirmed (${meta.confirmations || 1})`;
} else {
el.btnVoteConfirmCam.classList.remove('voted');
el.btnVoteConfirmText.innerHTML = `${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} Confirm Active (${meta.confirmations || 1})`;
}
}
// Multi-App Navigation & Pin Share Links
const elGoogle = document.getElementById('btn-proof-nav-google');
const elApple = document.getElementById('btn-proof-nav-apple');
const elWaze = document.getElementById('btn-proof-nav-waze');
const elShareCam = document.getElementById('btn-proof-share-cam');
if (elGoogle) elGoogle.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
if (elApple) elApple.href = `https://maps.apple.com/?daddr=${cam.lat},${cam.lng}&dirflg=d`;
if (elWaze) elWaze.href = `https://waze.com/ul?ll=${cam.lat},${cam.lng}&navigate=yes`;
if (elShareCam) {
elShareCam.onclick = () => {
shareApp(
`Flock Camera: ${cam.name}`,
`Flock surveillance camera spotted at ${cam.name} (${cam.agency || 'Public Roadway'}). Check specs on FlockRadar:`,
`https://flockradar.org/?cam=${cam.id}`
);
};
}
// Lookup Directory Intelligence for this camera & agency
let portal = null;
if (window.FLOCK_DATA && window.FLOCK_DATA.portals) {
if (cam.slug) {
portal = window.FLOCK_DATA.portals.find(p => p.slug === cam.slug);
}
if (!portal && cam.agency) {
portal = window.FLOCK_DATA.portals.find(p =>
(p.name && p.name.toLowerCase().includes(cam.agency.toLowerCase())) ||
(cam.agency.toLowerCase().includes((p.name || '').toLowerCase()))
);
}
}
// Populate Directory Intelligence Fields
const elDirCameras = document.getElementById('proof-dir-cameras');
const elDirVolume = document.getElementById('proof-dir-volume');
const elDirSearches = document.getElementById('proof-dir-searches');
const elDirRetention = document.getElementById('proof-dir-retention');
const elDirSharingCount = document.getElementById('proof-dir-sharing-count');
const elDirSharingTags = document.getElementById('proof-dir-sharing-tags');
const elDirReasonsBox = document.getElementById('proof-dir-reasons-box');
const elDirReasonsList = document.getElementById('proof-dir-reasons-list');
const elOfficialPortalBtn = document.getElementById('btn-proof-official-portal');
const elPortalText = document.getElementById('btn-proof-portal-text');
if (portal) {
// Official scraped portal record
if (elDirCameras) elDirCameras.textContent = portal.cameras ? `${portal.cameras.toLocaleString()} in Fleet` : `${cam.cameras || 4} at Node`;
if (elDirVolume) elDirVolume.textContent = portal.vehicles_captured ? `${portal.vehicles_captured.toLocaleString()} Reads` : (cam.captures30d ? `${cam.captures30d} Reads` : '300,000+');
if (elDirSearches) elDirSearches.textContent = portal.searches ? `${portal.searches.toLocaleString()} Queries` : 'Automated Hotlist';
if (elDirRetention) elDirRetention.textContent = '30 Days Auto-Purge';
const sharedIds = portal.shared_org_ids || [];
if (elDirSharingCount) elDirSharingCount.textContent = `${sharedIds.length} Partner Agencies`;
if (elDirSharingTags) {
const orgsLookup = (window.FLOCK_DATA && window.FLOCK_DATA.orgs) || [];
const topShared = sharedIds.slice(0, 15).map(id => orgsLookup[id] || `Agency #${id}`);
elDirSharingTags.innerHTML = topShared.map(name => `
${escapeHtml(name)}
`).join('') + (sharedIds.length > 15 ? `+${sharedIds.length - 15} more ` : '');
}
if (elDirReasonsList && elDirReasonsBox) {
const reasons = portal.top_search_reasons || {};
const entries = Object.entries(reasons).slice(0, 4);
if (entries.length > 0) {
elDirReasonsBox.style.display = 'block';
elDirReasonsList.innerHTML = entries.map(([r, count]) => `
• ${escapeHtml(r)}
${count.toLocaleString()} searches
`).join('');
} else {
elDirReasonsBox.style.display = 'none';
}
}
if (elOfficialPortalBtn && elPortalText) {
elOfficialPortalBtn.href = portal.url || `https://transparency.flocksafety.com/${portal.slug}`;
elPortalText.textContent = `Open Official ${portal.name} Transparency Portal ↗`;
}
} else {
// Local Antelope Valley / Sheriff corridor or Community Camera
const defaultCameras = cam.cameras || (cam.id && cam.id.includes('plm') ? 4 : 2);
const defaultCaptures = cam.captures30d ? `${cam.captures30d} Reads` : '285,000 Reads';
if (elDirCameras) elDirCameras.textContent = `${defaultCameras} Falcon Node Cameras`;
if (elDirVolume) elDirVolume.textContent = defaultCaptures;
if (elDirSearches) elDirSearches.textContent = 'Automated NCIC Hotlist';
if (elDirRetention) elDirRetention.textContent = '30 Days Auto-Purge';
if (elDirSharingCount) elDirSharingCount.textContent = 'Regional Law Enforcement Network';
if (elDirSharingTags) {
const regionalPartners = [
'Los Angeles County Sheriff (LASD)',
'California Highway Patrol (CHP)',
'Los Angeles Police Department (LAPD)',
'Kern County Sheriff Office',
'San Bernardino County Sheriff',
'Ventura County Sheriff'
];
elDirSharingTags.innerHTML = regionalPartners.map(name => `
${name}
`).join('');
}
if (elDirReasonsList && elDirReasonsBox) {
elDirReasonsBox.style.display = 'block';
elDirReasonsList.innerHTML = `
• Stolen Vehicle / Plate Alerts (10851 CVC)
Real-time
• Felony Warrants & BOLO Broadcasts
Active
• Amber Alert / Missing Person Inquiries
Broadcast
`;
}
if (elOfficialPortalBtn && elPortalText) {
elOfficialPortalBtn.href = `https://transparency.flocksafety.com/${cam.slug || 'palmdale-ca-lasd'}`;
elPortalText.textContent = `Open Official ${cam.agency || 'Agency'} Transparency Portal ↗`;
}
}
// 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) {
el.cameraDetailModal.scrollTop = 0;
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 ? `
` : `
${window.FlockIcons ? FlockIcons.mapPin('flock-svg-sm') : ''}
`}
${escapeHtml(r.name)}
${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} • ${r.dateStr}
${window.FlockIcons ? FlockIcons.trash('flock-svg-sm') : ''}
Delete
`).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)');
});
}
if (el.btnLocOverview) {
el.btnLocOverview.addEventListener('click', () => {
isFollowCarLocked = false;
updateFollowCarUI();
map.flyTo([34.6300, -118.1300], 11, { duration: 1.0 });
showToast('Zoomed out: Showing all Antelope Valley Flock cameras');
});
}
// 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);
}
});
}
// Sound Toggles (Both drawer button and top HUD capsule button)
if (el.btnMuteSound) {
el.btnMuteSound.addEventListener('click', toggleSound);
}
if (el.btnHudSoundToggle) {
el.btnHudSoundToggle.addEventListener('click', cycleAudioMode);
}
// Audio Alert Mode Pill Buttons
document.querySelectorAll('.btn-audio-mode').forEach(btn => {
btn.addEventListener('click', () => {
setAudioMode(btn.dataset.mode, true);
});
});
// Viral Share Buttons
const btnShareTools = document.getElementById('btn-share-radar-tools');
if (btnShareTools) {
btnShareTools.addEventListener('click', () => shareApp());
}
const btnShareSupport = document.getElementById('btn-support-share-app');
if (btnShareSupport) {
btnShareSupport.addEventListener('click', () => {
shareApp(
'Support FlockRadar',
'Help us keep local streets transparent and free of unconstitutional surveillance. Check out FlockRadar live map & driving detector:',
'https://flockradar.org/'
);
});
}
// PWA Install Button
const btnInstallApp = document.getElementById('btn-install-hud-app');
if (btnInstallApp) {
btnInstallApp.addEventListener('click', handleInstallApp);
}
// Offline / Online Network Listeners
window.addEventListener('online', () => {
updateOfflineStatus();
showToast('🟢 Back Online: Cloud database sync active.');
});
window.addEventListener('offline', () => {
updateOfflineStatus();
showToast('⚡ Offline Driving Active: All 36 cameras running from local cache.');
});
// 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 sound = document.getElementById('login-sound').checked;
window.FlockAuth.login(username, vehicle, sound);
updateProfileDisplay();
closeLoginModal();
playLogConfirmationChirp();
speakVoiceAlert(`Welcome to the grid, scout ${username}.`);
showToast(`Welcome back, Scout ${username}!`);
});
// Guest scout button
if (el.loginGuestBtn) {
el.loginGuestBtn.addEventListener('click', () => {
if (window.FlockAuth) {
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
updateProfileDisplay();
}
closeLoginModal();
showToast("Continuing as Anonymous Scout");
});
}
// 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);
// Desktop Tools Dropdown Toggle
if (el.btnDesktopToolsMenu && el.controlsToolsMenu) {
el.btnDesktopToolsMenu.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = el.controlsToolsMenu.classList.toggle('is-open');
el.btnDesktopToolsMenu.classList.toggle('active', isOpen);
});
// Close desktop dropdown on outside click
document.addEventListener('click', (e) => {
if (!el.controlsToolsMenu.contains(e.target) && !el.btnDesktopToolsMenu.contains(e.target)) {
el.controlsToolsMenu.classList.remove('is-open');
el.btnDesktopToolsMenu.classList.remove('active');
}
});
// Auto-close desktop dropdown when any tool item is clicked
el.controlsToolsMenu.querySelectorAll('.btn-tool-item').forEach(btn => {
btn.addEventListener('click', () => {
if (window.innerWidth > 900) {
el.controlsToolsMenu.classList.remove('is-open');
el.btnDesktopToolsMenu.classList.remove('active');
}
});
});
}
// 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 <= 900 && btn !== el.btnDesktopToolsMenu) {
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.innerHTML = isExpanded
? (window.FlockIcons ? FlockIcons.chevronDown('flock-svg-sm') : '▲')
: (window.FlockIcons ? FlockIcons.search('flock-svg-sm') : '▼');
}
}
});
}
// 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);
if (el.supportCloseBtn) el.supportCloseBtn.addEventListener('click', closeSupportModal);
// Developer Support Modal Triggers
if (el.navSupportBtn) {
el.navSupportBtn.addEventListener('click', openSupportModal);
}
if (el.btnDrawerSupport) {
el.btnDrawerSupport.addEventListener('click', () => {
openSupportModal();
});
}
if (el.supportCloseBtn) {
el.supportCloseBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
closeSupportModal();
});
}
if (el.supportModal) {
el.supportModal.addEventListener('click', e => {
if (e.target === el.supportModal) closeSupportModal();
});
el.supportModal.addEventListener('cancel', (e) => {
e.preventDefault();
closeSupportModal();
});
}
// Global listener for camera & portal inspection: NEVER switches views away from radar!
window.inspectPortal = function(slug) {
const matchedCam = allCameras.find(c => c.slug === slug);
if (matchedCam) {
openCameraHardwareProof(matchedCam.id);
} else if (typeof window.openAgencyModal === 'function') {
window.openAgencyModal(slug);
}
};
// Global camera list provider for Directory integration
window.getFlockAllCameras = function() {
return [...allCameras];
};
// Global jump from Directory straight to specific camera on Radar
window.jumpToRadarCamera = function(camId) {
if (el.navMapBtn) el.navMapBtn.click();
setTimeout(() => {
const cam = allCameras.find(c => c.id === camId);
if (cam && map) {
map.flyTo([cam.lat, cam.lng], 16, { animate: true, duration: 1.0 });
}
openCameraHardwareProof(camId);
}, 150);
};
// --- V26 CIVIC UTILITY EVENT BINDINGS ---
// Corridor Heatmap Toggle
if (el.btnCorridorHeatmap) {
el.btnCorridorHeatmap.addEventListener('click', toggleCorridorHeatmap);
}
// Pre-Trip Route Scanner
if (el.routeCorridorSelect) {
el.routeCorridorSelect.addEventListener('change', handleRouteCorridorChange);
}
if (el.btnApplyRouteBypass) {
el.btnApplyRouteBypass.addEventListener('click', applyRouteBypassDetour);
}
// Background Audio Guard
if (el.btnToggleBgAudio) {
el.btnToggleBgAudio.addEventListener('click', toggleBackgroundAudio);
}
// California SB 34 PRA Request Generator
if (el.btnOpenPraGenerator) {
el.btnOpenPraGenerator.addEventListener('click', openPraModal);
}
if (el.praModalCloseBtn) {
el.praModalCloseBtn.addEventListener('click', closePraModal);
}
if (el.praModal) {
el.praModal.addEventListener('click', e => {
if (e.target === el.praModal) closePraModal();
});
el.praModal.addEventListener('cancel', (e) => {
e.preventDefault();
closePraModal();
});
}
if (el.btnGeneratePra) el.btnGeneratePra.addEventListener('click', generatePraLetter);
if (el.praPlateInput) el.praPlateInput.addEventListener('input', generatePraLetter);
if (el.praStateInput) el.praStateInput.addEventListener('input', generatePraLetter);
if (el.praAgencySelect) el.praAgencySelect.addEventListener('change', generatePraLetter);
if (el.praRequesterName) el.praRequesterName.addEventListener('input', generatePraLetter);
if (el.btnCopyPraLetter) {
el.btnCopyPraLetter.addEventListener('click', () => {
const text = el.praLetterText ? el.praLetterText.value : '';
if (text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
playLogConfirmationChirp();
showToast("📋 Formal CPRA Demand Letter copied to clipboard!");
}).catch(() => {
showToast("Letter copied!");
});
} else if (el.praLetterText) {
el.praLetterText.select();
document.execCommand('copy');
showToast("📋 Letter copied to clipboard!");
}
}
});
}
// Open Civic Data Export (GeoJSON & CSV)
if (el.btnExportGeoJson) {
el.btnExportGeoJson.addEventListener('click', () => exportCameraData('geojson'));
}
if (el.btnExportCsv) {
el.btnExportCsv.addEventListener('click', () => exportCameraData('csv'));
}
}
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 or walking
let wakeLock = null;
async function requestWakeLock() {
if ('wakeLock' in navigator && document.visibilityState === 'visible') {
try {
if (!wakeLock || wakeLock.released) {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
wakeLock = null;
});
}
} catch (err) {
console.warn('Wake Lock error:', err.message);
}
}
}
// Re-acquire wake lock if browser tab returns to foreground while continuous tracking is active
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && isContinuousTracking) {
requestWakeLock();
}
});
// 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();
}
})();