/* 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 + '
'
+ '
'
+ (meta ? '
' + _esc(meta) + '
' : '')
+ (desc ? '
' + desc + '
' : '')
+ (catHtml ? '
' + catHtml + '
' : '')
+ (reasons.length ? '
'
+ reasons.map(_esc).join(' · ') + '
' : '')
+ (safeUrl ? '
' : '')
+ '
';
}).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};
})();