/**
* FLOCKRADAR - LIVE MAPPING & PROXIMITY ALERT ENGINE
* Full-screen Waze-style radar map with red camera recording zones,
* real-time proximity calculation, audio warning synthesizer, and drive simulator.
*/
(function() {
'use strict';
// --- CONFIGURATION & CAMERA CLUSTERS ---
// Key metropolitan corridors with realistic camera intersection networks
const DEFAULT_CAMERAS = [
// === PALMDALE & ANTELOPE VALLEY, CA (LOCAL SURVEILLANCE CORRIDORS) ===
{ id: 'cam-plm-1', name: 'Palmdale Blvd & 10th St W (SR-14 Commercial Corridor)', lat: 34.60194, lng: -118.14847, radius: 320, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 6, captures30d: '385,000' },
{ id: 'cam-plm-2', name: 'Palmdale Blvd & 20th St E (East Gateway)', lat: 34.60230, lng: -118.10305, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '295,000' },
{ id: 'cam-plm-3', name: 'Ave R & 47th St E (East Palmdale Hub)', lat: 34.58081, lng: -118.04497, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '240,000' },
{ id: 'cam-plm-4', name: '10th St E & Ave R (Palmdale Civic Center)', lat: 34.57985, lng: -118.11677, radius: 280, agency: 'Palmdale Sheriff Station', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '310,000' },
{ id: 'cam-plm-5', name: 'Ave S & 10th St W (Pelona Vista / Retail)', lat: 34.56829, lng: -118.14702, radius: 300, agency: 'Los Angeles County Sheriff', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '330,000' },
{ id: 'cam-plm-6', name: 'Ave S & 25th St E', lat: 34.55799, lng: -118.08278, radius: 280, agency: 'Flock Safety / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '215,000' },
{ id: 'cam-plm-7', name: 'Ave S & 40th St E', lat: 34.55821, lng: -118.04760, radius: 280, agency: 'Flock Safety / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '190,000' },
{ id: 'cam-plm-8', name: 'Rancho Vista Blvd & 15th St W (AV Mall & Amphitheater)', lat: 34.63179, lng: -118.12594, radius: 350, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 6, captures30d: '410,000' },
{ id: 'cam-plm-9', name: 'Palmdale Blvd & Highland High / 30th St W', lat: 34.60164, lng: -118.18153, radius: 300, agency: 'City of Palmdale / LASD', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '260,000' },
{ id: 'cam-plm-10', name: 'Pearblossom Hwy & Sierra Hwy (South Gateway)', lat: 34.53625, lng: -118.08431, radius: 320, agency: 'Los Angeles County Sheriff', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '345,000' },
{ id: 'cam-plm-11', name: 'Technology Dr & 10th St W (Auto Center Hub)', lat: 34.59852, lng: -118.14467, radius: 280, agency: 'Commercial ALPR Network', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '280,000' },
{ id: 'cam-plm-12', name: 'Sierra Hwy & Palmdale Blvd (Transit Station)', lat: 34.58259, lng: -118.13690, radius: 280, agency: 'Palmdale Transit Hub', slug: 'palmdale-ca-lasd', cameras: 4, captures30d: '290,000' },
// === LANCASTER, CA (ANTELOPE VALLEY NORTH) ===
{ id: 'cam-lan-1', name: 'Ave L & 10th St W (Lancaster Commercial)', lat: 34.69718, lng: -118.14770, radius: 320, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 6, captures30d: '390,000' },
{ id: 'cam-lan-2', name: 'Ave K & 30th St W (Lancaster West)', lat: 34.68925, lng: -118.18368, radius: 300, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 4, captures30d: '275,000' },
{ id: 'cam-lan-3', name: 'Ave J & 10th St W (Lancaster Central)', lat: 34.67398, lng: -118.14664, radius: 320, agency: 'City of Lancaster / LASD', slug: 'lancaster-ca-lasd', cameras: 6, captures30d: '360,000' },
{ id: 'cam-lan-4', name: 'The BLVD & Sierra Hwy (Lancaster Downtown)', lat: 34.69838, lng: -118.13727, radius: 280, agency: 'Lancaster Downtown BLVD', slug: 'lancaster-ca-lasd', cameras: 4, captures30d: '310,000' },
// === NATIONAL DEMO CORRIDORS (Lexington, KY) ===
{ id: 'cam-lex-1', name: 'Lexington PD - New Circle Rd & Nicholasville Rd', lat: 37.9942, lng: -84.5268, radius: 300, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 4, captures30d: '284,500' },
{ id: 'cam-lex-2', name: 'Lexington PD - Man o War Blvd & Harrodsburg Rd', lat: 37.9785, lng: -84.5582, radius: 320, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 6, captures30d: '312,000' },
{ id: 'cam-lex-3', name: 'Lexington PD - Richmond Rd & New Circle', lat: 38.0125, lng: -84.4715, radius: 280, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 4, captures30d: '265,100' },
{ id: 'cam-lex-4', name: 'Lexington PD - Downtown High St & S Broadway', lat: 38.0435, lng: -84.5020, radius: 250, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 2, captures30d: '198,400' },
{ id: 'cam-lex-5', name: 'Lexington PD - Russell Cave Rd & I-75', lat: 38.0815, lng: -84.4710, radius: 350, agency: 'Lexington Police Department', slug: 'lexington-ky-pd', cameras: 8, captures30d: '450,200' },
// Atlanta / Cobb County / Marietta, GA
{ id: 'cam-atl-1', name: 'Cobb County SO - Cobb Pkwy & Windy Hill Rd', lat: 33.9052, lng: -84.4789, radius: 350, agency: 'Cobb County Sheriff', slug: 'cobb-county-ga', cameras: 6, captures30d: '420,000' },
{ id: 'cam-atl-2', name: 'Marietta PD - Roswell St & Fairground St', lat: 33.9482, lng: -84.5365, radius: 300, agency: 'Marietta Police Department', slug: 'marietta-ga-pd', cameras: 4, captures30d: '290,000' },
{ id: 'cam-atl-3', name: 'Sandy Springs PD - Roswell Rd & Abernathy Rd', lat: 33.9312, lng: -84.3789, radius: 320, agency: 'Sandy Springs Police', slug: 'sandy-springs-ga-pd', cameras: 4, captures30d: '340,000' },
// California - Bay Area & Los Angeles
{ id: 'cam-cal-1', name: 'Vallejo PD - Sonoma Blvd & Redwood St', lat: 38.1125, lng: -122.2541, radius: 320, agency: 'Vallejo Police Department', slug: 'vallejo-ca-pd', cameras: 4, captures30d: '310,000' },
{ id: 'cam-cal-2', name: 'San Mateo County SO - El Camino Real & 5th Ave', lat: 37.5612, lng: -122.3215, radius: 300, agency: 'San Mateo County SO', slug: 'san-mateo-ca-so', cameras: 4, captures30d: '280,000' },
{ id: 'cam-cal-3', name: 'Glendale PD - Brand Blvd & Colorado St', lat: 34.1445, lng: -118.2555, radius: 280, agency: 'Glendale Police Department', slug: 'glendale-az-pd', cameras: 4, captures30d: '260,000' },
// Texas - Fort Worth / Dallas Metro
{ id: 'cam-dfw-1', name: 'Fort Worth PD - W 7th St & University Dr', lat: 32.7512, lng: -97.3562, radius: 320, agency: 'Fort Worth Police Department', slug: 'fort-worth-tx-pd', cameras: 6, captures30d: '390,000' },
{ id: 'cam-dfw-2', name: 'Fort Worth PD - Camp Bowie Blvd & Montgomery', lat: 32.7485, lng: -97.3685, radius: 300, agency: 'Fort Worth Police Department', slug: 'fort-worth-tx-pd', cameras: 4, captures30d: '310,000' }
];
// Simulated Drive Route: Palmdale Boulevard Corridor (Heading East past 10th St W & Sierra Hwy)
const SIM_ROUTE = [
{ lat: 34.60194, lng: -118.1680, speed: 45, label: 'Palmdale Blvd - Approaching 15th St W' },
{ lat: 34.60194, lng: -118.1580, speed: 42, label: 'Palmdale Blvd - Approaching SR-14 Freeway' },
{ lat: 34.60194, lng: -118.1525, speed: 38, label: 'ENTERING FLOCK CAMERA RECORDED ZONE' },
{ lat: 34.60194, lng: -118.14847, speed: 30, label: 'AT FLOCK CAMERA INTERSECTION (Palmdale Blvd & 10th St W)' },
{ lat: 34.60194, lng: -118.1420, speed: 35, label: 'Passing 5th St W' },
{ lat: 34.60194, lng: -118.1360, speed: 32, label: 'Approaching Sierra Hwy & Metrolink Crossing' },
{ lat: 34.60194, lng: -118.1250, speed: 40, label: 'Palmdale Blvd & 5th St E' },
{ lat: 34.60210, lng: -118.1150, speed: 42, label: 'Palmdale Blvd & 10th St E' },
{ lat: 34.60220, lng: -118.1080, speed: 38, label: 'Approaching East Palmdale Surveillance Grid' },
{ lat: 34.60230, lng: -118.10305, speed: 32, label: 'AT 20TH ST E FLOCK RECORDING INTERSECTION' },
{ lat: 34.60230, lng: -118.0950, speed: 45, label: 'Heading East towards 30th St E' }
];
// Default Location: Palmdale, California
const PALMDALE_DEFAULT = { lat: 34.60194, lng: -118.14847 };
// --- STATE ---
let map = null;
let darkTileLayer = null;
let satelliteLayer = null;
let satelliteLabelsLayer = null;
let isSatelliteActive = false;
let allCameras = [];
let cameraCircles = [];
let userMarker = null;
let currentPosition = { ...PALMDALE_DEFAULT };
let currentSpeed = 0;
let isInZone = false;
let nearestCamDistanceFt = 9999;
let audioContext = null;
let lastChimeTime = 0;
let isSimulating = false;
let simIndex = 0;
let simAnimId = null;
let simProgress = 0;
// New 5% Driving Mode Enhancements
let isFollowCarLocked = true;
let radarRangeMode = 'city'; // 'city' (1,000 ft) or 'highway' (2,500 ft)
let lastCautionSpokenCamId = null;
// Photo Evidence & Consensus State
let activeModalCamera = null;
let photoUploadTarget = 'detail'; // 'detail' | 'report' | 'contest'
let reportPhotoPendingDataUrl = null;
let contestPhotoPendingDataUrl = null;
// --- DOM REFERENCES ---
const el = {
radarView: document.getElementById('radar-view'),
directoryView: document.getElementById('directory-view'),
navMapBtn: document.getElementById('nav-map-btn'),
navDirBtn: document.getElementById('nav-dir-btn'),
driverProfileBtn: document.getElementById('driver-profile-btn'),
userProfilePill: document.getElementById('user-profile-pill'),
// HUD Elements
hudZoneBadge: document.getElementById('hud-zone-badge'),
hudSpeedVal: document.getElementById('hud-speed-val'),
hudDistVal: document.getElementById('hud-dist-val'),
hudDistSub: document.getElementById('hud-dist-sub'),
hudCamAgency: document.getElementById('hud-cam-agency'),
hudBannerAlert: document.getElementById('hud-banner-alert'),
hudSpottedBadge: document.getElementById('hud-spotted-badge'),
hudSpottedText: document.getElementById('hud-spotted-text'),
hudGaugeState: document.getElementById('hud-gauge-state'),
hudSignalMeter: document.getElementById('hud-signal-meter'),
// Quick Location Buttons & Search
btnLocateMe: document.getElementById('btn-locate-me'),
btnLocPalmdale: document.getElementById('btn-loc-palmdale'),
btnLocLancaster: document.getElementById('btn-loc-lancaster'),
btnLocPalmdaleEast: document.getElementById('btn-loc-palmdale-east'),
btnLocPalmdaleSouth: document.getElementById('btn-loc-palmdale-south'),
btnLocRancho: document.getElementById('btn-loc-rancho'),
mapSearchForm: document.getElementById('map-search-form'),
mapSearchInput: document.getElementById('map-search-input'),
// Sensitivity & Driving Controls
btnSensCity: document.getElementById('btn-sens-city'),
btnSensHwy: document.getElementById('btn-sens-hwy'),
btnFollowCar: document.getElementById('btn-follow-car'),
btnFullscreenMount: document.getElementById('btn-fullscreen-mount'),
btnRecenterCar: document.getElementById('btn-recenter-car'),
btnQuickLogCam: document.getElementById('btn-quick-log-cam'),
// Controls
btnLiveGps: document.getElementById('btn-live-gps'),
btnMapLayer: document.getElementById('btn-map-layer'),
btnTestCamera: document.getElementById('btn-test-camera'),
btnTestAlert: document.getElementById('btn-test-alert'),
btnSimDrive: document.getElementById('btn-sim-drive'),
btnResetSim: document.getElementById('btn-reset-sim'),
btnMuteSound: document.getElementById('btn-mute-sound'),
btnMobileConnect: document.getElementById('btn-mobile-connect'),
btnReportCameraFab: document.getElementById('btn-report-camera-fab'),
// Modals
loginModal: document.getElementById('login-modal'),
reportModal: document.getElementById('report-modal'),
loginForm: document.getElementById('login-form'),
reportForm: document.getElementById('report-form'),
loginCloseBtn: document.getElementById('login-close-btn'),
reportCloseBtn: document.getElementById('report-close-btn'),
loginGuestBtn: document.getElementById('login-guest-btn'),
// Hardware Proof & Verification Modals
cameraDetailModal: document.getElementById('camera-detail-modal'),
camDetailTitle: document.getElementById('cam-detail-title'),
camDetailVerifyBadge: document.getElementById('cam-detail-verify-badge'),
camDetailVerifyText: document.getElementById('cam-detail-verify-text'),
camDetailMeta: document.getElementById('cam-detail-meta'),
camProofContainer: document.getElementById('cam-proof-container'),
specMountType: document.getElementById('spec-mount-type'),
specAgency: document.getElementById('spec-agency'),
specCaptures: document.getElementById('spec-captures'),
specCoords: document.getElementById('spec-coords'),
btnVoteConfirmCam: document.getElementById('btn-vote-confirm-cam'),
btnVoteConfirmText: document.getElementById('btn-vote-confirm-text'),
btnProofAddPhoto: document.getElementById('btn-proof-add-photo'),
btnProofPhotoText: document.getElementById('btn-proof-photo-text'),
btnProofContest: document.getElementById('btn-proof-contest'),
btnProofNavigate: document.getElementById('btn-proof-navigate'),
btnProofOpenPortal: document.getElementById('btn-proof-open-portal'),
camDetailCloseBtn: document.getElementById('cam-detail-close-btn'),
// Contest Modal
contestModal: document.getElementById('contest-modal'),
contestCloseBtn: document.getElementById('contest-close-btn'),
contestForm: document.getElementById('contest-form'),
btnContestAddPhoto: document.getElementById('btn-contest-add-photo'),
contestPhotoBtnText: document.getElementById('contest-photo-btn-text'),
contestPhotoPreviewBox: document.getElementById('contest-photo-preview-box'),
contestPhotoPreviewImg: document.getElementById('contest-photo-preview-img'),
btnContestRemovePhoto: document.getElementById('btn-contest-remove-photo'),
// Report Form Photo Proof
btnReportAddPhoto: document.getElementById('btn-report-add-photo'),
reportPhotoBtnText: document.getElementById('report-photo-btn-text'),
reportPhotoPreviewBox: document.getElementById('report-photo-preview-box'),
reportPhotoPreviewImg: document.getElementById('report-photo-preview-img'),
btnReportRemovePhoto: document.getElementById('btn-report-remove-photo'),
// Native Camera / File Input
cameraPhotoFileInput: document.getElementById('camera-photo-file-input'),
// Admin Placed Camera Controls
camAdminControlBox: document.getElementById('cam-admin-control-box'),
btnAdminDeleteCam: document.getElementById('btn-admin-delete-cam'),
adminPlacedCount: document.getElementById('admin-placed-count'),
adminPlacedCamsList: document.getElementById('admin-placed-cams-list'),
btnAdminClearAllPlaced: document.getElementById('btn-admin-clear-all-placed'),
btnAdminManagePins: document.getElementById('btn-admin-manage-pins')
};
// --- INITIALIZATION ---
function init() {
setupViewSwitching();
initLeafletMap();
loadAllCameraData();
renderCameraZones();
setupUserMarker();
setupHud();
bindEvents();
updateProfileDisplay();
// Auto-detect real location immediately (Palmdale / GPS)
attemptAutoLocate();
// Start proximity ticker (runs locally every 300ms, zero API cost)
setInterval(checkProximityToCameras, 300);
}
// --- NAVIGATION VIEW SWITCHER ---
function setupViewSwitching() {
el.navMapBtn.addEventListener('click', () => switchView('map'));
el.navDirBtn.addEventListener('click', () => switchView('dir'));
}
function switchView(view) {
if (view === 'map') {
el.radarView.style.display = 'block';
el.directoryView.style.display = 'none';
el.navMapBtn.classList.add('active');
el.navDirBtn.classList.remove('active');
setTimeout(() => {
if (map) map.invalidateSize();
}, 100);
} else {
el.radarView.style.display = 'none';
el.directoryView.style.display = 'block';
el.navMapBtn.classList.remove('active');
el.navDirBtn.classList.add('active');
}
}
// --- LEAFLET MAP SETUP (HIGH-RESOLUTION ZERO-WATERMARK RADAR DARK MODE) ---
function initLeafletMap() {
// Center initially on Palmdale, California
map = L.map('radar-map', {
center: [34.5950, -118.1350],
zoom: 14,
zoomControl: false,
attributionControl: false
});
// High-Resolution Radar Dark Tiles (Zero API Key, Zero Watermarks, Sub-Meter Street Clarity up to Zoom 19)
darkTileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
subdomains: 'abc',
maxZoom: 19,
className: 'dark-radar-tile',
attribution: '© OpenStreetMap '
}).addTo(map);
// Prepare optional Satellite Layer (Zero API Key, Zero Watermarks)
satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
maxNativeZoom: 18,
maxZoom: 19,
attribution: '© Esri, Maxar, Earthstar Geographics'
});
// High-contrast street labels overlay for satellite view (Zero Watermarks)
satelliteLabelsLayer = L.tileLayer('https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', {
maxNativeZoom: 18,
maxZoom: 19
});
// Minimal zoom controls on bottom-left
L.control.zoom({ position: 'bottomleft' }).addTo(map);
// When the user drags the map, pause auto-centering and show Recenter button
map.on('dragstart', () => {
if (isFollowCarLocked) {
isFollowCarLocked = false;
updateFollowCarUI();
}
});
}
// --- LOAD CAMERA DATA (DEFAULTS + VERIFIED COMMUNITY + MUNICIPAL HUBS) ---
function loadAllCameraData() {
// Purge any unverified test marks or pins without photo proof
if (window.FlockAuth && typeof window.FlockAuth.clearUnverifiedReports === 'function') {
window.FlockAuth.clearUnverifiedReports();
}
// Filter out any unverified user pins or accidental test marks
allCameras = [...DEFAULT_CAMERAS].filter(c =>
!c.id.startsWith('usr-pin-') &&
c.notes !== '1-Tap Mobile Driver Pin' &&
c.name !== 'Community Report: Fixed ALPR Camera'
);
// Load crowdsourced reports from auth store (ONLY if photo verified!)
if (window.FlockAuth) {
const reports = window.FlockAuth.getReports();
reports.forEach(r => {
// Strict verification rule: Sighting MUST have verified photo proof!
if (!r || !r.photoUrl || r.id.startsWith('usr-pin-')) return;
allCameras.push({
id: r.id,
name: `Verified Sighting: ${r.type || 'Solar Pole Mount'}`,
lat: r.lat,
lng: r.lng,
radius: 280,
agency: 'Community Verified ALPR',
slug: '',
cameras: 1,
captures30d: 'Photo Verified Hardware',
isCommunity: true,
reportedBy: r.reportedBy,
notes: r.notes
});
});
}
// Load ALL 1,439 geocoded portals from national dataset as active radar zones
if (window.FLOCK_DATA && window.FLOCK_DATA.portals) {
window.FLOCK_DATA.portals.forEach(p => {
if (p.lat && p.lng && !allCameras.some(c => c.slug === p.slug)) {
const count = p.cameras || 4;
const radiusMeters = Math.min(600, Math.max(250, Math.round(Math.sqrt(count) * 45)));
allCameras.push({
id: 'portal-' + p.slug,
name: `${p.name} (${p.location}, ${p.state})`,
lat: p.lat,
lng: p.lng,
radius: radiusMeters,
agency: p.name,
slug: p.slug,
cameras: count,
captures30d: (p.vehicles_captured ? p.vehicles_captured.toLocaleString() : 'Active')
});
}
});
}
}
// --- RENDER RED RADAR CIRCLES & CAMERA NODES ---
function renderCameraZones() {
// Clear existing
cameraCircles.forEach(c => map.removeLayer(c));
cameraCircles = [];
allCameras.forEach(cam => {
const meta = window.FlockAuth ? window.FlockAuth.getCameraMeta(cam.id) : { confirmations: 1, hasPhoto: false, isContested: false };
const hasPhoto = meta.hasPhoto && !!meta.photoUrl;
// 1. Red Radar Recording Zone Circle (Translucent buffer)
const radarCircle = L.circle([cam.lat, cam.lng], {
radius: cam.radius,
color: meta.isContested ? '#f59e0b' : '#ef4444',
weight: 1.5,
opacity: 0.8,
fillColor: meta.isContested ? '#f59e0b' : '#ef4444',
fillOpacity: meta.isContested ? 0.12 : 0.18,
className: 'flock-radar-circle'
}).addTo(map);
// 2. Camera Center Node Marker
const iconHtml = `
`;
const customIcon = L.divIcon({
html: iconHtml,
className: 'custom-cam-div-icon',
iconSize: [28, 28],
iconAnchor: [14, 14]
});
const marker = L.marker([cam.lat, cam.lng], { icon: customIcon }).addTo(map);
// Popup details
const badgeText = meta.isContested
? 'π CONTESTED SIGHTING'
: (hasPhoto ? 'π’ PHOTO VERIFIED HARDWARE' : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
const popupHtml = `
`;
marker.bindPopup(popupHtml);
// Clicking marker directly opens the full hardware proof modal
marker.on('click', () => {
// Leaflet will open popup, but user can also click proof button directly
});
cameraCircles.push(radarCircle);
cameraCircles.push(marker);
});
}
// --- VEHICLE MARKER SETUP ---
function setupUserMarker() {
const vehicleHtml = `
`;
const vehicleIcon = L.divIcon({
html: vehicleHtml,
className: 'custom-vehicle-div-icon',
iconSize: [40, 40],
iconAnchor: [20, 20]
});
userMarker = L.marker([currentPosition.lat, currentPosition.lng], {
icon: vehicleIcon,
zIndexOffset: 1000
}).addTo(map);
}
// --- HUD SETUP ---
function setupHud() {
el.hudSpeedVal.textContent = '0';
el.hudDistVal.textContent = 'β';
el.hudZoneBadge.className = 'hud-badge badge-clear';
el.hudZoneBadge.innerHTML = ` ALL CLEAR`;
el.hudCamAgency.textContent = 'Monitoring Highway Network';
}
// --- PROXIMITY CALCULATION & ALERT ENGINE ---
function checkProximityToCameras() {
if (!allCameras.length) return;
let closestDistMeters = Infinity;
let closestCam = null;
allCameras.forEach(cam => {
const dist = getHaversineDistanceMeters(currentPosition.lat, currentPosition.lng, cam.lat, cam.lng);
if (dist < closestDistMeters) {
closestDistMeters = dist;
closestCam = cam;
}
});
const distFt = Math.round(closestDistMeters * 3.28084);
nearestCamDistanceFt = distFt;
// Check if inside zone radius
const insideZone = closestDistMeters <= (closestCam.radius || 300);
// Update HUD
if (distFt < 5280) {
el.hudDistVal.textContent = distFt > 1000 ? (distFt / 1000).toFixed(1) + 'k' : distFt;
el.hudDistSub.textContent = 'FT';
} else {
el.hudDistVal.textContent = (distFt / 5280).toFixed(1);
el.hudDistSub.textContent = 'MI';
}
if (closestCam) {
el.hudCamAgency.textContent = closestCam.name;
}
// Update Graduated Radar Signal Strength Meter (Valentine One Style)
updateRadarGauge(distFt, insideZone, closestCam);
// State Transitions
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
if (insideZone) {
if (!isInZone) {
// Just entered zone! Trigger Alert!
triggerZoneAlert(closestCam, distFt);
}
isInZone = true;
el.hudZoneBadge.className = 'hud-badge badge-danger';
el.hudZoneBadge.innerHTML = ` IN RECORDED ZONE`;
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
β οΈ
FLOCK CAMERA ACTIVE ZONE
${escapeHtml(closestCam.agency)} β’ Vehicle Profile Logging
`;
} else if (distFt <= cautionThreshold) {
// Approaching camera
isInZone = false;
el.hudZoneBadge.className = 'hud-badge badge-warning';
el.hudZoneBadge.innerHTML = ` CAMERA AHEAD`;
el.hudBannerAlert.classList.remove('visible');
} else {
// Clear
isInZone = false;
el.hudZoneBadge.className = 'hud-badge badge-clear';
el.hudZoneBadge.innerHTML = ` ALL CLEAR`;
el.hudBannerAlert.classList.remove('visible');
}
}
// --- 3-STAGE GRADUATED RADAR DETECTOR SIGNAL GAUGE ---
function updateRadarGauge(distFt, insideZone, closestCam) {
if (!el.hudGaugeState || !el.hudSignalMeter) return;
const isHighway = (radarRangeMode === 'highway');
const cautionDist = isHighway ? 2800 : 1500;
const warnDist = isHighway ? 1400 : 800;
const bars = el.hudSignalMeter.querySelectorAll('.signal-bar');
bars.forEach(b => {
b.className = 'signal-bar';
});
if (insideZone || distFt <= 450) {
// Level 5: Red Alert In Zone
el.hudGaugeState.className = 'hud-gauge-status status-red';
el.hudGaugeState.textContent = 'IN RECORDED ZONE!';
bars.forEach(b => b.classList.add('active-red'));
} else if (distFt <= warnDist) {
// Level 4: Immediate Proximity
el.hudGaugeState.className = 'hud-gauge-status status-red';
el.hudGaugeState.textContent = `ALERT: ${distFt} FT`;
bars[0].classList.add('active-red');
bars[1].classList.add('active-red');
bars[2].classList.add('active-red');
bars[3].classList.add('active-red');
} else if (distFt <= cautionDist) {
// Level 3: Approaching (Yellow Caution)
el.hudGaugeState.className = 'hud-gauge-status status-yellow';
el.hudGaugeState.textContent = `APPROACHING (${Math.round(distFt / 100) * 100} FT)`;
bars[0].classList.add('active-yellow');
bars[1].classList.add('active-yellow');
bars[2].classList.add('active-yellow');
// Pre-alert announcement once per camera approach
if (closestCam && lastCautionSpokenCamId !== closestCam.id) {
lastCautionSpokenCamId = closestCam.id;
playCautionBeep();
speakVoiceAlert(`Caution. Approaching surveillance camera in ${Math.round(distFt / 100) * 100} feet.`);
}
} else if (distFt <= (cautionDist * 1.6)) {
// Level 2: Grid Range
el.hudGaugeState.className = 'hud-gauge-status status-green';
el.hudGaugeState.textContent = 'MONITORING GRID';
bars[0].classList.add('active-green');
bars[1].classList.add('active-green');
if (closestCam && lastCautionSpokenCamId === closestCam.id && distFt > cautionDist + 500) {
lastCautionSpokenCamId = null; // Reset when moved away
}
} else {
// Level 1: All Clear
el.hudGaugeState.className = 'hud-gauge-status status-green';
el.hudGaugeState.textContent = 'ALL CLEAR';
bars[0].classList.add('active-green');
lastCautionSpokenCamId = null;
}
}
// --- AUDIO SYNTHESIZERS: CHIRPS & CAUTION BEEPS ---
function playLogConfirmationChirp() {
try {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
audioContext.resume();
}
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(587.33, audioContext.currentTime); // D5
osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.1); // A5
osc.frequency.setValueAtTime(1174.66, audioContext.currentTime + 0.2); // D6
gain.gain.setValueAtTime(0.35, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.4);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 0.4);
} catch (e) {
console.warn('Log chirp error:', e);
}
}
function playCautionBeep() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
try {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
audioContext.resume();
}
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(520, audioContext.currentTime);
gain.gain.setValueAtTime(0.25, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.22);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 0.22);
} catch (e) {
console.warn('Caution beep error:', e);
}
}
// --- 1-TAP DRIVER QUICK-LOG CAMERA (IMMEDIATELY OPENS CAMERA FOR PHOTO PROOF) ---
function quickLogFlockCameraAtCurrentLocation() {
photoUploadTarget = 'quicklog';
if (el.cameraPhotoFileInput) {
el.cameraPhotoFileInput.value = '';
el.cameraPhotoFileInput.click();
}
}
// --- AUTO-CENTER / FOLLOW CAR HANDLER ---
function toggleFollowCar() {
isFollowCarLocked = !isFollowCarLocked;
updateFollowCarUI();
if (isFollowCarLocked) {
map.setView([currentPosition.lat, currentPosition.lng], Math.max(map.getZoom(), 15), { animate: true });
}
}
function recenterOnCar() {
isFollowCarLocked = true;
updateFollowCarUI();
map.setView([currentPosition.lat, currentPosition.lng], Math.max(map.getZoom(), 15), { animate: true });
}
function updateFollowCarUI() {
if (el.btnFollowCar) {
el.btnFollowCar.classList.toggle('active', isFollowCarLocked);
el.btnFollowCar.innerHTML = isFollowCarLocked
? `π― Follow Car: ON `
: `π― Follow Car: OFF `;
}
if (el.btnRecenterCar) {
el.btnRecenterCar.classList.toggle('hidden', isFollowCarLocked);
}
}
// --- ALERT SENSITIVITY RANGE (CITY vs HIGHWAY) ---
function setRadarSensitivity(mode) {
radarRangeMode = mode;
if (el.btnSensCity) el.btnSensCity.classList.toggle('active', mode === 'city');
if (el.btnSensHwy) el.btnSensHwy.classList.toggle('active', mode === 'highway');
playCautionBeep();
showToast(mode === 'highway' ? 'π£οΈ Highway Range Active (2,500 ft advance alert)' : 'ποΈ City Range Active (1,000 ft alert)');
}
// --- DASHBOARD FULLSCREEN MOUNT MODE ---
function toggleFullscreenMount() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().then(() => {
if (el.btnFullscreenMount) el.btnFullscreenMount.classList.add('active');
showToast("π± Fullscreen Dashboard Mode Active");
}).catch(err => {
console.warn('Fullscreen request failed:', err);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
if (el.btnFullscreenMount) el.btnFullscreenMount.classList.remove('active');
}
}
}
// --- AUDIO SYNTHESIZER & SPOKEN VOICE ALERTS ---
function playRadarChime() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
const now = Date.now();
if (now - lastChimeTime < 3000) return;
lastChimeTime = now;
try {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
audioContext.resume();
}
// Synthesize clean two-tone radar alert
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, audioContext.currentTime); // High tone
osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.12); // Tone 2
osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.24); // Return tone
gain.gain.setValueAtTime(0.35, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.45);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 0.45);
} catch (e) {
console.warn('Audio chime error:', e);
}
}
function speakVoiceAlert(text) {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
if (!window.speechSynthesis) return;
try {
window.speechSynthesis.cancel();
const utter = new SpeechSynthesisUtterance(text);
utter.rate = 1.05;
utter.pitch = 1.0;
utter.volume = 1.0;
window.speechSynthesis.speak(utter);
} catch (e) {
console.warn('Speech error:', e);
}
}
function triggerZoneAlert(cam, distFt) {
playRadarChime();
speakVoiceAlert("Warning. Flock camera detected ahead. You are entering a recorded surveillance zone.");
}
// --- TEST ALERT SOUND & VOICE NOW ---
function testAlertSoundAndVoice() {
playRadarChime();
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
β οΈ
FLOCK CAMERA ACTIVE ZONE
Test Alert β’ Vehicle Profile Logging Active
`;
setTimeout(() => {
el.hudBannerAlert.classList.remove('visible');
}, 4500);
}
// --- PLACE TEST CAMERA 400FT AHEAD OF USER ---
function placeTestCameraAhead() {
// Offset approx 120m (400 ft) north-east of current location
const testCam = {
id: 'cam-test-' + Date.now(),
name: 'Test Flock Camera (400ft Ahead)',
lat: currentPosition.lat + 0.0011,
lng: currentPosition.lng + 0.0003,
radius: 260,
agency: 'Local Test Camera Deployment',
slug: '',
cameras: 2,
captures30d: 'Active Demonstration Zone',
isCommunity: true,
notes: 'Manually dropped test camera to verify real-time driving alerts.'
};
allCameras.push(testCam);
renderCameraZones();
map.flyTo([testCam.lat, testCam.lng], 16, { duration: 1 });
playRadarChime();
speakVoiceAlert('Test camera placed 400 feet ahead. Move forward into the red circle to test the alert.');
alert('π Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.');
}
// --- LIVE GPS CONTINUOUS TRACKING ---
let liveGpsWatchId = null;
function toggleLiveGps() {
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser.');
return;
}
if (liveGpsWatchId !== null) {
navigator.geolocation.clearWatch(liveGpsWatchId);
liveGpsWatchId = null;
el.btnLiveGps.classList.remove('active');
el.btnLiveGps.innerHTML = `π― Live GPS Drive Mode `;
el.hudSpeedVal.textContent = '0';
return;
}
if (isSimulating) stopDriveSimulation();
el.btnLiveGps.classList.add('active');
el.btnLiveGps.innerHTML = `π Stop Live GPS `;
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
liveGpsWatchId = navigator.geolocation.watchPosition(
pos => {
currentPosition = {
lat: pos.coords.latitude,
lng: pos.coords.longitude
};
// Speed in mph
if (pos.coords.speed !== null && !isNaN(pos.coords.speed)) {
currentSpeed = Math.round(pos.coords.speed * 2.23694);
} else {
currentSpeed = 0;
}
el.hudSpeedVal.textContent = currentSpeed;
// Heading
if (pos.coords.heading !== null && !isNaN(pos.coords.heading)) {
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) carElem.style.transform = `rotate(${pos.coords.heading}deg)`;
}
if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]);
if (isFollowCarLocked) {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.8 });
if (currentSpeed > 15 && map.getZoom() < 16) {
map.setZoom(16);
}
}
checkProximityToCameras();
},
err => {
console.warn('GPS Error:', err);
alert('GPS Location Error: ' + err.message + '\nPlease allow location access in your browser.');
},
{
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 10000
}
);
}
// --- MOBILE PHONE CONNECT INSTRUCTIONS ---
function showMobileConnectModal() {
const httpUrl = 'http://192.168.1.249:8085';
const httpsUrl = 'https://192.168.1.249:8443';
alert(
"π± HOW TO USE FLOCKRADAR ON YOUR PHONE IN ANTELOPE VALLEY:\n\n" +
"1. Connect your phone to your Wi-Fi or hotspot.\n\n" +
"2. FOR CONTINUOUS DRIVING GPS (iOS Safari & Android Chrome):\n" +
" Open: " + httpsUrl + "\n" +
" (Tap 'Show Details' / 'Advanced' -> 'Visit this website' to allow local SSL, then allow location access!)\n\n" +
"3. FOR INSTANT OVERVIEW: Open " + httpUrl + "\n\n" +
"4. Tap 'Auto-Detect (GPS)' and your device will track and alert you across all Antelope Valley Flock cameras!"
);
}
// --- DRIVE SIMULATOR ENGINE ---
function toggleDriveSimulation() {
if (isSimulating) {
stopDriveSimulation();
} else {
startDriveSimulation();
}
}
function startDriveSimulation() {
isSimulating = true;
el.btnSimDrive.innerHTML = `βΈ Pause Simulation`;
el.btnSimDrive.classList.add('active');
simIndex = 0;
simProgress = 0;
currentPosition = { ...SIM_ROUTE[0] };
map.flyTo([currentPosition.lat, currentPosition.lng], 15, { animate: true, duration: 1 });
stepSimulation();
}
function stopDriveSimulation() {
isSimulating = false;
el.btnSimDrive.innerHTML = `βΆ Simulate Drive`;
el.btnSimDrive.classList.remove('active');
if (simAnimId) cancelAnimationFrame(simAnimId);
}
function resetDriveSimulation() {
stopDriveSimulation();
simIndex = 0;
simProgress = 0;
currentPosition = { ...SIM_ROUTE[0] };
currentSpeed = 0;
el.hudSpeedVal.textContent = '0';
if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]);
map.panTo([currentPosition.lat, currentPosition.lng]);
checkProximityToCameras();
}
function stepSimulation() {
if (!isSimulating) return;
const p1 = SIM_ROUTE[simIndex];
const p2 = SIM_ROUTE[simIndex + 1];
if (!p2) {
// Reached end of demo route, loop smoothly
simIndex = 0;
simProgress = 0;
simAnimId = requestAnimationFrame(stepSimulation);
return;
}
// Step rate
simProgress += 0.004;
if (simProgress >= 1) {
simProgress = 0;
simIndex++;
} else {
// Interpolate lat / lng
currentPosition.lat = p1.lat + (p2.lat - p1.lat) * simProgress;
currentPosition.lng = p1.lng + (p2.lng - p1.lng) * simProgress;
currentSpeed = Math.round(p1.speed + (p2.speed - p1.speed) * simProgress);
// Update marker position
if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]);
// Rotate car icon based on heading angle
const angle = getBearing(p1.lat, p1.lng, p2.lat, p2.lng);
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) {
carElem.style.transform = `rotate(${angle}deg)`;
}
// Smoothly pan map along if follow car is locked
if (isFollowCarLocked) {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: false });
}
// Update Speedometer
el.hudSpeedVal.textContent = currentSpeed;
}
simAnimId = requestAnimationFrame(stepSimulation);
}
// --- HAVERSINE FORMULA ---
function getHaversineDistanceMeters(lat1, lon1, lat2, lon2) {
const R = 6371e3; // Earth radius in meters
const phi1 = lat1 * Math.PI / 180;
const phi2 = lat2 * Math.PI / 180;
const deltaPhi = (lat2 - lat1) * Math.PI / 180;
const deltaLambda = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +
Math.cos(phi1) * Math.cos(phi2) *
Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function getBearing(startLat, startLng, destLat, destLng) {
const startLatRad = startLat * Math.PI / 180;
const startLngRad = startLng * Math.PI / 180;
const destLatRad = destLat * Math.PI / 180;
const destLngRad = destLng * Math.PI / 180;
const y = Math.sin(destLngRad - startLngRad) * Math.cos(destLatRad);
const x = Math.cos(startLatRad) * Math.sin(destLatRad) -
Math.sin(startLatRad) * Math.cos(destLatRad) * Math.cos(destLngRad - startLngRad);
let brng = Math.atan2(y, x) * 180 / Math.PI;
return (brng + 360) % 360;
}
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
async function attemptAutoLocate() {
updateSpottedBadge('π°οΈ Spotting Antelope Valley Location...');
// Tier 1: Instant IP-based Network Geolocation
// Works 100% reliably even when browser GPS is blocked, disabled, or accessed over HTTP
let ipFound = false;
try {
const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' });
const ipData = await resp.json();
if (ipData && ipData.latitude && ipData.longitude) {
const ipLat = parseFloat(ipData.latitude);
const ipLng = parseFloat(ipData.longitude);
const city = ipData.city || 'Palmdale';
const region = ipData.region || 'California';
console.log('IP Network Geolocation spotted:', city, region, ipLat, ipLng);
// Center directly on Antelope Valley (Palmdale / Lancaster corridor)
let targetLat = ipLat;
let targetLng = ipLng;
const isAntelopeValley = city.toLowerCase().includes('palmdale') ||
city.toLowerCase().includes('lancaster') ||
(ipLat > 34.2 && ipLat < 34.85 && ipLng > -118.45 && ipLng < -117.8);
if (isAntelopeValley) {
// Snap directly to Palmdale central corridor (Palmdale Blvd & 10th St W)
targetLat = 34.60194;
targetLng = -118.14847;
}
updateUserCoordinates(targetLat, targetLng, 0, 0);
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 });
updateSpottedBadge(`π Spotted: ${city}, Antelope Valley (Network)`);
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`);
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
checkProximityToCameras();
ipFound = true;
}
} catch (e) {
console.warn('Primary geojs IP lookup failed, trying backup:', e);
try {
const resp2 = await fetch('https://ipwho.is/', { cache: 'no-cache' });
const ipData2 = await resp2.json();
if (ipData2 && ipData2.success && ipData2.latitude) {
updateUserCoordinates(34.60194, -118.14847, 0, 0);
map.flyTo([34.60194, -118.14847], 14, { duration: 1.2 });
updateSpottedBadge(`π Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
checkProximityToCameras();
ipFound = true;
}
} catch (err2) {
console.warn('Backup IP lookup failed:', err2);
}
}
if (!ipFound) {
updateSpottedBadge('π Antelope Valley: Palmdale, CA (Default)');
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
checkProximityToCameras();
}
// Tier 2: Hardware Device GPS (Sub-meter accuracy if granted)
if (navigator.geolocation && (window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
navigator.geolocation.getCurrentPosition(
pos => {
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
const acc = Math.round(pos.coords.accuracy || 10);
console.log('Hardware GPS lock acquired:', lat, lng, 'Β±' + acc + 'm');
updateUserCoordinates(lat, lng, pos.coords.speed, pos.coords.heading);
map.flyTo([lat, lng], 15, { duration: 1.2 });
updateSpottedBadge(`π― High-Precision GPS (Β±${acc}m) β’ Antelope Valley`);
updateHudLocationStatus('π Live Driving GPS Active');
checkProximityToCameras();
},
gpsErr => {
console.log('Hardware GPS not granted/pending:', gpsErr.message);
// Retain the confirmed Antelope Valley IP location smoothly
},
{ enableHighAccuracy: true, timeout: 6000, maximumAge: 30000 }
);
}
}
function updateSpottedBadge(text) {
if (el.hudSpottedText) {
el.hudSpottedText.textContent = text;
}
}
function updateUserCoordinates(lat, lng, speed, heading) {
currentPosition.lat = lat;
currentPosition.lng = lng;
if (speed !== null && !isNaN(speed)) {
currentSpeed = Math.round(speed * 2.23694);
} else {
currentSpeed = 0;
}
el.hudSpeedVal.textContent = currentSpeed;
if (userMarker) {
userMarker.setLatLng([lat, lng]);
}
if (heading !== null && !isNaN(heading)) {
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) carElem.style.transform = `rotate(${heading}deg)`;
}
}
function updateHudLocationStatus(statusText) {
if (el.hudCamAgency) {
el.hudCamAgency.textContent = statusText;
}
}
async function useLiveGps() {
if (isSimulating) stopDriveSimulation();
updateSpottedBadge('π°οΈ Re-detecting location in Antelope Valley...');
updateHudLocationStatus('Acquiring high-precision location...');
// 1. First ensure IP location is refreshed
try {
const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' });
const ipData = await resp.json();
if (ipData && ipData.latitude) {
const city = ipData.city || 'Palmdale';
jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`);
updateSpottedBadge(`π Spotted: ${city}, Antelope Valley`);
}
} catch (e) {}
// 2. Hardware GPS Attempt
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
pos => {
updateUserCoordinates(pos.coords.latitude, pos.coords.longitude, pos.coords.speed, pos.coords.heading);
map.flyTo([pos.coords.latitude, pos.coords.longitude], 16, { duration: 1.2 });
const acc = Math.round(pos.coords.accuracy || 10);
updateSpottedBadge(`π― Live GPS Active (Β±${acc}m)`);
updateHudLocationStatus('π High-Precision GPS Locked');
playRadarChime();
speakVoiceAlert('GPS position locked in Antelope Valley.');
checkProximityToCameras();
},
err => {
console.warn('GPS hardware notice:', err.message);
// Show non-blocking informative banner
if (err.code === 1) { // PERMISSION_DENIED
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
βΉοΈ
ANTELOPE VALLEY NETWORK LOCATION ACTIVE
To enable turn-by-turn driving GPS, click the lock icon π in your browser address bar and select 'Allow Location'.
`;
setTimeout(() => {
el.hudBannerAlert.classList.remove('visible');
}, 6000);
}
},
{ enableHighAccuracy: true, timeout: 8000 }
);
}
}
// --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) ---
function jumpToLocation(lat, lng, label, zoom = 15) {
if (isSimulating) stopDriveSimulation();
updateUserCoordinates(lat, lng, 0, 0);
map.flyTo([lat, lng], zoom, { duration: 1.2 });
updateHudLocationStatus(`π ${label}`);
updateSpottedBadge(`π Set to: ${label}`);
checkProximityToCameras();
}
// --- FREE NOMINATIM ADDRESS SEARCH (100% UNMETERED $0.00) ---
async function searchLocation(query) {
if (!query) return;
try {
updateHudLocationStatus(`Searching "${query}"...`);
const resp = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(query)}`);
const data = await resp.json();
if (data && data.length > 0) {
const item = data[0];
const lat = parseFloat(item.lat);
const lng = parseFloat(item.lon);
const shortName = item.display_name.split(',').slice(0, 2).join(',');
jumpToLocation(lat, lng, shortName);
speakVoiceAlert(`Navigated to ${shortName}`);
} else {
alert('Location not found. Please try a street or city name (e.g. Palmdale Blvd, Ave S, Lancaster).');
}
} catch (e) {
console.warn('Search error:', e);
alert('Could not complete address search. Please check network connection.');
}
}
// --- MAP LAYER TOGGLE (DARK CANVAS vs SATELLITE HYBRID) ---
function toggleMapLayer() {
if (!map) return;
isSatelliteActive = !isSatelliteActive;
if (isSatelliteActive) {
if (darkTileLayer && map.hasLayer(darkTileLayer)) map.removeLayer(darkTileLayer);
if (satelliteLayer) satelliteLayer.addTo(map);
if (satelliteLabelsLayer) satelliteLabelsLayer.addTo(map);
el.btnMapLayer.innerHTML = `πΊοΈ Dark Canvas `;
el.btnMapLayer.classList.add('active');
} else {
if (satelliteLayer && map.hasLayer(satelliteLayer)) map.removeLayer(satelliteLayer);
if (satelliteLabelsLayer && map.hasLayer(satelliteLabelsLayer)) map.removeLayer(satelliteLabelsLayer);
if (darkTileLayer) darkTileLayer.addTo(map);
el.btnMapLayer.innerHTML = `π°οΈ Satellite / Dark `;
el.btnMapLayer.classList.remove('active');
}
}
// --- USER PROFILE & LOGIN MODAL ---
function updateProfileDisplay() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && user.isLoggedIn) {
el.userProfilePill.innerHTML = `
π
${escapeHtml(user.username)}
${user.points} pts
`;
} else {
el.userProfilePill.innerHTML = `
π€
Driver Login
`;
}
}
function openLoginModal() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && user.isLoggedIn) {
// Pre-fill
document.getElementById('login-username').value = user.username;
document.getElementById('login-vehicle').value = user.vehicle;
}
renderAdminPlacedList();
if (typeof el.loginModal.showModal === 'function') {
el.loginModal.showModal();
} else {
el.loginModal.setAttribute('open', '');
}
}
function closeLoginModal() {
if (typeof el.loginModal.close === 'function') {
el.loginModal.close();
} else {
el.loginModal.removeAttribute('open');
}
}
// --- CROWDSOURCED REPORT MODAL ---
function openReportModal() {
// Fill current coordinates
document.getElementById('report-lat').value = currentPosition.lat.toFixed(5);
document.getElementById('report-lng').value = currentPosition.lng.toFixed(5);
if (typeof el.reportModal.showModal === 'function') {
el.reportModal.showModal();
} else {
el.reportModal.setAttribute('open', '');
}
}
function closeReportModal() {
if (typeof el.reportModal.close === 'function') {
el.reportModal.close();
} else {
el.reportModal.removeAttribute('open');
}
}
// --- SURVEILLANCE ZONE HARDWARE PROOF MODAL ---
function openCameraDetailModal(cam) {
if (!cam) return;
activeModalCamera = cam;
const meta = window.FlockAuth ? window.FlockAuth.getCameraMeta(cam.id) : { confirmations: 1, hasPhoto: false, isContested: false };
const hasPhoto = meta.hasPhoto && !!meta.photoUrl;
// Title & Agency
if (el.camDetailTitle) el.camDetailTitle.textContent = cam.name || 'Flock ALPR Surveillance Installation';
if (el.camDetailMeta) el.camDetailMeta.textContent = `${cam.agency || 'Law Enforcement'} β’ Monitored Highway Grid`;
// Verification Badge
if (el.camDetailVerifyBadge && el.camDetailVerifyText) {
if (meta.isContested) {
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-contested';
el.camDetailVerifyText.textContent = `π CONTESTED SIGHTING (${meta.contests ? meta.contests.length : 1} REPORTS)`;
} else if (hasPhoto) {
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified';
el.camDetailVerifyText.textContent = `π’ VERIFIED PHOTO PROOF β’ ${meta.confirmations || 1} CONFIRMATIONS`;
} else {
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified';
el.camDetailVerifyText.textContent = `π‘ UNVERIFIED PIN β’ NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)`;
}
}
// Photo Container
if (el.camProofContainer) {
if (hasPhoto) {
el.camProofContainer.innerHTML = `
πΈ Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}
Logged: ${new Date(meta.photoDate || Date.now()).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
VERIFIED HARDWARE
`;
if (el.btnProofPhotoText) el.btnProofPhotoText.textContent = 'Update Photo';
} else {
el.camProofContainer.innerHTML = `
πΈ
No Physical Hardware Photo Yet
Flock cameras stay permanently bolted to utility poles and traffic signals with small solar panels. Snap a photo of the pole to lock this camera into the permanent verified registry.
πΈ
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.textContent = `π Confirmed (${meta.confirmations || 1})`;
} else {
el.btnVoteConfirmCam.classList.remove('voted');
el.btnVoteConfirmText.textContent = `Confirm Active (${meta.confirmations || 1})`;
}
}
// Navigate link
if (el.btnProofNavigate) {
el.btnProofNavigate.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
}
// Transparency Portal Link
const portalBox = document.getElementById('proof-portal-link-box');
if (portalBox) {
if (cam.slug) {
portalBox.style.display = 'block';
if (el.btnProofOpenPortal) {
el.btnProofOpenPortal.onclick = () => {
closeCameraDetailModal();
window.inspectPortal(cam.slug);
};
}
} else {
portalBox.style.display = 'none';
}
}
// Admin Delete Control: ONLY visible for custom placed pins, NEVER for official scraped municipal portals
const isPlaced = cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-');
if (el.camAdminControlBox) {
el.camAdminControlBox.style.display = isPlaced ? 'block' : 'none';
}
// Show modal
if (el.cameraDetailModal) {
if (typeof el.cameraDetailModal.showModal === 'function') {
el.cameraDetailModal.showModal();
} else {
el.cameraDetailModal.setAttribute('open', '');
}
}
}
function closeCameraDetailModal() {
if (el.cameraDetailModal) {
if (typeof el.cameraDetailModal.close === 'function') {
el.cameraDetailModal.close();
} else {
el.cameraDetailModal.removeAttribute('open');
}
}
}
// --- ADMIN PLACED CAMERA MANAGEMENT ---
function renderAdminPlacedList() {
if (!el.adminPlacedCamsList) return;
const reports = window.FlockAuth ? window.FlockAuth.getReports() : [];
// Find all placed cameras from active radar memory (including test cameras)
const placedCams = allCameras.filter(c =>
c.isCommunity ||
c.id.startsWith('rpt-') ||
c.id.startsWith('usr-pin-') ||
c.id.startsWith('cam-test-') ||
c.id.startsWith('test-')
);
// Combine into unique map
const combinedMap = new Map();
reports.forEach(r => {
combinedMap.set(r.id, {
id: r.id,
name: r.intersection || r.type || 'Placed Camera',
lat: r.lat,
lng: r.lng,
dateStr: new Date(r.timestamp || Date.now()).toLocaleDateString(),
photoUrl: r.photoUrl || null
});
});
placedCams.forEach(c => {
if (!combinedMap.has(c.id)) {
combinedMap.set(c.id, {
id: c.id,
name: c.name || 'Test Placed Camera',
lat: c.lat,
lng: c.lng,
dateStr: 'Active Session',
photoUrl: null
});
}
});
const items = Array.from(combinedMap.values());
if (el.adminPlacedCount) el.adminPlacedCount.textContent = `${items.length} Placed`;
if (items.length === 0) {
el.adminPlacedCamsList.innerHTML = `
No custom placed cameras. All active radar cameras are official municipal portals.
`;
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none';
return;
}
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block';
el.adminPlacedCamsList.innerHTML = items.map(r => `
${r.photoUrl ? `
` : `
π
`}
${escapeHtml(r.name)}
${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} β’ ${r.dateStr}
ποΈ 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)');
});
}
// Address & City Search
if (el.mapSearchForm) {
el.mapSearchForm.addEventListener('submit', e => {
e.preventDefault();
const query = el.mapSearchInput.value.trim();
if (query) searchLocation(query);
});
}
el.btnTestCamera.addEventListener('click', placeTestCameraAhead);
el.btnTestAlert.addEventListener('click', testAlertSoundAndVoice);
el.btnSimDrive.addEventListener('click', toggleDriveSimulation);
el.btnResetSim.addEventListener('click', resetDriveSimulation);
el.btnMobileConnect.addEventListener('click', showMobileConnectModal);
// New 5% Driving Enhancement Listeners
if (el.btnFollowCar) el.btnFollowCar.addEventListener('click', toggleFollowCar);
if (el.btnRecenterCar) el.btnRecenterCar.addEventListener('click', recenterOnCar);
if (el.btnQuickLogCam) el.btnQuickLogCam.addEventListener('click', quickLogFlockCameraAtCurrentLocation);
if (el.btnFullscreenMount) el.btnFullscreenMount.addEventListener('click', toggleFullscreenMount);
if (el.btnSensCity) el.btnSensCity.addEventListener('click', () => setRadarSensitivity('city'));
if (el.btnSensHwy) el.btnSensHwy.addEventListener('click', () => setRadarSensitivity('highway'));
// Camera Hardware Proof & Voting Events
if (el.camDetailCloseBtn) el.camDetailCloseBtn.addEventListener('click', closeCameraDetailModal);
if (el.btnVoteConfirmCam) {
el.btnVoteConfirmCam.addEventListener('click', () => {
if (!activeModalCamera) return;
const res = window.FlockAuth.confirmCamera(activeModalCamera.id);
if (res.success) {
playLogConfirmationChirp();
speakVoiceAlert("Camera confirmed active on pole. +25 Scout Points!");
showToast(`π Confirmed active on pole! Total: ${res.count} confirmations.`);
openCameraDetailModal(activeModalCamera);
renderCameraZones();
updateProfileDisplay();
} else {
showToast(res.message || "Already confirmed!");
}
});
}
if (el.btnProofAddPhoto) {
el.btnProofAddPhoto.addEventListener('click', () => {
window.triggerPhotoCaptureForActiveCam();
});
}
if (el.btnProofContest) {
el.btnProofContest.addEventListener('click', () => {
if (!activeModalCamera) return;
closeCameraDetailModal();
openContestModal(activeModalCamera);
});
}
// Admin Delete Camera Button (Detail Modal)
if (el.btnAdminDeleteCam) {
el.btnAdminDeleteCam.addEventListener('click', () => {
if (!activeModalCamera) return;
window.adminDeleteCamera(activeModalCamera.id);
});
}
// Contest Modal Events
if (el.contestCloseBtn) el.contestCloseBtn.addEventListener('click', closeContestModal);
if (el.btnContestAddPhoto) el.btnContestAddPhoto.addEventListener('click', triggerPhotoCaptureForContest);
if (el.btnContestRemovePhoto) {
el.btnContestRemovePhoto.addEventListener('click', () => {
contestPhotoPendingDataUrl = null;
if (el.contestPhotoPreviewBox) el.contestPhotoPreviewBox.classList.add('hidden');
if (el.contestPhotoBtnText) el.contestPhotoBtnText.textContent = 'Snap Empty Pole';
});
}
if (el.contestForm) {
el.contestForm.addEventListener('submit', e => {
e.preventDefault();
if (!activeModalCamera) return;
const reason = document.getElementById('contest-reason').value;
const notes = document.getElementById('contest-notes').value.trim();
const res = window.FlockAuth.contestCamera(activeModalCamera.id, reason, notes);
closeContestModal();
if (res.success) {
if (res.isContested) {
speakVoiceAlert("Camera flagged as contested by community consensus.");
showToast("β οΈ Location marked Contested by community consensus.");
} else {
showToast(`β οΈ Contest submitted (${res.contestCount}/3 flags for consensus review).`);
}
renderCameraZones();
} else {
showToast(res.message || "Already reported!");
}
});
}
// Report Form Photo Proof Events
if (el.btnReportAddPhoto) el.btnReportAddPhoto.addEventListener('click', triggerPhotoCaptureForReport);
if (el.btnReportRemovePhoto) {
el.btnReportRemovePhoto.addEventListener('click', () => {
reportPhotoPendingDataUrl = null;
if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.add('hidden');
if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Snap Camera on Pole (+100 pts)';
});
}
// Native Camera / File Input Change Event
if (el.cameraPhotoFileInput) {
el.cameraPhotoFileInput.addEventListener('change', handlePhotoFileSelected);
}
// Nearest Camera HUD tap opens details
const hudLocationBar = document.querySelector('.hud-location-bar');
if (hudLocationBar) {
hudLocationBar.style.cursor = 'pointer';
hudLocationBar.title = 'Click to inspect nearest camera hardware proof & vote';
hudLocationBar.addEventListener('click', () => {
if (allCameras.length > 0) {
let closest = null;
let minDist = Infinity;
allCameras.forEach(cam => {
const d = getHaversineDistanceMeters(currentPosition.lat, currentPosition.lng, cam.lat, cam.lng);
if (d < minDist) { minDist = d; closest = cam; }
});
if (closest) openCameraDetailModal(closest);
}
});
}
// Mute toggle
el.btnMuteSound.addEventListener('click', () => {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user) {
user.soundEnabled = !user.soundEnabled;
window.FlockAuth.saveUser();
el.btnMuteSound.innerHTML = user.soundEnabled ? 'π Sound: ON' : 'π Sound: OFF';
el.btnMuteSound.classList.toggle('active', !user.soundEnabled);
}
});
// Report Camera FAB
if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal);
// Profile button click
el.driverProfileBtn.addEventListener('click', openLoginModal);
// Login Form Submit
el.loginForm.addEventListener('submit', e => {
e.preventDefault();
const username = document.getElementById('login-username').value.trim();
const vehicle = document.getElementById('login-vehicle').value.trim();
const soundEnabled = document.getElementById('login-sound').checked;
if (window.FlockAuth) {
window.FlockAuth.login(username, vehicle, soundEnabled);
updateProfileDisplay();
}
closeLoginModal();
});
// Login as Guest button
el.loginGuestBtn.addEventListener('click', () => {
if (window.FlockAuth) {
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
updateProfileDisplay();
}
closeLoginModal();
});
// Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals)
if (el.btnAdminClearAllPlaced) {
el.btnAdminClearAllPlaced.addEventListener('click', () => {
if (!confirm("Admin Action: Clear ALL custom placed pins and radar test markers? (The 1,439 official municipal portals will remain permanent and protected).")) {
return;
}
if (window.FlockAuth) {
window.FlockAuth.clearAllPlacedReports();
}
allCameras = allCameras.filter(c => !c.isCommunity && !c.id.startsWith('rpt-') && !c.id.startsWith('usr-pin-') && !c.id.startsWith('test-') && !c.id.startsWith('cam-test-'));
renderCameraZones();
renderAdminPlacedList();
playLogConfirmationChirp();
speakVoiceAlert("All custom placed pins cleared from radar.");
showToast("ποΈ All custom placed pins cleared.");
});
}
// Admin Placed Pins Shortcut in Map Top Controls
if (el.btnAdminManagePins) {
el.btnAdminManagePins.addEventListener('click', () => {
openLoginModal();
setTimeout(() => {
const adminSec = document.querySelector('.admin-profile-section');
if (adminSec) adminSec.scrollIntoView({ behavior: 'smooth' });
}, 120);
});
}
// Sync radar when reports updated
window.addEventListener('flock_reports_updated', () => {
loadAllCameraData();
renderCameraZones();
renderAdminPlacedList();
});
// Report Form Submit
el.reportForm.addEventListener('submit', e => {
e.preventDefault();
const type = document.getElementById('report-mount-type').value;
const intersection = document.getElementById('report-intersection').value.trim();
const notes = document.getElementById('report-notes').value.trim();
const lat = parseFloat(document.getElementById('report-lat').value);
const lng = parseFloat(document.getElementById('report-lng').value);
if (window.FlockAuth) {
const newRpt = window.FlockAuth.addReport({
lat,
lng,
type,
intersection,
notes,
photoUrl: reportPhotoPendingDataUrl
});
reportPhotoPendingDataUrl = null;
if (el.reportPhotoPreviewBox) el.reportPhotoPreviewBox.classList.add('hidden');
if (el.reportPhotoBtnText) el.reportPhotoBtnText.textContent = 'Snap Camera on Pole (+100 pts)';
loadAllCameraData();
renderCameraZones();
updateProfileDisplay();
playLogConfirmationChirp();
speakVoiceAlert("Camera sighting broadcast to radar network!");
showToast(`Camera report added! +${newRpt.photoUrl ? '150' : '50'} Scout Points.`);
}
closeReportModal();
});
// Modal Close buttons
el.loginCloseBtn.addEventListener('click', closeLoginModal);
el.reportCloseBtn.addEventListener('click', closeReportModal);
// Global listener for portal inspection from popup
window.inspectPortal = function(slug) {
switchView('dir');
setTimeout(() => {
window.location.hash = slug;
}, 150);
};
}
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// --- PWA SERVICE WORKER & MOBILE WAKE LOCK ---
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('./sw.js').catch(err => {
console.log('Service Worker registration skipped/failed:', err);
});
});
}
// Screen Wake Lock to prevent phone screen from going to sleep while driving
let wakeLock = null;
async function requestWakeLock() {
if ('wakeLock' in navigator) {
try {
wakeLock = await navigator.wakeLock.request('screen');
} catch (err) {
console.warn('Wake Lock error:', err);
}
}
}
// Auto-unlock Web Audio and Wake Lock on first touch (critical for mobile iOS/Android)
function unlockMobileFeatures() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext && audioContext.state === 'suspended') {
audioContext.resume();
}
requestWakeLock();
document.removeEventListener('touchstart', unlockMobileFeatures);
document.removeEventListener('click', unlockMobileFeatures);
}
document.addEventListener('touchstart', unlockMobileFeatures, { passive: true });
document.addEventListener('click', unlockMobileFeatures, { passive: true });
// --- START ENGINE ON DOM READY ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();