feat(tactical-driving): add Geiger proximity chirps, 1-tap HUD audio mute, live compass heading, and VIP supporter gold aura (v18)

This commit is contained in:
2026-09-12 11:14:44 -07:00
parent bcfa81f5ff
commit a807a3abca
4 changed files with 316 additions and 44 deletions
+190 -22
View File
@@ -148,6 +148,7 @@
btnSimDrive: document.getElementById('btn-sim-drive'),
btnResetSim: document.getElementById('btn-reset-sim'),
btnMuteSound: document.getElementById('btn-mute-sound'),
btnHudSoundToggle: document.getElementById('btn-hud-sound-toggle'),
btnMobileConnect: document.getElementById('btn-mobile-connect'),
btnReportCameraFab: document.getElementById('btn-report-camera-fab'),
@@ -457,8 +458,11 @@
// --- VEHICLE MARKER SETUP ---
function setupUserMarker() {
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
const supporterClass = isSupporter ? ' vip-supporter' : '';
const vehicleHtml = `
<div class="user-vehicle-marker" id="user-vehicle-marker">
<div class="user-vehicle-marker${supporterClass}" id="user-vehicle-marker">
<div class="supporter-gold-aura"></div>
<div class="radar-scan-cone"></div>
<div class="car-body-dot">
<div class="car-arrow"></div>
@@ -479,13 +483,51 @@
}).addTo(map);
}
function updateVehicleSupporterAura() {
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) {
carElem.classList.toggle('vip-supporter', isSupporter);
}
}
function getCardinalDirection(deg) {
if (deg === null || deg === undefined || isNaN(deg)) return 'IDLE';
const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
const normalized = ((deg % 360) + 360) % 360;
const index = Math.round(normalized / 45) % 8;
return directions[index];
}
function updateHudSpeedAndHeading(speed, headingDeg) {
if (el.hudSpeedVal) el.hudSpeedVal.textContent = speed;
const headingElem = document.getElementById('hud-heading-val');
const compassNeedle = document.getElementById('hud-compass-needle');
if (headingElem) {
if (speed > 1 && headingDeg !== null && !isNaN(headingDeg)) {
const cardinal = getCardinalDirection(headingDeg);
headingElem.textContent = `${cardinal} ${Math.round(headingDeg)}°`;
if (compassNeedle) {
compassNeedle.style.transform = `rotate(${Math.round(headingDeg)}deg)`;
compassNeedle.style.opacity = '1';
}
} else {
headingElem.textContent = 'IDLE';
if (compassNeedle) compassNeedle.style.opacity = '0.4';
}
}
}
// --- HUD SETUP ---
function setupHud() {
el.hudSpeedVal.textContent = '0';
updateHudSpeedAndHeading(0, null);
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';
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user) updateSoundUi(user.soundEnabled);
updateVehicleSupporterAura();
}
// --- PROXIMITY CALCULATION & ALERT ENGINE ---
@@ -525,6 +567,9 @@
// Update Graduated Radar Signal Strength Meter (Valentine One Style)
updateRadarGauge(distFt, insideZone, closestCam);
// Audio Proximity Geiger-Counter Chirps
handleProximityChirps(distFt);
// State Transitions
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
if (insideZone) {
@@ -735,6 +780,129 @@
}
// --- AUDIO SYNTHESIZER & SPOKEN VOICE ALERTS ---
let lastChirpTime = 0;
let stationaryChirpCount = 0;
function playRadarTick(freq = 800, volume = 0.22) {
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(freq, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 1.15, audioContext.currentTime + 0.045);
gain.gain.setValueAtTime(volume, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.05);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 0.05);
} catch (e) {
// AudioContext policy
}
}
function handleProximityChirps(distFt) {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
const maxChirpDist = (radarRangeMode === 'highway') ? 2200 : 1200;
if (distFt > maxChirpDist || distFt <= 0) {
stationaryChirpCount = 0;
return;
}
// Auto-quiet when stopped at a red light or parked
if (currentSpeed < 2) {
if (stationaryChirpCount >= 2) return;
} else {
stationaryChirpCount = 0;
}
let intervalMs = 2400;
let pitchHz = 640;
let chirpVol = 0.16;
if (distFt <= 120) {
intervalMs = 200;
pitchHz = 1100;
chirpVol = 0.30;
} else if (distFt <= 300) {
intervalMs = 420;
pitchHz = 950;
chirpVol = 0.26;
} else if (distFt <= 600) {
intervalMs = 800;
pitchHz = 820;
chirpVol = 0.22;
} else if (distFt <= 1000) {
intervalMs = 1400;
pitchHz = 720;
chirpVol = 0.18;
} else {
intervalMs = 2400;
pitchHz = 640;
chirpVol = 0.15;
}
const now = Date.now();
if (now - lastChirpTime < intervalMs) return;
lastChirpTime = now;
if (currentSpeed < 2) {
stationaryChirpCount++;
}
playRadarTick(pitchHz, chirpVol);
}
function updateSoundUi(soundEnabled) {
if (el.btnMuteSound) {
const volIcon = soundEnabled
? (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);
}
if (el.btnHudSoundToggle) {
el.btnHudSoundToggle.classList.toggle('is-muted', !soundEnabled);
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>`;
}
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 playRadarChime() {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
@@ -797,8 +965,12 @@
// --- TEST ALERT SOUND & VOICE NOW ---
function testAlertSoundAndVoice() {
playRadarChime();
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
playRadarTick(950, 0.3);
setTimeout(() => playRadarTick(1100, 0.35), 180);
setTimeout(() => {
playRadarChime();
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
}, 400);
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
@@ -897,6 +1069,7 @@
}
el.hudSpeedVal.textContent = currentSpeed;
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
updateSpottedBadge(`Live GPS (±${accuracy}m) • Locked`);
updateHudLocationStatus('Live GPS Active');
checkProximityToCameras();
@@ -924,7 +1097,7 @@
// If movement is under 1.5m (GPS drift while stationary or stopped at traffic signal)
if (distMeters < 1.5) {
currentSpeed = 0;
el.hudSpeedVal.textContent = '0';
updateHudSpeedAndHeading(0, lastGpsHeading);
updateSpottedBadge(`Live GPS (±${accuracy}m) • Stopped`);
updateHudLocationStatus('Stopped • Monitoring Cameras');
checkProximityToCameras();
@@ -951,7 +1124,7 @@
// Smooth speed: responsive yet stable
currentSpeed = Math.round(0.7 * calculatedSpeedMph + 0.3 * currentSpeed);
if (currentSpeed < 1) currentSpeed = 0;
el.hudSpeedVal.textContent = currentSpeed;
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
// Calculate Heading / Compass Bearing
let targetHeading = lastGpsHeading;
@@ -1199,8 +1372,8 @@
map.panTo([currentPosition.lat, currentPosition.lng], { animate: false });
}
// Update Speedometer
el.hudSpeedVal.textContent = currentSpeed;
// Update Speedometer & Heading
updateHudSpeedAndHeading(currentSpeed, angle);
}
simAnimId = requestAnimationFrame(stepSimulation);
@@ -1318,7 +1491,7 @@
} else {
currentSpeed = 0;
}
el.hudSpeedVal.textContent = currentSpeed;
updateHudSpeedAndHeading(currentSpeed, heading);
if (userMarker) {
userMarker.setLatLng([lat, lng]);
@@ -1436,6 +1609,7 @@
${supporterBadge}
`;
}
updateVehicleSupporterAura();
}
function checkSupporterReturn() {
@@ -2135,19 +2309,13 @@
});
}
// Mute toggle
el.btnMuteSound.addEventListener('click', () => {
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user) {
user.soundEnabled = !user.soundEnabled;
window.FlockAuth.saveUser();
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);
}
});
// Sound Toggles (Both drawer button and top HUD capsule button)
if (el.btnMuteSound) {
el.btnMuteSound.addEventListener('click', toggleSound);
}
if (el.btnHudSoundToggle) {
el.btnHudSoundToggle.addEventListener('click', toggleSound);
}
// Report Camera FAB
if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal);