diff --git a/static/css/components/signal-cards.css b/static/css/components/signal-cards.css index 283aeb6..2ccc275 100644 --- a/static/css/components/signal-cards.css +++ b/static/css/components/signal-cards.css @@ -1269,3 +1269,219 @@ color: var(--text-dim); font-style: italic; } + +/* ============================================ + Clickable Station Badge (APRS) + ============================================ */ + +.signal-station-clickable { + cursor: pointer; + transition: all 0.15s ease; + position: relative; +} + +.signal-station-clickable:hover { + background: var(--accent-purple); + color: #000; + transform: scale(1.05); + box-shadow: 0 0 8px rgba(138, 43, 226, 0.4); +} + +.signal-station-clickable:active { + transform: scale(0.98); +} + +.signal-station-clickable::after { + content: ''; + position: absolute; + bottom: -2px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent-purple); + transition: width 0.2s ease; +} + +.signal-station-clickable:hover::after { + width: 80%; +} + +/* ============================================ + Station Raw Data Modal + ============================================ */ + +.station-raw-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; +} + +.station-raw-modal.show { + opacity: 1; + visibility: visible; +} + +.station-raw-modal-backdrop { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); +} + +.station-raw-modal-content { + position: relative; + background: var(--panel-bg, #1a1a2e); + border: 1px solid var(--border-color, #333); + border-radius: 8px; + width: 90%; + max-width: 600px; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + transform: scale(0.95); + transition: transform 0.2s ease; +} + +.station-raw-modal.show .station-raw-modal-content { + transform: scale(1); +} + +.station-raw-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color, #333); + background: rgba(0, 0, 0, 0.2); + border-radius: 8px 8px 0 0; +} + +.station-raw-modal-title { + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + font-weight: 600; + color: var(--accent-cyan, #00d4ff); +} + +.station-raw-modal-close { + background: none; + border: none; + color: var(--text-muted, #888); + font-size: 24px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + transition: color 0.15s ease; +} + +.station-raw-modal-close:hover { + color: var(--accent-red, #ff4444); +} + +.station-raw-modal-body { + padding: 16px; + overflow-y: auto; + flex: 1; +} + +.station-raw-label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--text-muted, #888); + margin-bottom: 8px; +} + +.station-raw-data-display { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + line-height: 1.6; + color: var(--accent-green, #00ff88); + background: rgba(0, 0, 0, 0.3); + border: 1px solid var(--border-color, #333); + border-radius: 4px; + padding: 12px; + word-break: break-all; + white-space: pre-wrap; + margin: 0; + max-height: 300px; + overflow-y: auto; +} + +.station-raw-modal-footer { + display: flex; + justify-content: flex-end; + padding: 12px 16px; + border-top: 1px solid var(--border-color, #333); + background: rgba(0, 0, 0, 0.2); + border-radius: 0 0 8px 8px; +} + +.station-raw-copy-btn { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + padding: 8px 16px; + background: var(--accent-purple, #8a2be2); + border: none; + border-radius: 4px; + color: #fff; + cursor: pointer; + transition: all 0.15s ease; +} + +.station-raw-copy-btn:hover { + background: var(--accent-cyan, #00d4ff); + color: #000; +} + +/* ============================================ + SIGNAL GUESS INTEGRATION + ============================================ */ + +/* Signal guess badge in card header */ +.signal-card-badges .signal-guess-badge { + margin-left: 4px; +} + +/* Signal guess section in advanced panel */ +.signal-guess-section { + border-bottom: 1px solid var(--border-color); + padding-bottom: 12px; + margin-bottom: 12px; +} + +.signal-guess-section .signal-guess-container { + margin-top: 8px; +} + +/* Adjust guess label colors for dark theme */ +.signal-guess-section .signal-guess-label { + color: var(--text-primary, #e0e0e0); +} + +.signal-guess-section .signal-guess-tag { + background: var(--bg-tertiary, #2a2a2a); + color: var(--text-secondary, #888); +} + +.signal-guess-section .signal-guess-alt-item { + color: var(--text-secondary, #999); +} + +.signal-guess-section .signal-guess-popup-explanation { + color: var(--text-secondary, #aaa); +} diff --git a/static/js/components/activity-timeline.js b/static/js/components/activity-timeline.js index c0eeeb7..234b104 100644 --- a/static/js/components/activity-timeline.js +++ b/static/js/components/activity-timeline.js @@ -179,15 +179,15 @@ const ActivityTimeline = (function() { if (mode === 'rf' || mode === 'tscm' || mode === 'listening-post') { const f = parseFloat(id); if (!isNaN(f)) { - if (f >= 2400 && f <= 2500) return 'Wi-Fi 2.4GHz'; - if (f >= 5150 && f <= 5850) return 'Wi-Fi 5GHz'; - if (f >= 433 && f <= 434) return '433MHz ISM'; - if (f >= 868 && f <= 869) return '868MHz ISM'; - if (f >= 902 && f <= 928) return '915MHz ISM'; + if (f >= 2400 && f <= 2500) return '2.4 GHz wireless band'; + if (f >= 5150 && f <= 5850) return '5 GHz wireless band'; + if (f >= 433 && f <= 434) return '433 MHz low-power band'; + if (f >= 868 && f <= 869) return '868 MHz low-power band'; + if (f >= 902 && f <= 928) return '915 MHz low-power band'; if (f >= 315 && f <= 316) return '315MHz'; - if (f >= 2402 && f <= 2480) return 'Bluetooth'; - if (f >= 144 && f <= 148) return 'VHF Ham'; - if (f >= 420 && f <= 450) return 'UHF Ham'; + if (f >= 2402 && f <= 2480) return 'Bluetooth band'; + if (f >= 144 && f <= 148) return 'VHF amateur band'; + if (f >= 420 && f <= 450) return 'UHF amateur band'; return `${f.toFixed(3)} MHz`; } } @@ -231,7 +231,7 @@ const ActivityTimeline = (function() { item.firstSeen = now; state.items.set(id, item); - addAnnotation(state, 'new', `New: ${item.label}`, now); + addAnnotation(state, 'new', `New activity: ${item.label}`, now); } // Add event @@ -341,7 +341,7 @@ const ActivityTimeline = (function() { if (item.pattern !== patternStr) { item.pattern = patternStr; - addAnnotation(state, 'pattern', `Pattern: ${patternStr} - ${item.label}`, Date.now()); + addAnnotation(state, 'pattern', `Repeating pattern observed: ${patternStr} - ${item.label}`, Date.now()); } } } @@ -376,7 +376,7 @@ const ActivityTimeline = (function() { item.status = item.flagged ? 'flagged' : 'new'; addAnnotation(state, 'flagged', - item.flagged ? `Flagged: ${item.label}` : `Unflagged: ${item.label}`, + item.flagged ? `Marked for review: ${item.label}` : `Review mark removed: ${item.label}`, Date.now() ); @@ -398,7 +398,7 @@ const ActivityTimeline = (function() { const item = state.items.get(id); if (item && item.status !== 'gone') { item.status = 'gone'; - addAnnotation(state, 'gone', `Inactive: ${item.label}`, Date.now()); + addAnnotation(state, 'gone', `No longer active: ${item.label}`, Date.now()); } } @@ -479,7 +479,7 @@ const ActivityTimeline = (function() {