feat: add 3-way audio mode, viral share API, offline road-ready indicator, multi-app navigation (Google/Apple/Waze), and PWA car HUD install (v25)
This commit is contained in:
+189
-31
@@ -95,6 +95,8 @@
|
||||
let isUserInteractingWithMap = false;
|
||||
let touchInteractionTimer = null;
|
||||
let radarRangeMode = 'city'; // 'city' (1,000 ft) or 'highway' (2,500 ft)
|
||||
let radarAudioMode = localStorage.getItem('flock_radar_audio_mode') || 'full'; // 'full' | 'chime' | 'silent'
|
||||
let deferredInstallPrompt = null;
|
||||
let lastCautionSpokenCamId = null;
|
||||
|
||||
// Photo Evidence & Consensus State
|
||||
@@ -602,8 +604,8 @@
|
||||
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';
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user) updateSoundUi(user.soundEnabled);
|
||||
setAudioMode(radarAudioMode, false);
|
||||
updateOfflineStatus();
|
||||
updateVehicleSupporterAura();
|
||||
}
|
||||
|
||||
@@ -861,6 +863,7 @@
|
||||
let stationaryChirpCount = 0;
|
||||
|
||||
function playRadarTick(freq = 800, volume = 0.22) {
|
||||
if (radarAudioMode === 'silent') return;
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user && !user.soundEnabled) return;
|
||||
|
||||
@@ -892,6 +895,7 @@
|
||||
}
|
||||
|
||||
function handleProximityChirps(distFt) {
|
||||
if (radarAudioMode === 'silent') return;
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user && !user.soundEnabled) return;
|
||||
|
||||
@@ -945,42 +949,72 @@
|
||||
playRadarTick(pitchHz, chirpVol);
|
||||
}
|
||||
|
||||
function updateSoundUi(soundEnabled) {
|
||||
function setAudioMode(mode, showFeedback = true) {
|
||||
radarAudioMode = mode;
|
||||
localStorage.setItem('flock_radar_audio_mode', mode);
|
||||
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user) {
|
||||
user.soundEnabled = (mode !== 'silent');
|
||||
window.FlockAuth.saveUser();
|
||||
}
|
||||
|
||||
document.querySelectorAll('.btn-audio-mode').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.mode === mode);
|
||||
});
|
||||
|
||||
if (el.btnMuteSound) {
|
||||
const volIcon = soundEnabled
|
||||
const volIcon = mode !== 'silent'
|
||||
? (window.FlockIcons ? FlockIcons.volume('flock-svg-sm') : '')
|
||||
: (window.FlockIcons ? FlockIcons.volumeX('flock-svg-sm') : '');
|
||||
el.btnMuteSound.innerHTML = `${volIcon}<span>Sound: ${soundEnabled ? 'ON' : 'OFF'}</span>`;
|
||||
el.btnMuteSound.classList.toggle('active', !soundEnabled);
|
||||
el.btnMuteSound.innerHTML = `${volIcon}<span>Sound: ${mode.toUpperCase()}</span>`;
|
||||
el.btnMuteSound.classList.toggle('active', mode === 'silent');
|
||||
}
|
||||
|
||||
if (el.btnHudSoundToggle) {
|
||||
el.btnHudSoundToggle.classList.toggle('is-muted', !soundEnabled);
|
||||
el.btnHudSoundToggle.classList.toggle('is-muted', mode === 'silent');
|
||||
const iconWrap = document.getElementById('hud-sound-icon');
|
||||
if (iconWrap) {
|
||||
iconWrap.innerHTML = soundEnabled
|
||||
? `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>`
|
||||
: `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>`;
|
||||
const label = el.btnHudSoundToggle.querySelector('.capsule-sound-label');
|
||||
|
||||
if (mode === 'full') {
|
||||
if (iconWrap) iconWrap.innerHTML = `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>`;
|
||||
if (label) label.textContent = 'Audio';
|
||||
el.btnHudSoundToggle.title = 'Audio Mode: Full Voice + Radar Chimes • Tap to switch to Chime Only';
|
||||
if (showFeedback) {
|
||||
playRadarTick(880, 0.25);
|
||||
showToast('🔊 Audio Mode: Full Voice + Radar Chimes');
|
||||
}
|
||||
} else if (mode === 'chime') {
|
||||
if (iconWrap) iconWrap.innerHTML = `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>`;
|
||||
if (label) label.textContent = 'Chime';
|
||||
el.btnHudSoundToggle.title = 'Audio Mode: Discreet Chime Only (No Speech) • Tap to Mute';
|
||||
if (showFeedback) {
|
||||
playRadarTick(1000, 0.25);
|
||||
showToast('🔔 Audio Mode: Discreet Radar Chimes Only');
|
||||
}
|
||||
} else {
|
||||
if (iconWrap) iconWrap.innerHTML = `<svg class="flock-svg flock-svg-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>`;
|
||||
if (label) label.textContent = 'Muted';
|
||||
el.btnHudSoundToggle.title = 'Audio Mode: Silent (Visual HUD Only) • Tap to Unmute';
|
||||
if (showFeedback) {
|
||||
showToast('🔇 Audio Mode: Silent HUD Alerts Only');
|
||||
}
|
||||
}
|
||||
el.btnHudSoundToggle.title = soundEnabled ? 'Audio Radar Alerts: ON (Tap to Mute)' : 'Audio Radar Alerts: MUTED (Tap to Unmute)';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSound() {
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user) {
|
||||
user.soundEnabled = !user.soundEnabled;
|
||||
window.FlockAuth.saveUser();
|
||||
updateSoundUi(user.soundEnabled);
|
||||
if (user.soundEnabled) {
|
||||
playRadarTick(880, 0.25);
|
||||
showToast('🔊 Audio Radar Alerts: Active');
|
||||
} else {
|
||||
showToast('🔇 Audio Radar Alerts: Muted');
|
||||
}
|
||||
function cycleAudioMode() {
|
||||
if (radarAudioMode === 'full') {
|
||||
setAudioMode('chime', true);
|
||||
} else if (radarAudioMode === 'chime') {
|
||||
setAudioMode('silent', true);
|
||||
} else {
|
||||
setAudioMode('full', true);
|
||||
}
|
||||
}
|
||||
|
||||
function playRadarChime() {
|
||||
if (radarAudioMode === 'silent') return;
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user && !user.soundEnabled) return;
|
||||
|
||||
@@ -1001,9 +1035,9 @@
|
||||
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
|
||||
osc.frequency.setValueAtTime(880, audioContext.currentTime);
|
||||
osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.12);
|
||||
osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.24);
|
||||
|
||||
gain.gain.setValueAtTime(0.35, audioContext.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.45);
|
||||
@@ -1019,6 +1053,7 @@
|
||||
}
|
||||
|
||||
function speakVoiceAlert(text) {
|
||||
if (radarAudioMode !== 'full') return;
|
||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||
if (user && !user.soundEnabled) return;
|
||||
if (!window.speechSynthesis) return;
|
||||
@@ -1040,6 +1075,75 @@
|
||||
speakVoiceAlert("Warning. Flock camera detected ahead. You are entering a recorded surveillance zone.");
|
||||
}
|
||||
|
||||
// --- VIRAL SHARING HELPER ---
|
||||
async function shareApp(customTitle, customText, customUrl) {
|
||||
const shareData = {
|
||||
title: customTitle || 'FlockRadar • Live Surveillance Camera Detector',
|
||||
text: customText || 'Check out FlockRadar — real-time live map & radar detector for Flock Safety surveillance cameras with driving alerts:',
|
||||
url: customUrl || 'https://flockradar.org/'
|
||||
};
|
||||
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
showToast('Thanks for sharing FlockRadar!');
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return;
|
||||
}
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareData.url);
|
||||
showToast('Link copied to clipboard! Share with drivers.');
|
||||
return;
|
||||
} catch (err) {
|
||||
// Fallback below
|
||||
}
|
||||
}
|
||||
|
||||
prompt('Copy FlockRadar link to share:', shareData.url);
|
||||
}
|
||||
|
||||
// --- OFFLINE ROAD-READINESS & NETWORK MONITOR ---
|
||||
function updateOfflineStatus() {
|
||||
const pill = document.getElementById('tools-offline-status-pill');
|
||||
const dot = document.getElementById('offline-dot');
|
||||
const text = document.getElementById('tools-offline-status-text');
|
||||
if (!pill || !text) return;
|
||||
|
||||
if (navigator.onLine) {
|
||||
pill.classList.remove('offline-mode');
|
||||
if (dot) dot.className = 'status-indicator-dot dot-online';
|
||||
text.textContent = '36 Cameras Cached • Ready for Dead Zones';
|
||||
} else {
|
||||
pill.classList.add('offline-mode');
|
||||
if (dot) dot.className = 'status-indicator-dot dot-offline';
|
||||
text.textContent = '⚡ Offline Driving Active • 36 Cameras in Local Cache';
|
||||
}
|
||||
}
|
||||
|
||||
// --- PWA CAR HUD APP INSTALLATION ---
|
||||
function handleInstallApp() {
|
||||
if (deferredInstallPrompt) {
|
||||
deferredInstallPrompt.prompt();
|
||||
deferredInstallPrompt.userChoice.then((choiceResult) => {
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
showToast('FlockRadar added to your Home Screen!');
|
||||
}
|
||||
deferredInstallPrompt = null;
|
||||
});
|
||||
} else {
|
||||
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
if (isIos) {
|
||||
alert("To Install on iPhone:\n\n1. Tap the Share button in Safari (square with up arrow at bottom)\n2. Tap 'Add to Home Screen'\n\nFlockRadar will launch fullscreen on your car dashboard!");
|
||||
} else {
|
||||
showToast("To install, tap your browser's menu (⋮) and select 'Install app' or 'Add to Home Screen'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TEST ALERT SOUND & VOICE NOW ---
|
||||
function testAlertSoundAndVoice() {
|
||||
playRadarTick(950, 0.3);
|
||||
@@ -1852,9 +1956,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate link
|
||||
if (el.btnProofNavigate) {
|
||||
el.btnProofNavigate.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
|
||||
// Multi-App Navigation & Pin Share Links
|
||||
const elGoogle = document.getElementById('btn-proof-nav-google');
|
||||
const elApple = document.getElementById('btn-proof-nav-apple');
|
||||
const elWaze = document.getElementById('btn-proof-nav-waze');
|
||||
const elShareCam = document.getElementById('btn-proof-share-cam');
|
||||
|
||||
if (elGoogle) elGoogle.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
|
||||
if (elApple) elApple.href = `https://maps.apple.com/?daddr=${cam.lat},${cam.lng}&dirflg=d`;
|
||||
if (elWaze) elWaze.href = `https://waze.com/ul?ll=${cam.lat},${cam.lng}&navigate=yes`;
|
||||
if (elShareCam) {
|
||||
elShareCam.onclick = () => {
|
||||
shareApp(
|
||||
`Flock Camera: ${cam.name}`,
|
||||
`Flock surveillance camera spotted at ${cam.name} (${cam.agency || 'Public Roadway'}). Check specs on FlockRadar:`,
|
||||
`https://flockradar.org/?cam=${cam.id}`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Lookup Directory Intelligence for this camera & agency
|
||||
@@ -2399,9 +2517,49 @@
|
||||
el.btnMuteSound.addEventListener('click', toggleSound);
|
||||
}
|
||||
if (el.btnHudSoundToggle) {
|
||||
el.btnHudSoundToggle.addEventListener('click', toggleSound);
|
||||
el.btnHudSoundToggle.addEventListener('click', cycleAudioMode);
|
||||
}
|
||||
|
||||
// Audio Alert Mode Pill Buttons
|
||||
document.querySelectorAll('.btn-audio-mode').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
setAudioMode(btn.dataset.mode, true);
|
||||
});
|
||||
});
|
||||
|
||||
// Viral Share Buttons
|
||||
const btnShareTools = document.getElementById('btn-share-radar-tools');
|
||||
if (btnShareTools) {
|
||||
btnShareTools.addEventListener('click', () => shareApp());
|
||||
}
|
||||
|
||||
const btnShareSupport = document.getElementById('btn-support-share-app');
|
||||
if (btnShareSupport) {
|
||||
btnShareSupport.addEventListener('click', () => {
|
||||
shareApp(
|
||||
'Support FlockRadar',
|
||||
'Help us keep local streets transparent and free of unconstitutional surveillance. Check out FlockRadar live map & driving detector:',
|
||||
'https://flockradar.org/'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// PWA Install Button
|
||||
const btnInstallApp = document.getElementById('btn-install-hud-app');
|
||||
if (btnInstallApp) {
|
||||
btnInstallApp.addEventListener('click', handleInstallApp);
|
||||
}
|
||||
|
||||
// Offline / Online Network Listeners
|
||||
window.addEventListener('online', () => {
|
||||
updateOfflineStatus();
|
||||
showToast('🟢 Back Online: Cloud database sync active.');
|
||||
});
|
||||
window.addEventListener('offline', () => {
|
||||
updateOfflineStatus();
|
||||
showToast('⚡ Offline Driving Active: All 36 cameras running from local cache.');
|
||||
});
|
||||
|
||||
// Report Camera FAB
|
||||
if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user