mirror of
https://github.com/smittix/intercept.git
synced 2026-07-29 11:08:10 -07:00
Add radar overlay on map, fix squawk button and airband status
- Add "Radar" toggle in display controls to overlay radar effect on map - Radar overlay shows sweep line, range rings, compass rose, center point - Fix squawk button using addEventListener instead of inline onclick - Add missing airbandStatus element to fix null error - Improve squawk modal with click-outside-to-close Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -440,11 +440,27 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#radarMap {
|
#radarMap {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#radarOverlayCanvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 500;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#radarOverlayCanvas.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
#radarScope {
|
#radarScope {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -1004,6 +1020,8 @@ body {
|
|||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#airbandSquelch {
|
#airbandSquelch {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<span class="strip-label">SESSION</span>
|
<span class="strip-label">SESSION</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="strip-divider"></div>
|
<div class="strip-divider"></div>
|
||||||
<button type="button" class="strip-btn" onclick="showSquawkReference()" title="Squawk Code Reference">
|
<button type="button" class="strip-btn" id="squawkBtn" title="Squawk Code Reference">
|
||||||
📟 Squawk
|
📟 Squawk
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="strip-btn" onclick="lookupSelectedFlight()" title="Lookup selected aircraft on FlightAware" id="flightLookupBtn" disabled>
|
<button type="button" class="strip-btn" onclick="lookupSelectedFlight()" title="Lookup selected aircraft on FlightAware" id="flightLookupBtn" disabled>
|
||||||
@@ -136,7 +136,9 @@
|
|||||||
<!-- Main Display (Map or Radar Scope) -->
|
<!-- Main Display (Map or Radar Scope) -->
|
||||||
<div class="main-display">
|
<div class="main-display">
|
||||||
<div class="display-container">
|
<div class="display-container">
|
||||||
<div id="radarMap"></div>
|
<div id="radarMap">
|
||||||
|
<canvas id="radarOverlayCanvas"></canvas>
|
||||||
|
</div>
|
||||||
<div id="radarScope">
|
<div id="radarScope">
|
||||||
<canvas id="radarCanvas"></canvas>
|
<canvas id="radarCanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,6 +190,7 @@
|
|||||||
<div class="control-group-items">
|
<div class="control-group-items">
|
||||||
<label title="Show aircraft trails"><input type="checkbox" id="showTrails" onchange="toggleTrails()"> Trails</label>
|
<label title="Show aircraft trails"><input type="checkbox" id="showTrails" onchange="toggleTrails()"> Trails</label>
|
||||||
<label title="Show range rings"><input type="checkbox" id="showRangeRings" checked onchange="drawRangeRings()"> Rings</label>
|
<label title="Show range rings"><input type="checkbox" id="showRangeRings" checked onchange="drawRangeRings()"> Rings</label>
|
||||||
|
<label title="Radar sweep overlay"><input type="checkbox" id="radarOverlay" onchange="toggleRadarOverlay()"> Radar</label>
|
||||||
<label title="Audio alerts"><input type="checkbox" id="alertToggle" checked onchange="toggleAlerts()"> Alerts</label>
|
<label title="Audio alerts"><input type="checkbox" id="alertToggle" checked onchange="toggleAlerts()"> Alerts</label>
|
||||||
<select id="aircraftFilter" onchange="applyFilter()" title="Filter aircraft">
|
<select id="aircraftFilter" onchange="applyFilter()" title="Filter aircraft">
|
||||||
<option value="all">All Aircraft</option>
|
<option value="all">All Aircraft</option>
|
||||||
@@ -259,6 +262,7 @@
|
|||||||
<input type="range" id="airbandVolume" min="0" max="100" value="80" class="airband-controls" oninput="updateAirbandVolume()" title="Volume">
|
<input type="range" id="airbandVolume" min="0" max="100" value="80" class="airband-controls" oninput="updateAirbandVolume()" title="Volume">
|
||||||
</div>
|
</div>
|
||||||
<button class="airband-btn" id="airbandBtn" onclick="toggleAirband()">▶ LISTEN</button>
|
<button class="airband-btn" id="airbandBtn" onclick="toggleAirband()">▶ LISTEN</button>
|
||||||
|
<span id="airbandStatus" class="airband-status">OFF</span>
|
||||||
<div class="airband-visualizer" id="airbandVisualizerContainer" style="display: none;">
|
<div class="airband-visualizer" id="airbandVisualizerContainer" style="display: none;">
|
||||||
<div class="signal-meter">
|
<div class="signal-meter">
|
||||||
<div class="meter-bar">
|
<div class="meter-bar">
|
||||||
@@ -1112,13 +1116,17 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
|||||||
// SQUAWK CODE REFERENCE
|
// SQUAWK CODE REFERENCE
|
||||||
// ============================================
|
// ============================================
|
||||||
function showSquawkReference() {
|
function showSquawkReference() {
|
||||||
|
// Remove existing modal if any
|
||||||
|
const existing = document.querySelector('.squawk-modal');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
const modal = document.createElement('div');
|
||||||
modal.className = 'squawk-modal';
|
modal.className = 'squawk-modal';
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="squawk-content">
|
<div class="squawk-content">
|
||||||
<div class="squawk-header">
|
<div class="squawk-header">
|
||||||
<h2>📟 Squawk Code Reference</h2>
|
<h2>📟 Squawk Code Reference</h2>
|
||||||
<button class="squawk-close" onclick="this.closest('.squawk-modal').remove()">×</button>
|
<button class="squawk-close" type="button">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="squawk-body">
|
<div class="squawk-body">
|
||||||
<div class="squawk-section emergency">
|
<div class="squawk-section emergency">
|
||||||
@@ -1160,6 +1168,14 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Close button handler
|
||||||
|
modal.querySelector('.squawk-close').addEventListener('click', () => modal.remove());
|
||||||
|
|
||||||
|
// Click outside to close
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) modal.remove();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -1525,6 +1541,159 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// RADAR OVERLAY ON MAP
|
||||||
|
// ============================================
|
||||||
|
let radarOverlayEnabled = false;
|
||||||
|
let radarOverlayAnimationId = null;
|
||||||
|
let radarOverlaySweepAngle = 0;
|
||||||
|
|
||||||
|
function toggleRadarOverlay() {
|
||||||
|
radarOverlayEnabled = document.getElementById('radarOverlay').checked;
|
||||||
|
const canvas = document.getElementById('radarOverlayCanvas');
|
||||||
|
|
||||||
|
if (radarOverlayEnabled) {
|
||||||
|
canvas.classList.add('active');
|
||||||
|
startRadarOverlayAnimation();
|
||||||
|
} else {
|
||||||
|
canvas.classList.remove('active');
|
||||||
|
stopRadarOverlayAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRadarOverlayAnimation() {
|
||||||
|
if (radarOverlayAnimationId) return;
|
||||||
|
|
||||||
|
const canvas = document.getElementById('radarOverlayCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
if (radarOverlayEnabled && currentView === 'map') {
|
||||||
|
drawRadarOverlay(canvas, ctx);
|
||||||
|
}
|
||||||
|
radarOverlayAnimationId = requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRadarOverlayAnimation() {
|
||||||
|
if (radarOverlayAnimationId) {
|
||||||
|
cancelAnimationFrame(radarOverlayAnimationId);
|
||||||
|
radarOverlayAnimationId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRadarOverlay(canvas, ctx) {
|
||||||
|
const container = canvas.parentElement;
|
||||||
|
const w = container.clientWidth;
|
||||||
|
const h = container.clientHeight;
|
||||||
|
|
||||||
|
// Resize canvas if needed
|
||||||
|
if (canvas.width !== w || canvas.height !== h) {
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
const centerX = w / 2;
|
||||||
|
const centerY = h / 2;
|
||||||
|
const radius = Math.min(w, h) / 2 - 20;
|
||||||
|
|
||||||
|
// Clear with transparency
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
// Draw range rings
|
||||||
|
ctx.strokeStyle = 'rgba(0, 255, 255, 0.15)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
const rings = [0.25, 0.5, 0.75, 1.0];
|
||||||
|
rings.forEach((ratio, i) => {
|
||||||
|
const r = radius * ratio;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, r, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Range label
|
||||||
|
const rangeNm = Math.round(maxRange * ratio);
|
||||||
|
ctx.fillStyle = 'rgba(0, 255, 255, 0.4)';
|
||||||
|
ctx.font = '10px JetBrains Mono';
|
||||||
|
ctx.fillText(`${rangeNm}nm`, centerX + r + 5, centerY);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw compass directions
|
||||||
|
ctx.fillStyle = 'rgba(0, 255, 255, 0.5)';
|
||||||
|
ctx.font = 'bold 14px Orbitron';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
const directions = [
|
||||||
|
{ angle: 0, label: 'N', x: centerX, y: centerY - radius - 12 },
|
||||||
|
{ angle: 90, label: 'E', x: centerX + radius + 12, y: centerY },
|
||||||
|
{ angle: 180, label: 'S', x: centerX, y: centerY + radius + 12 },
|
||||||
|
{ angle: 270, label: 'W', x: centerX - radius - 12, y: centerY }
|
||||||
|
];
|
||||||
|
directions.forEach(d => ctx.fillText(d.label, d.x, d.y));
|
||||||
|
|
||||||
|
// Draw tick marks every 30 degrees
|
||||||
|
ctx.strokeStyle = 'rgba(0, 255, 255, 0.2)';
|
||||||
|
for (let angle = 0; angle < 360; angle += 30) {
|
||||||
|
const rad = (angle - 90) * Math.PI / 180;
|
||||||
|
const inner = radius - 8;
|
||||||
|
const outer = radius + 3;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(centerX + inner * Math.cos(rad), centerY + inner * Math.sin(rad));
|
||||||
|
ctx.lineTo(centerX + outer * Math.cos(rad), centerY + outer * Math.sin(rad));
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sweep line
|
||||||
|
const sweepRad = (radarOverlaySweepAngle - 90) * Math.PI / 180;
|
||||||
|
const gradient = ctx.createLinearGradient(
|
||||||
|
centerX, centerY,
|
||||||
|
centerX + radius * Math.cos(sweepRad),
|
||||||
|
centerY + radius * Math.sin(sweepRad)
|
||||||
|
);
|
||||||
|
gradient.addColorStop(0, 'rgba(0, 255, 255, 0.6)');
|
||||||
|
gradient.addColorStop(1, 'rgba(0, 255, 255, 0.1)');
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(centerX, centerY);
|
||||||
|
ctx.lineTo(
|
||||||
|
centerX + radius * Math.cos(sweepRad),
|
||||||
|
centerY + radius * Math.sin(sweepRad)
|
||||||
|
);
|
||||||
|
ctx.strokeStyle = gradient;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw sweep arc (afterglow)
|
||||||
|
const startAngle = (radarOverlaySweepAngle - 90 - 40) * Math.PI / 180;
|
||||||
|
const endAngle = (radarOverlaySweepAngle - 90) * Math.PI / 180;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(centerX, centerY);
|
||||||
|
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
const arcGradient = ctx.createConicGradient(startAngle, centerX, centerY);
|
||||||
|
arcGradient.addColorStop(0, 'rgba(0, 255, 255, 0)');
|
||||||
|
arcGradient.addColorStop(1, 'rgba(0, 255, 255, 0.1)');
|
||||||
|
ctx.fillStyle = arcGradient;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw center point
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, 4, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 0, 0.8)';
|
||||||
|
ctx.fill();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, 6, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = 'rgba(255, 255, 0, 0.4)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Update sweep angle
|
||||||
|
radarOverlaySweepAngle = (radarOverlaySweepAngle + 1.5) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// VIEW TOGGLE
|
// VIEW TOGGLE
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -1820,6 +1989,9 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
|||||||
|
|
||||||
// Auto-connect to gpsd if available
|
// Auto-connect to gpsd if available
|
||||||
autoConnectGps();
|
autoConnectGps();
|
||||||
|
|
||||||
|
// Squawk button event listener
|
||||||
|
document.getElementById('squawkBtn').addEventListener('click', showSquawkReference);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track which device is being used for ADS-B tracking
|
// Track which device is being used for ADS-B tracking
|
||||||
|
|||||||
Reference in New Issue
Block a user