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:
+96
-1
@@ -373,9 +373,51 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hud-capsule-actions {
|
.hud-capsule-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-toggle-tools {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
#btn-toggle-tools {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound-trigger {
|
||||||
|
padding: 6px 9px;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound-trigger:not(.is-muted) {
|
||||||
|
color: #38bdf8;
|
||||||
|
border-color: rgba(56, 189, 248, 0.4);
|
||||||
|
background: rgba(56, 189, 248, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound-trigger.is-muted {
|
||||||
|
color: #ef4444;
|
||||||
|
border-color: rgba(239, 68, 68, 0.45);
|
||||||
|
background: rgba(239, 68, 68, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.capsule-sound-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.capsule-sound-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hud-collapsible-content {
|
.hud-collapsible-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -486,10 +528,21 @@ body {
|
|||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
display: block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hud-compass-icon {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s ease;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.hud-location-bar {
|
.hud-location-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -844,6 +897,48 @@ body {
|
|||||||
transition: transform 0.2s linear;
|
transition: transform 0.2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* VIP Supporter Gold Radar Aura */
|
||||||
|
.user-vehicle-marker.vip-supporter .supporter-gold-aura {
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
left: -12px;
|
||||||
|
right: -12px;
|
||||||
|
bottom: -12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1.5px solid #f59e0b;
|
||||||
|
background: radial-gradient(circle, rgba(245, 158, 11, 0.25) 0%, rgba(245, 158, 11, 0.05) 60%, transparent 80%);
|
||||||
|
animation: vipSupporterPulse 2.2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-vehicle-marker.vip-supporter .car-body-dot {
|
||||||
|
background: linear-gradient(135deg, #f59e0b, #d97706) !important;
|
||||||
|
box-shadow: 0 0 16px rgba(245, 158, 11, 0.9), 0 0 28px rgba(245, 158, 11, 0.4) !important;
|
||||||
|
border-color: #fef08a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-vehicle-marker.vip-supporter .radar-scan-cone {
|
||||||
|
background: radial-gradient(circle at 50% 100%, rgba(245, 158, 11, 0.45), transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes vipSupporterPulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.85);
|
||||||
|
opacity: 0.95;
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1.6);
|
||||||
|
opacity: 0.25;
|
||||||
|
box-shadow: 0 0 0 14px rgba(245, 158, 11, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.radar-scan-cone {
|
.radar-scan-cone {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -15px;
|
top: -15px;
|
||||||
|
|||||||
+17
-8
@@ -54,7 +54,7 @@
|
|||||||
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
||||||
|
|
||||||
<!-- Main Stylesheet -->
|
<!-- Main Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=17">
|
<link rel="stylesheet" href="css/styles.css?v=18">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -162,7 +162,10 @@
|
|||||||
<span class="hud-metric-number" id="hud-speed-val">0</span>
|
<span class="hud-metric-number" id="hud-speed-val">0</span>
|
||||||
<span class="hud-metric-unit">MPH</span>
|
<span class="hud-metric-unit">MPH</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="hud-metric-label">Speed</span>
|
<span class="hud-metric-label" id="hud-speed-label">
|
||||||
|
<span id="hud-compass-needle" class="hud-compass-icon" title="Vehicle Heading Needle">▲</span>
|
||||||
|
<span id="hud-heading-val">IDLE</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hud-metric-box">
|
<div class="hud-metric-box">
|
||||||
@@ -174,8 +177,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Header Quick Triggers -->
|
<!-- Header Quick Triggers (Audio Toggle, Tools, Search) -->
|
||||||
<div class="hud-capsule-actions">
|
<div class="hud-capsule-actions">
|
||||||
|
<button type="button" id="btn-hud-sound-toggle" class="btn-capsule-trigger btn-sound-trigger" title="Toggle Audio Radar Alerts (Sound ON/OFF)">
|
||||||
|
<span id="hud-sound-icon">
|
||||||
|
<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>
|
||||||
|
</span>
|
||||||
|
<span class="capsule-sound-label">Audio</span>
|
||||||
|
</button>
|
||||||
<button type="button" id="btn-toggle-tools" class="btn-capsule-trigger" title="Open Driving Controls Menu">
|
<button type="button" id="btn-toggle-tools" class="btn-capsule-trigger" title="Open Driving Controls Menu">
|
||||||
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
<svg class="flock-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||||
<span class="capsule-btn-text">Tools</span>
|
<span class="capsule-btn-text">Tools</span>
|
||||||
@@ -1290,11 +1299,11 @@
|
|||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
|
||||||
<!-- Data & Application Logic with Cache Busting -->
|
<!-- Data & Application Logic with Cache Busting -->
|
||||||
<script src="data/portals.js?v=17"></script>
|
<script src="data/portals.js?v=18"></script>
|
||||||
<script src="js/icons.js?v=17"></script>
|
<script src="js/icons.js?v=18"></script>
|
||||||
<script src="js/auth.js?v=17"></script>
|
<script src="js/auth.js?v=18"></script>
|
||||||
<script src="js/radar.js?v=17"></script>
|
<script src="js/radar.js?v=18"></script>
|
||||||
<script src="js/app.js?v=17"></script>
|
<script src="js/app.js?v=18"></script>
|
||||||
|
|
||||||
<!-- Service Worker Instant Cache Invalidation -->
|
<!-- Service Worker Instant Cache Invalidation -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+190
-22
@@ -148,6 +148,7 @@
|
|||||||
btnSimDrive: document.getElementById('btn-sim-drive'),
|
btnSimDrive: document.getElementById('btn-sim-drive'),
|
||||||
btnResetSim: document.getElementById('btn-reset-sim'),
|
btnResetSim: document.getElementById('btn-reset-sim'),
|
||||||
btnMuteSound: document.getElementById('btn-mute-sound'),
|
btnMuteSound: document.getElementById('btn-mute-sound'),
|
||||||
|
btnHudSoundToggle: document.getElementById('btn-hud-sound-toggle'),
|
||||||
btnMobileConnect: document.getElementById('btn-mobile-connect'),
|
btnMobileConnect: document.getElementById('btn-mobile-connect'),
|
||||||
btnReportCameraFab: document.getElementById('btn-report-camera-fab'),
|
btnReportCameraFab: document.getElementById('btn-report-camera-fab'),
|
||||||
|
|
||||||
@@ -457,8 +458,11 @@
|
|||||||
|
|
||||||
// --- VEHICLE MARKER SETUP ---
|
// --- VEHICLE MARKER SETUP ---
|
||||||
function setupUserMarker() {
|
function setupUserMarker() {
|
||||||
|
const isSupporter = localStorage.getItem('flock_is_supporter') === 'true';
|
||||||
|
const supporterClass = isSupporter ? ' vip-supporter' : '';
|
||||||
const vehicleHtml = `
|
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="radar-scan-cone"></div>
|
||||||
<div class="car-body-dot">
|
<div class="car-body-dot">
|
||||||
<div class="car-arrow"></div>
|
<div class="car-arrow"></div>
|
||||||
@@ -479,13 +483,51 @@
|
|||||||
}).addTo(map);
|
}).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 ---
|
// --- HUD SETUP ---
|
||||||
function setupHud() {
|
function setupHud() {
|
||||||
el.hudSpeedVal.textContent = '0';
|
updateHudSpeedAndHeading(0, null);
|
||||||
el.hudDistVal.textContent = '—';
|
el.hudDistVal.textContent = '—';
|
||||||
el.hudZoneBadge.className = 'hud-badge badge-clear';
|
el.hudZoneBadge.className = 'hud-badge badge-clear';
|
||||||
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-clear"></span> ALL CLEAR`;
|
el.hudZoneBadge.innerHTML = `<span class="badge-dot dot-clear"></span> ALL CLEAR`;
|
||||||
el.hudCamAgency.textContent = 'Monitoring Highway Network';
|
el.hudCamAgency.textContent = 'Monitoring Highway Network';
|
||||||
|
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||||
|
if (user) updateSoundUi(user.soundEnabled);
|
||||||
|
updateVehicleSupporterAura();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PROXIMITY CALCULATION & ALERT ENGINE ---
|
// --- PROXIMITY CALCULATION & ALERT ENGINE ---
|
||||||
@@ -525,6 +567,9 @@
|
|||||||
// Update Graduated Radar Signal Strength Meter (Valentine One Style)
|
// Update Graduated Radar Signal Strength Meter (Valentine One Style)
|
||||||
updateRadarGauge(distFt, insideZone, closestCam);
|
updateRadarGauge(distFt, insideZone, closestCam);
|
||||||
|
|
||||||
|
// Audio Proximity Geiger-Counter Chirps
|
||||||
|
handleProximityChirps(distFt);
|
||||||
|
|
||||||
// State Transitions
|
// State Transitions
|
||||||
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
|
const cautionThreshold = (radarRangeMode === 'highway') ? 2500 : 1200;
|
||||||
if (insideZone) {
|
if (insideZone) {
|
||||||
@@ -735,6 +780,129 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- AUDIO SYNTHESIZER & SPOKEN VOICE ALERTS ---
|
// --- 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() {
|
function playRadarChime() {
|
||||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
||||||
if (user && !user.soundEnabled) return;
|
if (user && !user.soundEnabled) return;
|
||||||
@@ -797,8 +965,12 @@
|
|||||||
|
|
||||||
// --- TEST ALERT SOUND & VOICE NOW ---
|
// --- TEST ALERT SOUND & VOICE NOW ---
|
||||||
function testAlertSoundAndVoice() {
|
function testAlertSoundAndVoice() {
|
||||||
playRadarChime();
|
playRadarTick(950, 0.3);
|
||||||
speakVoiceAlert("Warning. Flock camera active zone ahead. You are being recorded.");
|
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.classList.add('visible');
|
||||||
el.hudBannerAlert.innerHTML = `
|
el.hudBannerAlert.innerHTML = `
|
||||||
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
|
||||||
@@ -897,6 +1069,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
el.hudSpeedVal.textContent = currentSpeed;
|
el.hudSpeedVal.textContent = currentSpeed;
|
||||||
|
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
|
||||||
updateSpottedBadge(`Live GPS (±${accuracy}m) • Locked`);
|
updateSpottedBadge(`Live GPS (±${accuracy}m) • Locked`);
|
||||||
updateHudLocationStatus('Live GPS Active');
|
updateHudLocationStatus('Live GPS Active');
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
@@ -924,7 +1097,7 @@
|
|||||||
// If movement is under 1.5m (GPS drift while stationary or stopped at traffic signal)
|
// If movement is under 1.5m (GPS drift while stationary or stopped at traffic signal)
|
||||||
if (distMeters < 1.5) {
|
if (distMeters < 1.5) {
|
||||||
currentSpeed = 0;
|
currentSpeed = 0;
|
||||||
el.hudSpeedVal.textContent = '0';
|
updateHudSpeedAndHeading(0, lastGpsHeading);
|
||||||
updateSpottedBadge(`Live GPS (±${accuracy}m) • Stopped`);
|
updateSpottedBadge(`Live GPS (±${accuracy}m) • Stopped`);
|
||||||
updateHudLocationStatus('Stopped • Monitoring Cameras');
|
updateHudLocationStatus('Stopped • Monitoring Cameras');
|
||||||
checkProximityToCameras();
|
checkProximityToCameras();
|
||||||
@@ -951,7 +1124,7 @@
|
|||||||
// Smooth speed: responsive yet stable
|
// Smooth speed: responsive yet stable
|
||||||
currentSpeed = Math.round(0.7 * calculatedSpeedMph + 0.3 * currentSpeed);
|
currentSpeed = Math.round(0.7 * calculatedSpeedMph + 0.3 * currentSpeed);
|
||||||
if (currentSpeed < 1) currentSpeed = 0;
|
if (currentSpeed < 1) currentSpeed = 0;
|
||||||
el.hudSpeedVal.textContent = currentSpeed;
|
updateHudSpeedAndHeading(currentSpeed, lastGpsHeading);
|
||||||
|
|
||||||
// Calculate Heading / Compass Bearing
|
// Calculate Heading / Compass Bearing
|
||||||
let targetHeading = lastGpsHeading;
|
let targetHeading = lastGpsHeading;
|
||||||
@@ -1199,8 +1372,8 @@
|
|||||||
map.panTo([currentPosition.lat, currentPosition.lng], { animate: false });
|
map.panTo([currentPosition.lat, currentPosition.lng], { animate: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Speedometer
|
// Update Speedometer & Heading
|
||||||
el.hudSpeedVal.textContent = currentSpeed;
|
updateHudSpeedAndHeading(currentSpeed, angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
simAnimId = requestAnimationFrame(stepSimulation);
|
simAnimId = requestAnimationFrame(stepSimulation);
|
||||||
@@ -1318,7 +1491,7 @@
|
|||||||
} else {
|
} else {
|
||||||
currentSpeed = 0;
|
currentSpeed = 0;
|
||||||
}
|
}
|
||||||
el.hudSpeedVal.textContent = currentSpeed;
|
updateHudSpeedAndHeading(currentSpeed, heading);
|
||||||
|
|
||||||
if (userMarker) {
|
if (userMarker) {
|
||||||
userMarker.setLatLng([lat, lng]);
|
userMarker.setLatLng([lat, lng]);
|
||||||
@@ -1436,6 +1609,7 @@
|
|||||||
${supporterBadge}
|
${supporterBadge}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
updateVehicleSupporterAura();
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkSupporterReturn() {
|
function checkSupporterReturn() {
|
||||||
@@ -2135,19 +2309,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mute toggle
|
// Sound Toggles (Both drawer button and top HUD capsule button)
|
||||||
el.btnMuteSound.addEventListener('click', () => {
|
if (el.btnMuteSound) {
|
||||||
const user = window.FlockAuth ? window.FlockAuth.getUser() : null;
|
el.btnMuteSound.addEventListener('click', toggleSound);
|
||||||
if (user) {
|
}
|
||||||
user.soundEnabled = !user.soundEnabled;
|
if (el.btnHudSoundToggle) {
|
||||||
window.FlockAuth.saveUser();
|
el.btnHudSoundToggle.addEventListener('click', toggleSound);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Report Camera FAB
|
// Report Camera FAB
|
||||||
if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal);
|
if (el.btnReportCameraFab) el.btnReportCameraFab.addEventListener('click', openReportModal);
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// Service Worker for FlockRadar Offline Driving & Live Updates
|
// Service Worker for FlockRadar Offline Driving & Live Updates
|
||||||
const CACHE_NAME = 'flockradar-v17';
|
const CACHE_NAME = 'flockradar-v18';
|
||||||
const ASSETS_TO_CACHE = [
|
const ASSETS_TO_CACHE = [
|
||||||
'./',
|
'./',
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./css/styles.css?v=17',
|
'./css/styles.css?v=18',
|
||||||
'./js/icons.js?v=17',
|
'./js/icons.js?v=18',
|
||||||
'./js/app.js?v=17',
|
'./js/app.js?v=18',
|
||||||
'./js/auth.js?v=17',
|
'./js/auth.js?v=18',
|
||||||
'./js/radar.js?v=17',
|
'./js/radar.js?v=18',
|
||||||
'./data/portals.js?v=17',
|
'./data/portals.js?v=18',
|
||||||
'./favicon.svg?v=17',
|
'./favicon.svg?v=18',
|
||||||
'./favicon.ico?v=17',
|
'./favicon.ico?v=18',
|
||||||
'./apple-touch-icon.png?v=17',
|
'./apple-touch-icon.png?v=18',
|
||||||
'./icon-192.png?v=17',
|
'./icon-192.png?v=18',
|
||||||
'./icon-512.png?v=17',
|
'./icon-512.png?v=18',
|
||||||
'./og-image.png?v=17',
|
'./og-image.png?v=18',
|
||||||
'./assets/img/logo.svg',
|
'./assets/img/logo.svg',
|
||||||
'./assets/img/flock_pole_cam_palmdale.jpg',
|
'./assets/img/flock_pole_cam_palmdale.jpg',
|
||||||
'./manifest.json'
|
'./manifest.json'
|
||||||
|
|||||||
Reference in New Issue
Block a user