@@ -1160,6 +1168,14 @@ ACARS: ${r.statistics.acarsMessages} messages`;
`;
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
// ============================================
@@ -1820,6 +1989,9 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// Auto-connect to gpsd if available
autoConnectGps();
+
+ // Squawk button event listener
+ document.getElementById('squawkBtn').addEventListener('click', showSquawkReference);
});
// Track which device is being used for ADS-B tracking