feat(v27): directional FOV approach filter, dynamic ETA countdown, windshield mirror HUD, stealth AMOLED night mode, 1-tap driving sighting stamp, audio boost and steering haptics

This commit is contained in:
2026-09-12 19:01:06 -07:00
parent 671c908c46
commit 4f8c67e6f6
4 changed files with 862 additions and 128 deletions
+502 -79
View File
@@ -112,6 +112,15 @@
let bgAudioElement = null;
let activeDetourLayerGroup = null;
// --- V27 DRIVER-CENTRIC POLISH STATE ---
let isHudMirrored = localStorage.getItem('flock_hud_mirror') === 'true';
let isStealthAmoled = localStorage.getItem('flock_stealth_amoled') === 'true';
let isAudioBoostEnabled = localStorage.getItem('flock_radar_audio_boost') === 'true';
let isHapticBoostEnabled = localStorage.getItem('flock_radar_haptic_boost') !== 'false';
let masterGainNode = null;
let savedQuickStamps = [];
let stampMarkers = [];
const SURVEILLANCE_CORRIDORS = {
'palmdale-blvd': {
name: 'Palmdale Blvd Corridor (SR-14 to 50th St E)',
@@ -406,7 +415,25 @@
btnGeneratePra: document.getElementById('btn-generate-pra'),
btnCopyPraLetter: document.getElementById('btn-copy-pra-letter'),
btnEmailPraLetter: document.getElementById('btn-email-pra-letter'),
praLetterText: document.getElementById('pra-letter-text')
praLetterText: document.getElementById('pra-letter-text'),
// Cockpit, FOV & Driver Polish (v27)
hudApproachBadge: document.getElementById('hud-approach-badge'),
hudEtaBox: document.getElementById('hud-eta-box'),
hudEtaVal: document.getElementById('hud-eta-val'),
hudEtaSub: document.getElementById('hud-eta-sub'),
btnQuickMarkDash: document.getElementById('btn-quick-mark-dash'),
btnMirrorHud: document.getElementById('btn-mirror-hud'),
mirrorHudLabel: document.getElementById('mirror-hud-label'),
btnStealthAmoled: document.getElementById('btn-stealth-amoled'),
stealthAmoledLabel: document.getElementById('stealth-amoled-label'),
btnToggleAudioBoost: document.getElementById('btn-toggle-audio-boost'),
audioBoostLabel: document.getElementById('audio-boost-label'),
btnToggleHapticBoost: document.getElementById('btn-toggle-haptic-boost'),
hapticBoostLabel: document.getElementById('haptic-boost-label'),
stampsCount: document.getElementById('stamps-count'),
stampsListContainer: document.getElementById('stamps-list-container'),
btnClearStamps: document.getElementById('btn-clear-stamps')
};
// --- INITIALIZATION ---
@@ -421,6 +448,13 @@
updateProfileDisplay();
checkSupporterReturn();
// Initialize v27 Driver Cockpit Preferences & Quick Stamps
applyHudMirror(isHudMirrored, false);
applyStealthAmoled(isStealthAmoled, false);
applyAudioBoost(isAudioBoostEnabled, false);
applyHapticBoost(isHapticBoostEnabled, false);
loadQuickStamps();
// Auto-detect real location immediately (Palmdale / GPS)
attemptAutoLocate();
@@ -780,6 +814,12 @@
function setupHud() {
updateHudSpeedAndHeading(0, null);
el.hudDistVal.textContent = '—';
if (el.hudEtaVal) el.hudEtaVal.textContent = '—';
if (el.hudEtaSub) el.hudEtaSub.textContent = 'IDLE';
if (el.hudApproachBadge) {
el.hudApproachBadge.className = 'hud-approach-badge approach-none';
el.hudApproachBadge.textContent = 'ALL CLEAR';
}
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';
@@ -788,6 +828,38 @@
updateVehicleSupporterAura();
}
// --- DIRECTIONAL TRAVEL FILTER & APPROACH ANGLE CLASSIFIER (v27) ---
function getBearing(lat1, lng1, lat2, lng2) {
const toRad = deg => deg * Math.PI / 180;
const toDeg = rad => rad * 180 / Math.PI;
const dLng = toRad(lng2 - lng1);
const lat1Rad = toRad(lat1);
const lat2Rad = toRad(lat2);
const y = Math.sin(dLng) * Math.cos(lat2Rad);
const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLng);
return (toDeg(Math.atan2(y, x)) + 360) % 360;
}
function classifyApproach(userLat, userLng, userHeading, camLat, camLng, speedMph) {
if (speedMph < 3 || userHeading === null || isNaN(userHeading)) {
return { type: 'proximity', label: 'RADAR PROXIMITY', angleDelta: 0, suppressAlert: false };
}
const camBearing = getBearing(userLat, userLng, camLat, camLng);
const angleDelta = Math.abs((userHeading - camBearing + 180) % 360 - 180);
if (angleDelta < 50) {
return { type: 'direct', label: 'DIRECT APPROACH', angleDelta, suppressAlert: false };
} else if (angleDelta <= 130) {
return { type: 'cross', label: 'CROSS-TRAFFIC', angleDelta, suppressAlert: false };
} else {
return { type: 'away', label: 'RECEDING / BEHIND', angleDelta, suppressAlert: true };
}
}
window.getBearing = getBearing;
window.classifyApproach = classifyApproach;
// --- PROXIMITY CALCULATION & ALERT ENGINE ---
function checkProximityToCameras() {
if (!allCameras.length) return;
@@ -809,7 +881,7 @@
// Check if inside zone radius
const insideZone = closestDistMeters <= (closestCam.radius || 300);
// Update HUD
// Update HUD distance display
if (distFt < 5280) {
el.hudDistVal.textContent = distFt > 1000 ? (distFt / 1000).toFixed(1) + 'k' : distFt;
el.hudDistSub.textContent = 'FT';
@@ -822,18 +894,55 @@
el.hudCamAgency.textContent = closestCam.name;
}
// Update Graduated Radar Signal Strength Meter (Valentine One Style)
updateRadarGauge(distFt, insideZone, closestCam);
const approach = closestCam
? classifyApproach(currentPosition.lat, currentPosition.lng, lastGpsHeading, closestCam.lat, closestCam.lng, currentSpeed)
: { type: 'proximity', label: 'ALL CLEAR', angleDelta: 0, suppressAlert: false };
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
// Update Directional Approach Angle Badge
if (el.hudApproachBadge) {
if (distFt > cautionThreshold || !closestCam) {
el.hudApproachBadge.className = 'hud-approach-badge approach-none';
el.hudApproachBadge.textContent = 'ALL CLEAR';
} else {
el.hudApproachBadge.className = `hud-approach-badge approach-${approach.type}`;
el.hudApproachBadge.textContent = approach.label;
}
}
// Dynamic Time-to-Zone Intercept Countdown
if (el.hudEtaVal && el.hudEtaSub) {
if (currentSpeed >= 4 && !approach.suppressAlert && distFt <= (radarRangeMode === 'highway' ? 3500 : 2000)) {
const speedMps = currentSpeed * 0.44704;
const etaSec = Math.round(closestDistMeters / speedMps);
if (etaSec <= 60) {
el.hudEtaVal.textContent = `~${etaSec}`;
el.hudEtaSub.textContent = 'SEC';
} else {
el.hudEtaVal.textContent = `~${(etaSec / 60).toFixed(1)}`;
el.hudEtaSub.textContent = 'MIN';
}
} else if (approach.suppressAlert && distFt <= cautionThreshold) {
el.hudEtaVal.textContent = '—';
el.hudEtaSub.textContent = 'AWAY';
} else {
el.hudEtaVal.textContent = '—';
el.hudEtaSub.textContent = 'IDLE';
}
}
// Update Graduated Radar Signal Strength Meter
updateRadarGauge(distFt, insideZone, closestCam, approach);
// Audio Proximity Geiger-Counter Chirps
handleProximityChirps(distFt);
handleProximityChirps(distFt, approach);
// State Transitions
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
if (insideZone) {
if (!isInZone) {
// Just entered zone! Trigger Alert!
triggerZoneAlert(closestCam, distFt);
triggerZoneAlert(closestCam, distFt, approach);
}
isInZone = true;
el.hudZoneBadge.className = 'hud-badge badge-danger';
@@ -843,14 +952,22 @@
<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>
<p>${escapeHtml(closestCam.agency)}${approach.label}</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`;
if (approach.suppressAlert) {
el.hudZoneBadge.className = 'hud-badge badge-clear';
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-clear"></span> RECEDING CAMERA`;
} else if (approach.type === 'cross') {
el.hudZoneBadge.className = 'hud-badge badge-warning';
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-warning"></span> CROSS-TRAFFIC CAMERA`;
} else {
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
@@ -862,7 +979,7 @@
}
// --- 3-STAGE GRADUATED RADAR DETECTOR SIGNAL GAUGE ---
function updateRadarGauge(distFt, insideZone, closestCam) {
function updateRadarGauge(distFt, insideZone, closestCam, approach) {
if (!el.hudGaugeState || !el.hudSignalMeter) return;
const isHighway = (radarRangeMode === 'highway');
@@ -874,6 +991,14 @@
b.className = 'signal-bar';
});
// If moving away from camera past immediate zone, show subdued reassurance
if (approach && approach.suppressAlert && distFt > 350) {
el.hudGaugeState.className = 'hud-gauge-status status-green';
el.hudGaugeState.textContent = `RECEDING (${distFt} FT)`;
bars[0].classList.add('active-green');
return;
}
if (insideZone || distFt <= 450) {
// Level 5: Red Alert In Zone
el.hudGaugeState.className = 'hud-gauge-status status-red';
@@ -882,7 +1007,9 @@
} else if (distFt <= warnDist) {
// Level 4: Immediate Proximity
el.hudGaugeState.className = 'hud-gauge-status status-red';
el.hudGaugeState.textContent = `ALERT: ${distFt} FT`;
el.hudGaugeState.textContent = (approach && approach.type === 'cross')
? `CROSS-TRAFFIC: ${distFt} FT`
: `ALERT: ${distFt} FT`;
bars[0].classList.add('active-red');
bars[1].classList.add('active-red');
bars[2].classList.add('active-red');
@@ -890,16 +1017,21 @@
} 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)`;
el.hudGaugeState.textContent = (approach && approach.type === 'cross')
? `CROSS-STREET (${Math.round(distFt / 100) * 100} FT)`
: `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
// Pre-alert announcement once per camera approach (only if not moving away)
if (closestCam && lastCautionSpokenCamId !== closestCam.id) {
lastCautionSpokenCamId = closestCam.id;
playCautionBeep();
speakVoiceAlert(`Caution. Approaching surveillance camera in ${Math.round(distFt / 100) * 100} feet.`);
if (!approach || !approach.suppressAlert) {
lastCautionSpokenCamId = closestCam.id;
playCautionBeep();
const approachDesc = (approach && approach.type === 'cross') ? 'cross-traffic ' : '';
speakVoiceAlert(`Caution. Approaching ${approachDesc}surveillance camera in ${Math.round(distFt / 100) * 100} feet.`);
}
}
} else if (distFt <= (cautionDist * 1.6)) {
// Level 2: Grid Range
@@ -919,27 +1051,53 @@
}
}
// --- AUDIO SYNTHESIZERS: CHIRPS & CAUTION BEEPS ---
// --- AUDIO SYNTHESIZERS & MASTER GAIN BOOSTER (v27) ---
function getAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
audioContext.resume();
}
if (!masterGainNode) {
masterGainNode = audioContext.createGain();
masterGainNode.gain.setValueAtTime(isAudioBoostEnabled ? 1.6 : 1.0, audioContext.currentTime);
masterGainNode.connect(audioContext.destination);
}
return audioContext;
}
function getAudioOutput() {
const ctx = getAudioContext();
return masterGainNode || ctx.destination;
}
function triggerHapticFeedback(pattern = [180, 70, 180, 70, 350]) {
if (!isHapticBoostEnabled) return;
if ('vibrate' in navigator) {
try {
navigator.vibrate(pattern);
} catch (e) {
// Ignored
}
}
}
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();
const ctx = getAudioContext();
const osc = ctx.createOscillator();
const gain = ctx.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.frequency.setValueAtTime(587.33, ctx.currentTime); // D5
osc.frequency.setValueAtTime(880, ctx.currentTime + 0.1); // A5
osc.frequency.setValueAtTime(1174.66, ctx.currentTime + 0.2); // D6
gain.gain.setValueAtTime(0.35, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(audioContext.destination);
gain.connect(getAudioOutput());
osc.start();
osc.stop(audioContext.currentTime + 0.4);
osc.stop(ctx.currentTime + 0.4);
} catch (e) {
console.warn('Log chirp error:', e);
}
@@ -949,22 +1107,17 @@
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();
const ctx = getAudioContext();
const osc = ctx.createOscillator();
const gain = ctx.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.frequency.setValueAtTime(520, ctx.currentTime);
gain.gain.setValueAtTime(0.25, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.22);
osc.connect(gain);
gain.connect(audioContext.destination);
gain.connect(getAudioOutput());
osc.start();
osc.stop(audioContext.currentTime + 0.22);
osc.stop(ctx.currentTime + 0.22);
} catch (e) {
console.warn('Caution beep error:', e);
}
@@ -1047,37 +1200,37 @@
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();
const ctx = getAudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 1.15, audioContext.currentTime + 0.045);
osc.frequency.setValueAtTime(freq, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 1.15, ctx.currentTime + 0.045);
gain.gain.setValueAtTime(volume, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.05);
gain.gain.setValueAtTime(volume, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05);
osc.connect(gain);
gain.connect(audioContext.destination);
gain.connect(getAudioOutput());
osc.start();
osc.stop(audioContext.currentTime + 0.05);
osc.stop(ctx.currentTime + 0.05);
} catch (e) {
// AudioContext policy
}
}
function handleProximityChirps(distFt) {
function handleProximityChirps(distFt, approach) {
if (radarAudioMode === 'silent') return;
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
if (user && !user.soundEnabled) return;
// Suppress repeated geiger chirps when driving away from camera past 350ft
if (approach && approach.suppressAlert && distFt > 350) {
return;
}
const maxChirpDist = (radarRangeMode === 'highway') ? 2200 : 1200;
if (distFt > maxChirpDist || distFt <= 0) {
stationaryChirpCount = 0;
@@ -1202,30 +1355,25 @@
lastChimeTime = now;
try {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
audioContext.resume();
}
const ctx = getAudioContext();
// Synthesize clean two-tone radar alert
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, audioContext.currentTime);
osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.12);
osc.frequency.setValueAtTime(880, audioContext.currentTime + 0.24);
osc.frequency.setValueAtTime(880, ctx.currentTime);
osc.frequency.setValueAtTime(659.25, ctx.currentTime + 0.12);
osc.frequency.setValueAtTime(880, ctx.currentTime + 0.24);
gain.gain.setValueAtTime(0.35, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.45);
gain.gain.setValueAtTime(0.35, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.45);
osc.connect(gain);
gain.connect(audioContext.destination);
gain.connect(getAudioOutput());
osc.start();
osc.stop(audioContext.currentTime + 0.45);
osc.stop(ctx.currentTime + 0.45);
} catch (e) {
console.warn('Audio chime error:', e);
}
@@ -1242,16 +1390,22 @@
const utter = new SpeechSynthesisUtterance(text);
utter.rate = 1.05;
utter.pitch = 1.0;
utter.volume = 1.0;
utter.volume = isAudioBoostEnabled ? 1.0 : 0.9;
window.speechSynthesis.speak(utter);
} catch (e) {
console.warn('Speech error:', e);
}
}
function triggerZoneAlert(cam, distFt) {
function triggerZoneAlert(cam, distFt, approach) {
if (approach && approach.suppressAlert) return;
playRadarChime();
speakVoiceAlert("Warning. Flock camera detected ahead. You are entering a recorded surveillance zone.");
triggerHapticFeedback([180, 70, 180, 70, 350]);
if (approach && approach.type === 'cross') {
speakVoiceAlert("Caution. Cross-traffic Flock camera active on perpendicular avenue.");
} else {
speakVoiceAlert("Warning. Flock camera detected ahead. You are entering a recorded surveillance zone.");
}
}
// --- VIRAL SHARING HELPER ---
@@ -2413,6 +2567,267 @@ Generated via FlockRadar Civic Transparency Network (flockradar.org)`;
}, 100);
}
// ==========================================================================
// v27 DRIVER-CENTRIC POLISH REFINEMENTS
// 1. Instant 1-Tap Driving Sighting Stamp
// 2. Windshield Reflection Mirror Mode
// 3. Stealth AMOLED True Black Mode
// 4. Cabin Audio Gain Booster (+6dB)
// 5. Steering Haptic Vibration Feedback
// ==========================================================================
// --- 1-TAP "MARK CAMERA WHILE DRIVING" INSTANT SIGHTING STAMP ---
function loadQuickStamps() {
try {
const stored = localStorage.getItem('flock_quick_stamps');
savedQuickStamps = stored ? JSON.parse(stored) : [];
} catch (e) {
savedQuickStamps = [];
}
renderStampedSightings();
renderExistingStampMarkers();
}
function saveQuickStamps() {
try {
localStorage.setItem('flock_quick_stamps', JSON.stringify(savedQuickStamps));
} catch (e) {
console.warn('Failed to save quick stamps', e);
}
renderStampedSightings();
}
function renderExistingStampMarkers() {
if (!map) return;
stampMarkers.forEach(m => map.removeLayer(m));
stampMarkers = [];
savedQuickStamps.forEach(s => {
const stampIcon = L.divIcon({
className: 'stamp-marker-icon',
html: `<div class="stamp-pulse-pin" style="width:24px; height:24px; font-size:12px;">⚡</div>`,
iconSize: [24, 24],
iconAnchor: [12, 12]
});
const marker = L.marker([s.lat, s.lng], { icon: stampIcon }).addTo(map);
marker.bindPopup(`
<div style="font-family:inherit; min-width:180px; font-size:0.8rem; color:#0f172a; padding:4px;">
<strong style="color:#d97706; display:flex; align-items:center; gap:4px;">
⚡ Stamped Sighting (Draft)
</strong>
<p style="margin:4px 0 6px 0; font-size:0.75rem; color:#475569;">
Logged at: <strong>${s.timeStr}</strong><br>
Speed: <strong>${s.speed} MPH</strong> • Heading: <strong>${s.heading}°</strong><br>
Coords: <code>${s.lat}, ${s.lng}</code>
</p>
<span style="font-size:0.72rem; color:#0284c7; font-weight:600;">Saved in Local Drafts</span>
</div>
`);
stampMarkers.push(marker);
});
}
function quickMarkAtGps() {
const lat = currentPosition.lat;
const lng = currentPosition.lng;
const speed = currentSpeed || 0;
const heading = lastGpsHeading || 0;
const now = new Date();
const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const stamp = {
id: 'stamp_' + Date.now(),
lat: Number(lat.toFixed(6)),
lng: Number(lng.toFixed(6)),
speed: Math.round(speed),
heading: Math.round(heading),
timestamp: now.toISOString(),
timeStr: timeStr
};
savedQuickStamps.unshift(stamp);
saveQuickStamps();
// Instant Audio Affirmation (Dual high-pitch chirp)
playLogConfirmationChirp();
// Haptic steering vibration pulse
triggerHapticFeedback([100, 50, 120]);
// Drop marker on Leaflet map
if (map) {
const stampIcon = L.divIcon({
className: 'stamp-marker-icon',
html: `<div class="stamp-pulse-pin" style="width:26px; height:26px;">⚡</div>`,
iconSize: [26, 26],
iconAnchor: [13, 13]
});
const marker = L.marker([stamp.lat, stamp.lng], { icon: stampIcon }).addTo(map);
marker.bindPopup(`
<div style="font-family:inherit; min-width:180px; font-size:0.8rem; color:#0f172a; padding:4px;">
<strong style="color:#d97706; display:flex; align-items:center; gap:4px;">
⚡ Stamped Sighting (Draft)
</strong>
<p style="margin:4px 0 6px 0; font-size:0.75rem; color:#475569;">
Logged at: <strong>${timeStr}</strong><br>
Speed: <strong>${speed} MPH</strong> • Heading: <strong>${heading}°</strong><br>
Coords: <code>${stamp.lat}, ${stamp.lng}</code>
</p>
<span style="font-size:0.72rem; color:#0284c7; font-weight:600;">Saved in Local Drafts</span>
</div>
`).openPopup();
stampMarkers.push(marker);
}
showToast(`⚡ Camera Sighting Stamped at ${stamp.lat}, ${stamp.lng}! Saved to drafts.`);
}
function clearAllStamps() {
if (!savedQuickStamps.length) return;
if (confirm('Clear all saved camera sighting stamps?')) {
savedQuickStamps = [];
saveQuickStamps();
stampMarkers.forEach(m => {
if (map && m) map.removeLayer(m);
});
stampMarkers = [];
showToast('Cleared all local camera stamps.');
}
}
function renderStampedSightings() {
if (el.stampsCount) {
el.stampsCount.textContent = savedQuickStamps.length;
}
if (!el.stampsListContainer) return;
if (!savedQuickStamps.length) {
el.stampsListContainer.innerHTML = `<p class="stamps-empty-hint">No quick stamps yet. Tap ⚡ Stamp while driving to record GPS instantly.</p>`;
return;
}
el.stampsListContainer.innerHTML = savedQuickStamps.map(s => `
<div class="stamp-item-card" data-id="${s.id}">
<div class="stamp-item-left">
<span class="stamp-badge-zap">⚡</span>
<div>
<strong>${s.timeStr}</strong> (${s.speed} MPH)
<div style="font-size:0.72rem; color:#94a3b8;">${s.lat.toFixed(4)}, ${s.lng.toFixed(4)}</div>
</div>
</div>
<button type="button" class="btn-mini-link" style="color:#38bdf8; background:none; border:none; cursor:pointer; font-size:0.72rem; font-weight:600;" onclick="window.zoomToStamp(${s.lat}, ${s.lng})">Zoom</button>
</div>
`).join('');
}
window.zoomToStamp = function(lat, lng) {
if (map) {
map.setView([lat, lng], 17, { animate: true });
showToast(`Zoomed to stamped camera at ${lat}, ${lng}`);
}
};
// --- WINDSHIELD REFLECTION MIRROR MODE ---
function applyHudMirror(enabled, showFeedback = true) {
isHudMirrored = enabled;
localStorage.setItem('flock_hud_mirror', enabled ? 'true' : 'false');
if (el.driverHud) {
el.driverHud.classList.toggle('mirror-hud', enabled);
}
if (el.btnMirrorHud) {
el.btnMirrorHud.classList.toggle('active', enabled);
}
if (el.mirrorHudLabel) {
el.mirrorHudLabel.textContent = enabled ? 'Windshield Mirror: ON' : 'Windshield Mirror: OFF';
}
if (showFeedback) {
if (enabled) {
showToast('🪞 Windshield HUD Mirror ON: Lay phone flat on dashboard under glass!');
} else {
showToast('🪞 Windshield HUD Mirror OFF');
}
}
}
function toggleMirrorHud() {
applyHudMirror(!isHudMirrored, true);
}
// --- STEALTH AMOLED NIGHT MODE (TRUE #000000) ---
function applyStealthAmoled(enabled, showFeedback = true) {
isStealthAmoled = enabled;
localStorage.setItem('flock_stealth_amoled', enabled ? 'true' : 'false');
document.body.classList.toggle('stealth-amoled', enabled);
if (el.btnStealthAmoled) {
el.btnStealthAmoled.classList.toggle('active', enabled);
}
if (el.stealthAmoledLabel) {
el.stealthAmoledLabel.textContent = enabled ? 'AMOLED Pitch Dark: ON' : 'AMOLED Pitch Dark: OFF';
}
if (showFeedback) {
if (enabled) {
showToast('🌙 AMOLED Stealth Mode Activated: Zero Cockpit Glare (True #000000)');
} else {
showToast('☀️ AMOLED Stealth Mode Deactivated');
}
}
}
function toggleStealthAmoled() {
applyStealthAmoled(!isStealthAmoled, true);
}
// --- CABIN AUDIO GAIN BOOSTER (+6dB) ---
function applyAudioBoost(enabled, showFeedback = true) {
isAudioBoostEnabled = enabled;
localStorage.setItem('flock_radar_audio_boost', enabled ? 'true' : 'false');
if (audioContext && masterGainNode) {
masterGainNode.gain.setValueAtTime(enabled ? 1.6 : 1.0, audioContext.currentTime);
}
if (el.btnToggleAudioBoost) {
el.btnToggleAudioBoost.classList.toggle('active', enabled);
}
if (el.audioBoostLabel) {
el.audioBoostLabel.textContent = enabled ? 'Audio Boost (+6dB): ON' : 'Audio Boost (+6dB): OFF';
}
if (showFeedback) {
playRadarTick(enabled ? 1100 : 700, 0.3);
if (enabled) {
showToast('🔊 Cabin Audio Boost (+6dB / 160%) ENABLED');
} else {
showToast('🔈 Audio Volume Set to Standard Level');
}
}
}
function toggleAudioBoost() {
applyAudioBoost(!isAudioBoostEnabled, true);
}
// --- HAPTIC STEERING VIBRATION FEEDBACK ---
function applyHapticBoost(enabled, showFeedback = true) {
isHapticBoostEnabled = enabled;
localStorage.setItem('flock_radar_haptic_boost', enabled ? 'true' : 'false');
if (el.btnToggleHapticBoost) {
el.btnToggleHapticBoost.classList.toggle('active', enabled);
}
if (el.hapticBoostLabel) {
el.hapticBoostLabel.textContent = enabled ? 'Steering Haptics: ON' : 'Steering Haptics: OFF';
}
if (showFeedback) {
if (enabled) {
triggerHapticFeedback([100, 50, 150]);
showToast('📳 Haptic Steering Vibration Alerts ENABLED');
} else {
showToast('📴 Steering Haptic Alerts Disabled');
}
}
}
function toggleHapticBoost() {
applyHapticBoost(!isHapticBoostEnabled, true);
}
// --- SURVEILLANCE ZONE HARDWARE PROOF MODAL ---
function openCameraDetailModal(cam) {
if (!cam) return;
@@ -2944,6 +3359,14 @@ Generated via FlockRadar Civic Transparency Network (flockradar.org)`;
if (el.btnSensCity) el.btnSensCity.addEventListener('click', () => setRadarSensitivity('city'));
if (el.btnSensHwy) el.btnSensHwy.addEventListener('click', () => setRadarSensitivity('highway'));
// v27 Driving Polish & Cockpit Listeners
if (el.btnQuickMarkDash) el.btnQuickMarkDash.addEventListener('click', quickMarkAtGps);
if (el.btnClearStamps) el.btnClearStamps.addEventListener('click', clearAllStamps);
if (el.btnMirrorHud) el.btnMirrorHud.addEventListener('click', toggleMirrorHud);
if (el.btnStealthAmoled) el.btnStealthAmoled.addEventListener('click', toggleStealthAmoled);
if (el.btnToggleAudioBoost) el.btnToggleAudioBoost.addEventListener('click', toggleAudioBoost);
if (el.btnToggleHapticBoost) el.btnToggleHapticBoost.addEventListener('click', toggleHapticBoost);
// Camera Hardware Proof & Voting Events
if (el.camDetailCloseBtn) el.camDetailCloseBtn.addEventListener('click', closeCameraDetailModal);