Files
FlockRadar/js/radar.js
T

1998 lines
82 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).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: '&copy; 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 = `
<div class="camera-map-pin ${cam.isCommunity ? 'community-pin' : ''} ${hasPhoto ? 'has-photo-pin' : ''} ${meta.isContested ? 'is-contested-pin' : ''}">
<div class="cam-pulse-wave"></div>
<div class="cam-dot-center">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2.5">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
<circle cx="12" cy="13" r="4"></circle>
</svg>
</div>
${hasPhoto ? '<div class="pin-photo-dot" title="Photo Evidence Verified">📸</div>' : ''}
</div>
`;
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 = `
<div class="radar-popup-card">
<div class="popup-badge ${hasPhoto ? 'badge-verified-photo' : (meta.isContested ? 'badge-contested-tag' : '')}">${badgeText}</div>
<h4 class="popup-title">${escapeHtml(cam.name)}</h4>
<div class="popup-meta">
<span><strong>Agency:</strong> ${escapeHtml(cam.agency)}</span><br>
<span><strong>Consensus:</strong> 👍 ${meta.confirmations || 1} Confirmations</span><br>
<span><strong>Photo Proof:</strong> ${hasPhoto ? '🟢 Verified on Pole' : '⚠️ No Photo Yet (Needs Driver Sighting)'}</span>
${cam.notes ? `<br><span><em>${escapeHtml(cam.notes)}</em></span>` : ''}
</div>
<div style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 600;" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
📸 View Hardware Proof & Vote ↗
</button>
${cam.slug ? `
<button class="popup-inspect-btn" onclick="window.inspectPortal('${escapeHtml(cam.slug)}')">
Inspect Agency Records & Sharing ↗
</button>
` : ''}
${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? `
<button class="popup-inspect-btn popup-delete-btn" style="background: #dc2626 !important; color: #ffffff !important; font-weight: 800 !important; font-size: 0.88rem !important; padding: 10px 14px !important; border-radius: 6px !important; margin-top: 6px !important; display: flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; border: none !important; cursor: pointer !important; box-shadow: 0 3px 10px rgba(220, 38, 38, 0.45) !important;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
<span style="font-size: 1rem;">🗑️</span>
<span>REMOVE THIS PIN (ADMIN)</span>
</button>
` : ''}
</div>
</div>
`;
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 = `
<div class="user-vehicle-marker" id="user-vehicle-marker">
<div class="radar-scan-cone"></div>
<div class="car-body-dot">
<div class="car-arrow"></div>
</div>
</div>
`;
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 = `<span class="badge-dot dot-clear"></span> 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 = `<span class="badge-dot dot-danger"></span> IN RECORDED ZONE`;
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
<span class="banner-pulse-icon">⚠️</span>
<div>
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
<p>${escapeHtml(closestCam.agency)} • Vehicle Profile Logging</p>
</div>
`;
} else if (distFt <= cautionThreshold) {
// Approaching camera
isInZone = false;
el.hudZoneBadge.className = 'hud-badge badge-warning';
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-warning"></span> CAMERA AHEAD`;
el.hudBannerAlert.classList.remove('visible');
} else {
// Clear
isInZone = false;
el.hudZoneBadge.className = 'hud-badge badge-clear';
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-clear"></span> 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
? `<span>🎯</span><span>Follow Car: ON</span>`
: `<span>🎯</span><span>Follow Car: OFF</span>`;
}
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 = `
<span class="banner-pulse-icon">⚠️</span>
<div>
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
<p>Test Alert • Vehicle Profile Logging Active</p>
</div>
`;
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 = `<span>🎯</span><span>Live GPS Drive Mode</span>`;
el.hudSpeedVal.textContent = '0';
return;
}
if (isSimulating) stopDriveSimulation();
el.btnLiveGps.classList.add('active');
el.btnLiveGps.innerHTML = `<span>🛑</span><span>Stop Live GPS</span>`;
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 = `<span>⏸</span> 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 = `<span>▶</span> 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 = `
<span class="banner-pulse-icon">️</span>
<div>
<strong>ANTELOPE VALLEY NETWORK LOCATION ACTIVE</strong>
<p>To enable turn-by-turn driving GPS, click the lock icon 🔒 in your browser address bar and select 'Allow Location'.</p>
</div>
`;
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 = `<span>🗺️</span><span>Dark Canvas</span>`;
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 = `<span>🛰️</span><span>Satellite / Dark</span>`;
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 = `
<span class="avatar-dot">🚘</span>
<span>${escapeHtml(user.username)}</span>
<span class="points-badge">${user.points} pts</span>
`;
} else {
el.userProfilePill.innerHTML = `
<span class="avatar-dot">👤</span>
<span>Driver Login</span>
`;
}
}
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 = `
<div class="proof-photo-box">
<img src="${meta.photoUrl}" class="proof-photo-img" alt="Flock camera mounted on pole" onclick="window.open('${meta.photoUrl}', '_blank')" title="Click to view full size">
<div class="proof-photo-overlay">
<div>
<span class="proof-photo-author">📸 Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}</span><br>
<span class="proof-photo-date">Logged: ${new Date(meta.photoDate || Date.now()).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
</div>
<span style="background: rgba(16,185,129,0.25); border: 1px solid #10b981; padding: 3px 8px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; color: #34d399;">VERIFIED HARDWARE</span>
</div>
</div>
`;
if (el.btnProofPhotoText) el.btnProofPhotoText.textContent = 'Update Photo';
} else {
el.camProofContainer.innerHTML = `
<div class="proof-empty-box">
<div class="proof-empty-icon">📸</div>
<div class="proof-empty-title">No Physical Hardware Photo Yet</div>
<div class="proof-empty-desc">
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.
</div>
<button type="button" class="btn-proof-snap-cta" onclick="window.triggerPhotoCaptureForActiveCam()">
<span>📸</span>
<span>Snap Camera on Pole (+100 pts)</span>
</button>
</div>
`;
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 = `
<div style="padding: 12px; text-align: center; color: #94a3b8; font-size: 0.8rem; background: rgba(30,41,59,0.4); border-radius: 6px;">
No custom placed cameras. All active radar cameras are official municipal portals.
</div>
`;
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none';
return;
}
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block';
el.adminPlacedCamsList.innerHTML = items.map(r => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; background: rgba(30,41,59,0.5); border: 1px solid rgba(148,163,184,0.1); border-radius: 6px; font-size: 0.78rem;">
<div style="display: flex; align-items: center; gap: 8px; overflow: hidden; max-width: 240px;">
${r.photoUrl ? `
<img src="${r.photoUrl}" alt="Thumbnail" style="width: 34px; height: 34px; object-fit: cover; border-radius: 4px; border: 1px solid var(--accent-cyan); flex-shrink: 0;">
` : `
<span style="font-size: 1.1rem; flex-shrink: 0;">📍</span>
`}
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<strong style="color: #f8fafc; display: block; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(r.name)}</strong>
<span style="color: #94a3b8; font-size: 0.7rem;">${r.lat.toFixed(4)}, ${r.lng.toFixed(4)}${r.dateStr}</span>
</div>
</div>
<button type="button" class="btn-secondary" style="padding: 5px 10px; font-size: 0.74rem; color: #f87171; border-color: rgba(239,68,68,0.3); cursor: pointer; flex-shrink: 0;" onclick="window.adminDeleteCamera('${escapeHtml(r.id)}')">
🗑️ Delete
</button>
</div>
`).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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// --- 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();
}
})();