+ 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';
+ }
+ }
+
+ // 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');
+ }
+ }
+ }
+
+ // --- 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 === '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
@@ -1324,6 +1600,108 @@
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);
+ });
+ }
+
+ // 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;
@@ -1374,11 +1752,25 @@
const lng = parseFloat(document.getElementById('report-lng').value);
if (window.FlockAuth) {
- const newRpt = window.FlockAuth.addReport({ lat, lng, type, intersection, notes });
+ 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();
- alert(`Camera report added! +50 Scout Points. Zone is now active on radar.`);
+ playLogConfirmationChirp();
+ speakVoiceAlert("Camera sighting broadcast to radar network!");
+ showToast(`Camera report added! +${newRpt.photoUrl ? '150' : '50'} Scout Points.`);
}
closeReportModal();