feat(ui): replace all emojis with professional vector SVG icons suite
This commit is contained in:
+105
-75
@@ -395,7 +395,7 @@
|
||||
<circle cx="12" cy="13" r="4"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
${hasPhoto ? '<div class="pin-photo-dot" title="Photo Evidence Verified">📸</div>' : ''}
|
||||
${hasPhoto ? `<div class="pin-photo-dot" title="Photo Evidence Verified">${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -410,8 +410,8 @@
|
||||
|
||||
// Popup details
|
||||
const badgeText = meta.isContested
|
||||
? '🟠 CONTESTED SIGHTING'
|
||||
: (hasPhoto ? '🟢 PHOTO VERIFIED HARDWARE' : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
|
||||
? `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} CONTESTED SIGHTING`
|
||||
: (hasPhoto ? `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} PHOTO VERIFIED HARDWARE` : (cam.isCommunity ? 'COMMUNITY SIGHTING' : 'FLOCK RECORDED ZONE'));
|
||||
|
||||
const popupHtml = `
|
||||
<div class="radar-popup-card">
|
||||
@@ -419,17 +419,18 @@
|
||||
<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>
|
||||
<span style="display: inline-flex; align-items: center; gap: 4px;"><strong>Consensus:</strong> ${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} ${meta.confirmations || 1} Confirmations</span><br>
|
||||
<span style="display: inline-flex; align-items: center; gap: 4px;"><strong>Photo Proof:</strong> ${hasPhoto ? `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} Verified on Pole` : `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} No Photo Yet`}</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: 700; width: 100%; border: none; border-radius: 6px; padding: 10px; cursor: pointer; box-shadow: 0 2px 8px rgba(6, 182, 212, 0.4);" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||
📸 View Camera Info & Agency Records ↗
|
||||
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 700; width: 100%; border: none; border-radius: 6px; padding: 10px; cursor: pointer; box-shadow: 0 2px 8px rgba(6, 182, 212, 0.4); display: flex; align-items: center; justify-content: center; gap: 8px;" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||
<span>View Camera Info & Agency Records ↗</span>
|
||||
</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>
|
||||
<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
<span>REMOVE THIS PIN (ADMIN)</span>
|
||||
</button>
|
||||
` : ''}
|
||||
@@ -531,7 +532,7 @@
|
||||
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>
|
||||
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
||||
<div>
|
||||
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
||||
<p>${escapeHtml(closestCam.agency)} • Vehicle Profile Logging</p>
|
||||
@@ -688,9 +689,10 @@
|
||||
function updateFollowCarUI() {
|
||||
if (el.btnFollowCar) {
|
||||
el.btnFollowCar.classList.toggle('active', isFollowCarLocked);
|
||||
const icon = window.FlockIcons ? FlockIcons.target() : '';
|
||||
el.btnFollowCar.innerHTML = isFollowCarLocked
|
||||
? `<span>🎯</span><span>Follow Car: ON</span>`
|
||||
: `<span>🎯</span><span>Follow Car: OFF</span>`;
|
||||
? `${icon}<span>Follow Car: ON</span>`
|
||||
: `${icon}<span>Follow Car: OFF</span>`;
|
||||
}
|
||||
if (el.btnRecenterCar) {
|
||||
el.btnRecenterCar.classList.toggle('hidden', isFollowCarLocked);
|
||||
@@ -703,7 +705,7 @@
|
||||
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)');
|
||||
showToast(mode === 'highway' ? 'Highway Range Active (2,500 ft advance alert)' : 'City Range Active (1,000 ft alert)');
|
||||
}
|
||||
|
||||
// --- DASHBOARD FULLSCREEN MOUNT MODE ---
|
||||
@@ -711,7 +713,7 @@
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().then(() => {
|
||||
if (el.btnFullscreenMount) el.btnFullscreenMount.classList.add('active');
|
||||
showToast("📱 Fullscreen Dashboard Mode Active");
|
||||
showToast("Fullscreen Dashboard Mode Active");
|
||||
}).catch(err => {
|
||||
console.warn('Fullscreen request failed:', err);
|
||||
});
|
||||
@@ -790,7 +792,7 @@
|
||||
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>
|
||||
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
||||
<div>
|
||||
<strong>FLOCK CAMERA ACTIVE ZONE</strong>
|
||||
<p>Test Alert • Vehicle Profile Logging Active</p>
|
||||
@@ -824,7 +826,7 @@
|
||||
|
||||
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.');
|
||||
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 ---
|
||||
@@ -840,7 +842,8 @@
|
||||
navigator.geolocation.clearWatch(liveGpsWatchId);
|
||||
liveGpsWatchId = null;
|
||||
el.btnLiveGps.classList.remove('active');
|
||||
el.btnLiveGps.innerHTML = `<span>🎯</span><span>Live GPS Drive Mode</span>`;
|
||||
const radioIcon = window.FlockIcons ? FlockIcons.radio() : '';
|
||||
el.btnLiveGps.innerHTML = `${radioIcon}<span>Live GPS Drive Mode</span>`;
|
||||
el.hudSpeedVal.textContent = '0';
|
||||
return;
|
||||
}
|
||||
@@ -848,7 +851,8 @@
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
|
||||
el.btnLiveGps.classList.add('active');
|
||||
el.btnLiveGps.innerHTML = `<span>🛑</span><span>Stop Live GPS</span>`;
|
||||
const stopIcon = window.FlockIcons ? FlockIcons.stopCircle() : '';
|
||||
el.btnLiveGps.innerHTML = `${stopIcon}<span>Stop Live GPS</span>`;
|
||||
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
|
||||
|
||||
liveGpsWatchId = navigator.geolocation.watchPosition(
|
||||
@@ -898,7 +902,7 @@
|
||||
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" +
|
||||
"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" +
|
||||
@@ -919,7 +923,8 @@
|
||||
|
||||
function startDriveSimulation() {
|
||||
isSimulating = true;
|
||||
el.btnSimDrive.innerHTML = `<span>⏸</span> Pause Simulation`;
|
||||
const pauseIcon = window.FlockIcons ? FlockIcons.pause('flock-svg-sm') : '';
|
||||
el.btnSimDrive.innerHTML = `${pauseIcon}<span>Pause Simulation</span>`;
|
||||
el.btnSimDrive.classList.add('active');
|
||||
|
||||
simIndex = 0;
|
||||
@@ -932,7 +937,8 @@
|
||||
|
||||
function stopDriveSimulation() {
|
||||
isSimulating = false;
|
||||
el.btnSimDrive.innerHTML = `<span>▶</span> Simulate Drive`;
|
||||
const playIcon = window.FlockIcons ? FlockIcons.play('flock-svg-sm') : '';
|
||||
el.btnSimDrive.innerHTML = `${playIcon}<span>Simulate Drive</span>`;
|
||||
el.btnSimDrive.classList.remove('active');
|
||||
if (simAnimId) cancelAnimationFrame(simAnimId);
|
||||
}
|
||||
@@ -1028,7 +1034,7 @@
|
||||
|
||||
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
|
||||
async function attemptAutoLocate() {
|
||||
updateSpottedBadge('🛰️ Spotting Antelope Valley Location...');
|
||||
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
|
||||
@@ -1059,7 +1065,7 @@
|
||||
|
||||
updateUserCoordinates(targetLat, targetLng, 0, 0);
|
||||
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 });
|
||||
updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley (Network)`);
|
||||
updateSpottedBadge(`Spotted: ${city}, Antelope Valley (Network)`);
|
||||
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`);
|
||||
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
|
||||
checkProximityToCameras();
|
||||
@@ -1073,7 +1079,7 @@
|
||||
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)`);
|
||||
updateSpottedBadge(`Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
|
||||
checkProximityToCameras();
|
||||
ipFound = true;
|
||||
}
|
||||
@@ -1083,7 +1089,7 @@
|
||||
}
|
||||
|
||||
if (!ipFound) {
|
||||
updateSpottedBadge('📍 Antelope Valley: Palmdale, CA (Default)');
|
||||
updateSpottedBadge('Antelope Valley: Palmdale, CA (Default)');
|
||||
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
|
||||
checkProximityToCameras();
|
||||
}
|
||||
@@ -1098,8 +1104,8 @@
|
||||
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');
|
||||
updateSpottedBadge(`High-Precision GPS (±${acc}m) • Antelope Valley`);
|
||||
updateHudLocationStatus('Live Driving GPS Active');
|
||||
checkProximityToCameras();
|
||||
},
|
||||
gpsErr => {
|
||||
@@ -1147,7 +1153,7 @@
|
||||
async function useLiveGps() {
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
|
||||
updateSpottedBadge('🛰️ Re-detecting location in Antelope Valley...');
|
||||
updateSpottedBadge('Re-detecting location in Antelope Valley...');
|
||||
updateHudLocationStatus('Acquiring high-precision location...');
|
||||
|
||||
// 1. First ensure IP location is refreshed
|
||||
@@ -1157,7 +1163,7 @@
|
||||
if (ipData && ipData.latitude) {
|
||||
const city = ipData.city || 'Palmdale';
|
||||
jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`);
|
||||
updateSpottedBadge(`📍 Spotted: ${city}, Antelope Valley`);
|
||||
updateSpottedBadge(`Spotted: ${city}, Antelope Valley`);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
@@ -1168,8 +1174,8 @@
|
||||
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');
|
||||
updateSpottedBadge(`Live GPS Active (±${acc}m)`);
|
||||
updateHudLocationStatus('High-Precision GPS Locked');
|
||||
playRadarChime();
|
||||
speakVoiceAlert('GPS position locked in Antelope Valley.');
|
||||
checkProximityToCameras();
|
||||
@@ -1180,10 +1186,10 @@
|
||||
if (err.code === 1) { // PERMISSION_DENIED
|
||||
el.hudBannerAlert.classList.add('visible');
|
||||
el.hudBannerAlert.innerHTML = `
|
||||
<span class="banner-pulse-icon">ℹ️</span>
|
||||
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.radar('flock-svg-lg') : ''}</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>
|
||||
<p>To enable turn-by-turn driving GPS, click the permissions icon in your browser address bar and select 'Allow Location'.</p>
|
||||
</div>
|
||||
`;
|
||||
setTimeout(() => {
|
||||
@@ -1201,8 +1207,8 @@
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
updateUserCoordinates(lat, lng, 0, 0);
|
||||
map.flyTo([lat, lng], zoom, { duration: 1.2 });
|
||||
updateHudLocationStatus(`📍 ${label}`);
|
||||
updateSpottedBadge(`📍 Set to: ${label}`);
|
||||
updateHudLocationStatus(label);
|
||||
updateSpottedBadge(`Set to: ${label}`);
|
||||
checkProximityToCameras();
|
||||
}
|
||||
|
||||
@@ -1237,13 +1243,15 @@
|
||||
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>`;
|
||||
const mapIcon = window.FlockIcons ? FlockIcons.map() : '';
|
||||
el.btnMapLayer.innerHTML = `${mapIcon}<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>`;
|
||||
const layersIcon = window.FlockIcons ? FlockIcons.layers() : '';
|
||||
el.btnMapLayer.innerHTML = `${layersIcon}<span>Satellite / Dark</span>`;
|
||||
el.btnMapLayer.classList.remove('active');
|
||||
}
|
||||
}
|
||||
@@ -1252,14 +1260,16 @@
|
||||
function updateProfileDisplay() {
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user && user.isLoggedIn) {
|
||||
const carSvg = window.FlockIcons ? FlockIcons.car('flock-svg-sm') : '';
|
||||
el.userProfilePill.innerHTML = `
|
||||
<span class="avatar-dot">🚘</span>
|
||||
<span class="avatar-dot">${carSvg}</span>
|
||||
<span>${escapeHtml(user.username)}</span>
|
||||
<span class="points-badge">${user.points} pts</span>
|
||||
`;
|
||||
} else {
|
||||
const userSvg = `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
|
||||
el.userProfilePill.innerHTML = `
|
||||
<span class="avatar-dot">👤</span>
|
||||
<span class="avatar-dot">${userSvg}</span>
|
||||
<span>Driver Login</span>
|
||||
`;
|
||||
}
|
||||
@@ -1326,13 +1336,13 @@
|
||||
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)`;
|
||||
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.alertTriangle('flock-svg-sm') : ''} <span>CONTESTED LOCATION • RE-VERIFICATION PENDING</span>`;
|
||||
} else if (hasPhoto) {
|
||||
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-verified';
|
||||
el.camDetailVerifyText.textContent = `🟢 VERIFIED PHOTO PROOF • ${meta.confirmations || 1} CONFIRMATIONS`;
|
||||
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''} <span>VERIFIED PHOTO PROOF • ${meta.confirmations || 1} CONFIRMATIONS</span>`;
|
||||
} else {
|
||||
el.camDetailVerifyBadge.className = 'cam-verification-badge badge-unverified';
|
||||
el.camDetailVerifyText.textContent = `🟡 UNVERIFIED PIN • NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)`;
|
||||
el.camDetailVerifyText.innerHTML = `${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''} <span>UNVERIFIED PIN • NEEDS PHOTO PROOF (${meta.confirmations || 1} CONFIRMED)</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1344,10 +1354,16 @@
|
||||
<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-author" style="display: inline-flex; align-items: center; gap: 5px;">
|
||||
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||
<span>Scout: ${escapeHtml(meta.photoAuthor || 'Active Driver')}</span>
|
||||
</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>
|
||||
<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; display: inline-flex; align-items: center; gap: 4px;">
|
||||
${window.FlockIcons ? window.FlockIcons.checkCircle('flock-svg-sm') : ''}
|
||||
<span>VERIFIED HARDWARE</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1355,13 +1371,15 @@
|
||||
} else {
|
||||
el.camProofContainer.innerHTML = `
|
||||
<div class="proof-empty-box">
|
||||
<div class="proof-empty-icon">📸</div>
|
||||
<div class="proof-empty-icon">
|
||||
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-hero') : ''}
|
||||
</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>
|
||||
${window.FlockIcons ? window.FlockIcons.camera('flock-svg-sm') : ''}
|
||||
<span>Snap Camera on Pole (+100 pts)</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1381,10 +1399,10 @@
|
||||
if (el.btnVoteConfirmCam && el.btnVoteConfirmText) {
|
||||
if (userConfirmed) {
|
||||
el.btnVoteConfirmCam.classList.add('voted');
|
||||
el.btnVoteConfirmText.textContent = `👍 Confirmed (${meta.confirmations || 1})`;
|
||||
el.btnVoteConfirmText.innerHTML = `${window.FlockIcons ? window.FlockIcons.check('flock-svg-sm') : ''} Confirmed (${meta.confirmations || 1})`;
|
||||
} else {
|
||||
el.btnVoteConfirmCam.classList.remove('voted');
|
||||
el.btnVoteConfirmText.textContent = `Confirm Active (${meta.confirmations || 1})`;
|
||||
el.btnVoteConfirmText.innerHTML = `${window.FlockIcons ? window.FlockIcons.thumbUp('flock-svg-sm') : ''} Confirm Active (${meta.confirmations || 1})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,15 +1610,16 @@
|
||||
${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>
|
||||
<span style="display: flex; align-items: center; color: var(--accent-cyan); flex-shrink: 0;">${window.FlockIcons ? FlockIcons.mapPin('flock-svg-sm') : ''}</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 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; display: flex; align-items: center; gap: 5px;" onclick="window.adminDeleteCamera('${escapeHtml(r.id)}')">
|
||||
${window.FlockIcons ? FlockIcons.trash('flock-svg-sm') : ''}
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
@@ -1630,7 +1649,7 @@
|
||||
closeCameraDetailModal();
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert("Placed camera deleted from radar.");
|
||||
showToast("🗑️ Placed camera permanently removed.");
|
||||
showToast("Placed camera permanently removed.");
|
||||
};
|
||||
|
||||
// --- CONTEST CAMERA MODAL ---
|
||||
@@ -1693,7 +1712,7 @@
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
showToast("📸 Processing & compressing photo proof...");
|
||||
showToast("Processing & compressing photo proof...");
|
||||
const compressedDataUrl = await window.FlockAuth.compressImage(file, 1000, 0.72);
|
||||
|
||||
if (photoUploadTarget === 'quicklog') {
|
||||
@@ -1712,7 +1731,7 @@
|
||||
updateProfileDisplay();
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert("Camera photo proof verified and logged to radar grid. +150 Scout Points!");
|
||||
showToast("📸 Camera photo proof verified & logged! +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);
|
||||
@@ -1722,19 +1741,19 @@
|
||||
renderCameraZones();
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert("Photo proof verified and locked into radar database. +100 Scout Points awarded!");
|
||||
showToast("📸 Photo proof verified & locked! +100 Scout Points!");
|
||||
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!");
|
||||
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!");
|
||||
showToast("Empty pole photo attached to contest report!");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Photo processing error:', err);
|
||||
@@ -1820,7 +1839,7 @@
|
||||
if (res.success) {
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert("Camera confirmed active on pole. +25 Scout Points!");
|
||||
showToast(`👍 Confirmed active on pole! Total: ${res.count} confirmations.`);
|
||||
showToast(`Confirmed active on pole! Total: ${res.count} confirmations.`);
|
||||
openCameraDetailModal(activeModalCamera);
|
||||
renderCameraZones();
|
||||
updateProfileDisplay();
|
||||
@@ -1876,9 +1895,9 @@
|
||||
if (res.success) {
|
||||
if (res.isContested) {
|
||||
speakVoiceAlert("Camera flagged as contested by community consensus.");
|
||||
showToast("⚠️ Location marked Contested by community consensus.");
|
||||
showToast("Location marked Contested by community consensus.");
|
||||
} else {
|
||||
showToast(`⚠️ Contest submitted (${res.contestCount}/3 flags for consensus review).`);
|
||||
showToast(`Contest submitted (${res.contestCount}/3 flags for consensus review).`);
|
||||
}
|
||||
renderCameraZones();
|
||||
} else {
|
||||
@@ -1926,7 +1945,10 @@
|
||||
if (user) {
|
||||
user.soundEnabled = !user.soundEnabled;
|
||||
window.FlockAuth.saveUser();
|
||||
el.btnMuteSound.innerHTML = user.soundEnabled ? '🔔 Sound: ON' : '🔕 Sound: OFF';
|
||||
const volIcon = user.soundEnabled
|
||||
? (window.FlockIcons ? FlockIcons.volume('flock-svg-sm') : '')
|
||||
: (window.FlockIcons ? FlockIcons.volumeX('flock-svg-sm') : '');
|
||||
el.btnMuteSound.innerHTML = `${volIcon}<span>Sound: ${user.soundEnabled ? 'ON' : 'OFF'}</span>`;
|
||||
el.btnMuteSound.classList.toggle('active', !user.soundEnabled);
|
||||
}
|
||||
});
|
||||
@@ -1942,23 +1964,27 @@
|
||||
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;
|
||||
const sound = document.getElementById('login-sound').checked;
|
||||
|
||||
if (window.FlockAuth) {
|
||||
window.FlockAuth.login(username, vehicle, soundEnabled);
|
||||
updateProfileDisplay();
|
||||
}
|
||||
window.FlockAuth.login(username, vehicle, sound);
|
||||
updateProfileDisplay();
|
||||
closeLoginModal();
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert(`Welcome to the grid, scout ${username}.`);
|
||||
showToast(`Welcome back, Scout ${username}!`);
|
||||
});
|
||||
|
||||
// Login as Guest button
|
||||
el.loginGuestBtn.addEventListener('click', () => {
|
||||
if (window.FlockAuth) {
|
||||
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
|
||||
updateProfileDisplay();
|
||||
}
|
||||
closeLoginModal();
|
||||
});
|
||||
// Guest scout button
|
||||
if (el.loginGuestBtn) {
|
||||
el.loginGuestBtn.addEventListener('click', () => {
|
||||
if (window.FlockAuth) {
|
||||
window.FlockAuth.login('Guest Scout', 'Unspecified Vehicle', true);
|
||||
updateProfileDisplay();
|
||||
}
|
||||
closeLoginModal();
|
||||
showToast("Continuing as Anonymous Scout");
|
||||
});
|
||||
}
|
||||
|
||||
// Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals)
|
||||
if (el.btnAdminClearAllPlaced) {
|
||||
@@ -1974,7 +2000,7 @@
|
||||
renderAdminPlacedList();
|
||||
playLogConfirmationChirp();
|
||||
speakVoiceAlert("All custom placed pins cleared from radar.");
|
||||
showToast("🗑️ All custom placed pins cleared.");
|
||||
showToast("All custom placed pins cleared.");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2031,7 +2057,11 @@
|
||||
e.stopPropagation();
|
||||
if (el.driverHud) {
|
||||
const isExpanded = el.driverHud.classList.toggle('is-expanded');
|
||||
if (el.hudDetailsArrow) el.hudDetailsArrow.textContent = isExpanded ? '▲' : '🔍';
|
||||
if (el.hudDetailsArrow) {
|
||||
el.hudDetailsArrow.innerHTML = isExpanded
|
||||
? (window.FlockIcons ? FlockIcons.chevronDown('flock-svg-sm') : '▲')
|
||||
: (window.FlockIcons ? FlockIcons.search('flock-svg-sm') : '▼');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user