/** * Signal Cards Component * JavaScript utilities for creating and managing signal cards * Used across: Pager, APRS, Sensors, and other signal-based modes */ const SignalCards = (function() { 'use strict'; // Store for managing cards and state const state = { cards: new Map(), filters: { status: 'all', type: 'all' }, counts: { all: 0, emergency: 0, new: 0, burst: 0, repeated: 0, baseline: 0 } }; /** * Escape HTML to prevent XSS */ function escapeHtml(text) { if (text === null || text === undefined) return ''; const div = document.createElement('div'); div.textContent = String(text); return div.innerHTML; } /** * Format timestamp to relative time */ function formatRelativeTime(timestamp) { if (!timestamp) return ''; const date = new Date(timestamp); const now = new Date(); const diff = Math.floor((now - date) / 1000); if (diff < 60) return 'Just now'; if (diff < 3600) return Math.floor(diff / 60) + ' min ago'; if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; return date.toLocaleDateString(); } /** * Determine signal status based on message data */ function determineStatus(msg) { // Check for emergency indicators if (msg.emergency || (msg.message && /emergency|distress|mayday|sos/i.test(msg.message))) { return 'emergency'; } // Check if it's a new/first-seen signal if (msg.isNew || msg.firstSeen) { return 'new'; } // Check for burst activity if (msg.burst || msg.spike) { return 'burst'; } // Check for repeated pattern if (msg.repeated || msg.count > 5) { return 'repeated'; } // Default to baseline return 'baseline'; } /** * Get protocol class name */ function getProtoClass(protocol) { if (!protocol) return ''; const proto = protocol.toLowerCase(); if (proto.includes('pocsag')) return 'pocsag'; if (proto.includes('flex')) return 'flex'; if (proto.includes('aprs')) return 'aprs'; if (proto.includes('ais')) return 'ais'; if (proto.includes('acars')) return 'acars'; return ''; } /** * Check if message content is numeric */ function isNumericContent(message) { if (!message) return false; return /^[0-9\s\-\*\#U]+$/.test(message); } /** * Create a pager message card */ function createPagerCard(msg, options = {}) { const status = options.status || determineStatus(msg); const protoClass = getProtoClass(msg.protocol); const isNumeric = isNumericContent(msg.message); const relativeTime = formatRelativeTime(msg.timestamp); const isToneOnly = msg.message === '[Tone Only]' || msg.msg_type === 'Tone'; const card = document.createElement('article'); card.className = 'signal-card'; card.dataset.status = status; card.dataset.type = 'message'; card.dataset.protocol = protoClass; if (msg.address) card.dataset.address = msg.address; // Build card HTML card.innerHTML = `