/* Signal identification modal — standalone IIFE component. * Usage: SignalIdModal.open({ frequency_mhz: 98.5, modulation: 'WFM' }) * SignalIdModal.open({}) // blank fields * SignalIdModal.close() */ window.SignalIdModal = (function () { 'use strict'; var _modal = null; var _backdrop = null; var _lastFreq = null; var _lastMod = null; function _esc(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function _safeSigIdUrl(url) { try { var p = new URL(String(url || '')); if (p.protocol === 'https:' && p.hostname.endsWith('sigidwiki.com')) return p.toString(); } catch (_) {} return null; } function _build() { if (_modal) return; _backdrop = document.createElement('div'); _backdrop.id = 'sigIdBackdrop'; _backdrop.style.cssText = [ 'position:fixed', 'inset:0', 'z-index:9998', 'background:rgba(0,0,0,0.65)', 'display:none', ].join(';'); _backdrop.addEventListener('click', close); _modal = document.createElement('div'); _modal.id = 'sigIdModal'; _modal.style.cssText = [ 'position:fixed', 'top:50%', 'left:50%', 'transform:translate(-50%,-50%)', 'z-index:9999', 'width:min(500px,95vw)', 'max-height:85vh', 'overflow-y:auto', 'background:var(--bg-card,#1a1a2e)', 'border:1px solid rgba(255,255,255,0.12)', 'border-radius:8px', 'padding:16px', 'display:none', 'font-family:var(--font-mono,monospace)', 'color:var(--text-primary,#e0e0e0)', 'box-sizing:border-box', ].join(';'); _modal.innerHTML = [ '
', '
Signal Identification
', ' ', '
', '
', '
', ' ', ' ', '
', '
', ' ', ' ', '
', '
', '
', '
', ' ', ' ', '
', ' ', '
', '
', '
', ].join(''); document.body.appendChild(_backdrop); document.body.appendChild(_modal); document.getElementById('sigIdClose').addEventListener('click', close); document.getElementById('sigIdSearch').addEventListener('click', search); document.getElementById('sigIdFreq').addEventListener('input', _validateFreq); } function _validateFreq() { var freq = document.getElementById('sigIdFreq'); var btn = document.getElementById('sigIdSearch'); if (!freq || !btn) return; var val = parseFloat(freq.value); var valid = isFinite(val) && val > 0; btn.disabled = !valid; freq.style.borderColor = (freq.value === '' || valid) ? 'rgba(255,255,255,0.15)' : 'var(--accent-red,#ff4444)'; } function _setStatus(text, isError) { var el = document.getElementById('sigIdStatus'); if (!el) return; el.textContent = text || ''; el.style.color = isError ? 'var(--accent-red,#ff4444)' : 'var(--text-muted,#888)'; } function _renderResults(matches, freqMhz) { var el = document.getElementById('sigIdResults'); if (!el) return; if (!matches.length) { el.innerHTML = '
' + 'No signals match ' + freqMhz.toFixed(4) + ' MHz' + ' — try adjusting the frequency or leaving bandwidth blank.
'; return; } el.innerHTML = matches.map(function (m, i) { var dot = i === 0 ? '●' : '○'; var score = Number(m.score) || 0; var name = _esc(m.name || 'Unknown'); var desc = _esc(m.description || ''); var cats = Array.isArray(m.categories) ? m.categories : []; var reasons = Array.isArray(m.match_reasons) ? m.match_reasons : []; var safeUrl = _safeSigIdUrl(m.sigidwiki_url); var ranges = Array.isArray(m.frequency_ranges) ? m.frequency_ranges : []; var mods = Array.isArray(m.modulations) ? m.modulations : []; var bw = m.bandwidth_range; var freqStr = ranges.length ? (ranges[0].min_hz / 1e6).toFixed(3) + '–' + (ranges[0].max_hz / 1e6).toFixed(3) + ' MHz' : ''; var bwStr = bw ? (bw.min_hz >= 1000 ? (bw.min_hz / 1000).toFixed(0) + 'k' : bw.min_hz) + '–' + (bw.max_hz >= 1000 ? (bw.max_hz / 1000).toFixed(0) + 'k' : bw.max_hz) + ' Hz' : ''; var modStr = mods.join(', '); var meta = [freqStr, modStr, bwStr].filter(Boolean).join(' · '); var catHtml = cats.slice(0, 5).map(function (c) { return '' + _esc(c) + ''; }).join(''); return '
' + '
' + '
' + dot + ' ' + name + '
' + '
' + '
' + '
' + '
' + score + '
' + '
' + (meta ? '
' + _esc(meta) + '
' : '') + (desc ? '
' + desc + '
' : '') + (catHtml ? '
' + catHtml + '
' : '') + (reasons.length ? '
' + reasons.map(_esc).join(' · ') + '
' : '') + (safeUrl ? '
' + '↗ View on SigID Wiki
' : '') + '
'; }).join(''); } function open(opts) { _build(); opts = opts || {}; var freqEl = document.getElementById('sigIdFreq'); var modEl = document.getElementById('sigIdMod'); var bwEl = document.getElementById('sigIdBw'); var resultsEl = document.getElementById('sigIdResults'); if (opts.frequency_mhz != null) { freqEl.value = Number(opts.frequency_mhz).toFixed(4); _lastFreq = Number(opts.frequency_mhz); } else { freqEl.value = ''; _lastFreq = null; } var modVal = (opts.modulation || '').toUpperCase(); modEl.value = modVal || ''; bwEl.value = ''; resultsEl.innerHTML = ''; _setStatus(''); _validateFreq(); _backdrop.style.display = 'block'; _modal.style.display = 'block'; if (!opts.frequency_mhz) { setTimeout(function () { freqEl.focus(); }, 50); } } function close() { if (!_modal) return; _modal.style.display = 'none'; _backdrop.style.display = 'none'; } function search() { var freqEl = document.getElementById('sigIdFreq'); var modEl = document.getElementById('sigIdMod'); var bwEl = document.getElementById('sigIdBw'); var searchBtn = document.getElementById('sigIdSearch'); var freq = parseFloat(freqEl.value); if (!isFinite(freq) || freq <= 0) return; var bwKhz = parseFloat(bwEl.value); var bwHz = (isFinite(bwKhz) && bwKhz > 0) ? Math.round(bwKhz * 1000) : null; var mod = (modEl.value || '').trim().toUpperCase() || null; _setStatus('Searching ' + freq.toFixed(4) + ' MHz…'); document.getElementById('sigIdResults').innerHTML = ''; searchBtn.disabled = true; fetch('/signalid/match', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ frequency_mhz: freq, bandwidth_hz: bwHz, modulation: mod, limit: 8, }), }) .then(function (r) { return r.json(); }) .then(function (data) { searchBtn.disabled = false; if (!data || data.status !== 'ok') { _setStatus('Search failed', true); return; } var matches = data.matches || []; if (matches.length) { _setStatus(matches.length + ' match' + (matches.length !== 1 ? 'es' : '') + ' for ' + freq.toFixed(4) + ' MHz'); } else { _setStatus(''); } _renderResults(matches, freq); }) .catch(function () { searchBtn.disabled = false; _setStatus('Search failed — check connection', true); document.getElementById('sigIdResults').innerHTML = '
' + 'Network error —
'; }); } return {open: open, close: close, search: search}; })();