Use Terminus font across app

This commit is contained in:
Smittix
2026-02-04 00:56:22 +00:00
parent 8f3c636c61
commit 50227ccae6
37 changed files with 22210 additions and 22184 deletions

View File

@@ -132,7 +132,7 @@ body {
display: flex;
gap: 20px;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -624,7 +624,7 @@ body {
}
.telemetry-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
color: var(--accent-cyan);
}
@@ -680,7 +680,7 @@ body {
}
.aircraft-icao {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-secondary);
background: rgba(74, 158, 255, 0.1);
@@ -700,7 +700,7 @@ body {
}
.aircraft-detail-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
color: var(--accent-cyan);
font-size: 11px;
}
@@ -790,7 +790,7 @@ body {
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -801,7 +801,7 @@ body {
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -879,7 +879,7 @@ body {
border: none;
background: var(--accent-green);
color: #fff;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
@@ -911,7 +911,7 @@ body {
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
cursor: pointer;
}
@@ -1023,7 +1023,7 @@ body {
cursor: pointer;
font-size: 11px;
font-weight: 600;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
display: flex;
align-items: center;
gap: 5px;
@@ -1057,7 +1057,7 @@ body {
}
.airband-status {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
padding: 0 8px;
color: var(--text-muted);
@@ -1407,7 +1407,7 @@ body {
}
.strip-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
@@ -1545,7 +1545,7 @@ body {
.report-grid span:nth-child(even) {
color: var(--text-primary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.report-highlights {
@@ -1784,7 +1784,7 @@ body {
font-size: 11px;
font-weight: 500;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
padding-left: 8px;
border-left: 1px solid rgba(74, 158, 255, 0.2);
white-space: nowrap;
@@ -1938,7 +1938,7 @@ body {
}
.squawk-code {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-weight: 700;
color: var(--accent-cyan);
font-size: 12px;

View File

@@ -27,7 +27,7 @@ body {
}
.mono {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.radar-bg {
@@ -91,7 +91,7 @@ body {
display: flex;
align-items: center;
gap: 12px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -268,7 +268,7 @@ body {
}
.status-pill {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
padding: 8px 12px;
border-radius: 999px;
@@ -306,7 +306,7 @@ body {
}
.panel-meta {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
}
@@ -347,7 +347,7 @@ body {
}
.mono {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.empty-row td,

View File

@@ -1,343 +1,343 @@
/*
* Agents Management CSS
* Styles for the remote agent management interface
*/
/* CSS Variables (inherited from main theme) */
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--text-primary: #e0e0e0;
--text-secondary: #888;
--border-color: #1a1a2e;
--accent-cyan: #00d4ff;
--accent-green: #00ff88;
--accent-red: #ff3366;
--accent-orange: #ff9f1c;
}
/* Agent indicator in navigation */
.agent-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 20px;
cursor: pointer;
transition: all 0.2s;
}
.agent-indicator:hover {
background: rgba(0, 212, 255, 0.2);
border-color: var(--accent-cyan);
}
.agent-indicator-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent-green);
box-shadow: 0 0 6px var(--accent-green);
}
.agent-indicator-dot.remote {
background: var(--accent-cyan);
box-shadow: 0 0 6px var(--accent-cyan);
}
.agent-indicator-dot.multiple {
background: var(--accent-orange);
box-shadow: 0 0 6px var(--accent-orange);
}
.agent-indicator-label {
font-size: 11px;
color: var(--text-primary);
font-family: 'JetBrains Mono', monospace;
}
.agent-indicator-count {
font-size: 10px;
padding: 2px 6px;
background: rgba(0, 212, 255, 0.2);
border-radius: 10px;
color: var(--accent-cyan);
}
/* Agent selector dropdown */
.agent-selector {
position: relative;
}
.agent-selector-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
min-width: 280px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
z-index: 1000;
display: none;
}
.agent-selector-dropdown.show {
display: block;
}
.agent-selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid var(--border-color);
}
.agent-selector-header h4 {
margin: 0;
font-size: 12px;
color: var(--accent-cyan);
text-transform: uppercase;
letter-spacing: 1px;
}
.agent-selector-manage {
font-size: 11px;
color: var(--accent-cyan);
text-decoration: none;
}
.agent-selector-manage:hover {
text-decoration: underline;
}
.agent-selector-list {
max-height: 300px;
overflow-y: auto;
}
.agent-selector-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
cursor: pointer;
transition: background 0.2s;
border-bottom: 1px solid var(--border-color);
}
.agent-selector-item:last-child {
border-bottom: none;
}
.agent-selector-item:hover {
background: rgba(0, 212, 255, 0.1);
}
.agent-selector-item.selected {
background: rgba(0, 212, 255, 0.15);
border-left: 3px solid var(--accent-cyan);
}
.agent-selector-item.local {
border-left: 3px solid var(--accent-green);
}
.agent-selector-item-status {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.agent-selector-item-status.online {
background: var(--accent-green);
}
.agent-selector-item-status.offline {
background: var(--accent-red);
}
.agent-selector-item-info {
flex: 1;
min-width: 0;
}
.agent-selector-item-name {
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-selector-item-url {
font-size: 10px;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-selector-item-check {
color: var(--accent-green);
opacity: 0;
}
.agent-selector-item.selected .agent-selector-item-check {
opacity: 1;
}
/* Agent badge in data displays */
.agent-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 10px;
background: rgba(0, 212, 255, 0.1);
color: var(--accent-cyan);
border-radius: 10px;
font-family: 'JetBrains Mono', monospace;
}
.agent-badge.local,
.agent-badge.agent-local {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
}
.agent-badge.agent-remote {
background: rgba(0, 212, 255, 0.1);
color: var(--accent-cyan);
}
/* WiFi table agent column */
.wifi-networks-table .col-agent {
width: 100px;
text-align: center;
}
.wifi-networks-table th.col-agent {
font-size: 10px;
}
/* Bluetooth table agent column */
.bt-devices-table .col-agent {
width: 100px;
text-align: center;
}
.agent-badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
/* Agent column in data tables */
.data-table .agent-col {
width: 120px;
max-width: 120px;
}
/* Multi-agent stream indicator */
.multi-agent-indicator {
position: fixed;
bottom: 20px;
left: 20px;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 20px;
font-size: 11px;
color: var(--text-secondary);
z-index: 100;
}
.multi-agent-indicator.active {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.multi-agent-indicator-pulse {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent-cyan);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
/* Agent connection status toast */
.agent-toast {
position: fixed;
top: 80px;
right: 20px;
padding: 10px 15px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
z-index: 1001;
animation: slideInRight 0.3s ease;
}
.agent-toast.connected {
border-color: var(--accent-green);
color: var(--accent-green);
}
.agent-toast.disconnected {
border-color: var(--accent-red);
color: var(--accent-red);
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.agent-indicator {
padding: 4px 8px;
}
.agent-indicator-label {
display: none;
}
.agent-selector-dropdown {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
margin: 0;
border-radius: 16px 16px 0 0;
max-height: 60vh;
}
.agents-grid {
grid-template-columns: 1fr;
}
}
/*
* Agents Management CSS
* Styles for the remote agent management interface
*/
/* CSS Variables (inherited from main theme) */
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--text-primary: #e0e0e0;
--text-secondary: #888;
--border-color: #1a1a2e;
--accent-cyan: #00d4ff;
--accent-green: #00ff88;
--accent-red: #ff3366;
--accent-orange: #ff9f1c;
}
/* Agent indicator in navigation */
.agent-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 20px;
cursor: pointer;
transition: all 0.2s;
}
.agent-indicator:hover {
background: rgba(0, 212, 255, 0.2);
border-color: var(--accent-cyan);
}
.agent-indicator-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent-green);
box-shadow: 0 0 6px var(--accent-green);
}
.agent-indicator-dot.remote {
background: var(--accent-cyan);
box-shadow: 0 0 6px var(--accent-cyan);
}
.agent-indicator-dot.multiple {
background: var(--accent-orange);
box-shadow: 0 0 6px var(--accent-orange);
}
.agent-indicator-label {
font-size: 11px;
color: var(--text-primary);
font-family: var(--font-mono);
}
.agent-indicator-count {
font-size: 10px;
padding: 2px 6px;
background: rgba(0, 212, 255, 0.2);
border-radius: 10px;
color: var(--accent-cyan);
}
/* Agent selector dropdown */
.agent-selector {
position: relative;
}
.agent-selector-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
min-width: 280px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
z-index: 1000;
display: none;
}
.agent-selector-dropdown.show {
display: block;
}
.agent-selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid var(--border-color);
}
.agent-selector-header h4 {
margin: 0;
font-size: 12px;
color: var(--accent-cyan);
text-transform: uppercase;
letter-spacing: 1px;
}
.agent-selector-manage {
font-size: 11px;
color: var(--accent-cyan);
text-decoration: none;
}
.agent-selector-manage:hover {
text-decoration: underline;
}
.agent-selector-list {
max-height: 300px;
overflow-y: auto;
}
.agent-selector-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
cursor: pointer;
transition: background 0.2s;
border-bottom: 1px solid var(--border-color);
}
.agent-selector-item:last-child {
border-bottom: none;
}
.agent-selector-item:hover {
background: rgba(0, 212, 255, 0.1);
}
.agent-selector-item.selected {
background: rgba(0, 212, 255, 0.15);
border-left: 3px solid var(--accent-cyan);
}
.agent-selector-item.local {
border-left: 3px solid var(--accent-green);
}
.agent-selector-item-status {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.agent-selector-item-status.online {
background: var(--accent-green);
}
.agent-selector-item-status.offline {
background: var(--accent-red);
}
.agent-selector-item-info {
flex: 1;
min-width: 0;
}
.agent-selector-item-name {
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-selector-item-url {
font-size: 10px;
color: var(--text-secondary);
font-family: var(--font-mono);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-selector-item-check {
color: var(--accent-green);
opacity: 0;
}
.agent-selector-item.selected .agent-selector-item-check {
opacity: 1;
}
/* Agent badge in data displays */
.agent-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 10px;
background: rgba(0, 212, 255, 0.1);
color: var(--accent-cyan);
border-radius: 10px;
font-family: var(--font-mono);
}
.agent-badge.local,
.agent-badge.agent-local {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
}
.agent-badge.agent-remote {
background: rgba(0, 212, 255, 0.1);
color: var(--accent-cyan);
}
/* WiFi table agent column */
.wifi-networks-table .col-agent {
width: 100px;
text-align: center;
}
.wifi-networks-table th.col-agent {
font-size: 10px;
}
/* Bluetooth table agent column */
.bt-devices-table .col-agent {
width: 100px;
text-align: center;
}
.agent-badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
/* Agent column in data tables */
.data-table .agent-col {
width: 120px;
max-width: 120px;
}
/* Multi-agent stream indicator */
.multi-agent-indicator {
position: fixed;
bottom: 20px;
left: 20px;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 20px;
font-size: 11px;
color: var(--text-secondary);
z-index: 100;
}
.multi-agent-indicator.active {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.multi-agent-indicator-pulse {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent-cyan);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
/* Agent connection status toast */
.agent-toast {
position: fixed;
top: 80px;
right: 20px;
padding: 10px 15px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
z-index: 1001;
animation: slideInRight 0.3s ease;
}
.agent-toast.connected {
border-color: var(--accent-green);
color: var(--accent-green);
}
.agent-toast.disconnected {
border-color: var(--accent-red);
color: var(--accent-red);
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.agent-indicator {
padding: 4px 8px;
}
.agent-indicator-label {
display: none;
}
.agent-selector-dropdown {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
margin: 0;
border-radius: 16px 16px 0 0;
max-height: 60vh;
}
.agents-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -134,7 +134,7 @@ body {
display: flex;
gap: 20px;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -183,7 +183,7 @@ body {
}
.strip-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
@@ -287,7 +287,7 @@ body {
font-size: 11px;
font-weight: 500;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
padding-left: 8px;
border-left: 1px solid rgba(74, 158, 255, 0.2);
white-space: nowrap;
@@ -367,7 +367,7 @@ body {
/* Leaflet overrides - Dark map styling */
.leaflet-container {
background: var(--bg-dark) !important;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
/* Using actual dark tiles now - no filter needed */
@@ -438,7 +438,7 @@ body {
padding: 10px 15px;
background: rgba(74, 158, 255, 0.05);
border-bottom: 1px solid rgba(74, 158, 255, 0.1);
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 11px;
font-weight: 500;
letter-spacing: 2px;
@@ -510,7 +510,7 @@ body {
}
.vessel-name {
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 16px;
font-weight: 700;
color: var(--accent-cyan);
@@ -518,7 +518,7 @@ body {
}
.vessel-mmsi {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-secondary);
background: rgba(74, 158, 255, 0.1);
@@ -548,7 +548,7 @@ body {
}
.detail-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
color: var(--accent-cyan);
}
@@ -604,20 +604,20 @@ body {
}
.vessel-item-name {
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 12px;
font-weight: 600;
color: var(--accent-cyan);
}
.vessel-item-type {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-secondary);
}
.vessel-item-speed {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
text-align: right;
@@ -687,7 +687,7 @@ body {
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -698,7 +698,7 @@ body {
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -717,7 +717,7 @@ body {
border: none;
background: var(--accent-green);
color: #fff;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
@@ -1004,7 +1004,7 @@ body {
padding: 6px 12px;
background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid rgba(245, 158, 11, 0.1);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
}
@@ -1079,7 +1079,7 @@ body {
}
.dsc-message-category {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
@@ -1096,13 +1096,13 @@ body {
}
.dsc-message-time {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-dim);
}
.dsc-message-mmsi {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-orange);
}
@@ -1120,7 +1120,7 @@ body {
}
.dsc-message-pos {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-secondary);
}
@@ -1148,7 +1148,7 @@ body {
}
.dsc-distress-alert .dsc-alert-header {
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 24px;
font-weight: 700;
color: var(--accent-red);
@@ -1157,7 +1157,7 @@ body {
}
.dsc-distress-alert .dsc-alert-mmsi {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 16px;
color: var(--accent-cyan);
margin-bottom: 8px;
@@ -1177,7 +1177,7 @@ body {
}
.dsc-distress-alert .dsc-alert-position {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
color: var(--accent-cyan);
margin-bottom: 16px;
@@ -1188,7 +1188,7 @@ body {
border: none;
color: white;
padding: 10px 24px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,371 +1,371 @@
/* Function Strip (Action Bar) - Shared across modes
* Based on APRS strip pattern, reusable for Pager, Sensor, Bluetooth, WiFi, TSCM, etc.
*/
.function-strip {
background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px 12px;
margin-bottom: 10px;
overflow: visible;
min-height: 44px;
}
.function-strip-inner {
display: flex;
align-items: center;
gap: 8px;
min-width: max-content;
}
/* Stats */
.function-strip .strip-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 10px;
background: rgba(74, 158, 255, 0.05);
border: 1px solid rgba(74, 158, 255, 0.15);
border-radius: 4px;
min-width: 55px;
}
.function-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.function-strip .strip-value {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
line-height: 1.2;
}
.function-strip .strip-label {
font-size: 8px;
font-weight: 600;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 1px;
}
.function-strip .strip-divider {
width: 1px;
height: 28px;
background: var(--border-color);
margin: 0 4px;
}
/* Signal stat coloring */
.function-strip .signal-stat.good .strip-value { color: var(--accent-green); }
.function-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
.function-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
/* Controls */
.function-strip .strip-control {
display: flex;
align-items: center;
gap: 4px;
}
.function-strip .strip-select {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
cursor: pointer;
}
.function-strip .strip-select:hover {
border-color: var(--accent-cyan);
}
.function-strip .strip-select:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.function-strip .strip-input-label {
font-size: 9px;
color: var(--text-muted);
font-weight: 600;
}
.function-strip .strip-input {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 6px;
border-radius: 4px;
font-size: 10px;
width: 50px;
text-align: center;
}
.function-strip .strip-input:hover,
.function-strip .strip-input:focus {
border-color: var(--accent-cyan);
outline: none;
}
.function-strip .strip-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Wider input for frequency values */
.function-strip .strip-input.wide {
width: 70px;
}
/* Tool Status Indicators */
.function-strip .strip-tools {
display: flex;
gap: 4px;
}
.function-strip .strip-tool {
font-size: 9px;
font-weight: 600;
padding: 3px 6px;
border-radius: 3px;
background: rgba(255, 59, 48, 0.2);
color: var(--accent-red);
border: 1px solid rgba(255, 59, 48, 0.3);
}
.function-strip .strip-tool.ok {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
border-color: rgba(0, 255, 136, 0.3);
}
.function-strip .strip-tool.warn {
background: rgba(255, 193, 7, 0.2);
color: var(--accent-yellow);
border-color: rgba(255, 193, 7, 0.3);
}
/* Buttons */
.function-strip .strip-btn {
background: rgba(74, 158, 255, 0.1);
border: 1px solid rgba(74, 158, 255, 0.2);
color: var(--text-primary);
padding: 6px 12px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.function-strip .strip-btn:hover:not(:disabled) {
background: rgba(74, 158, 255, 0.2);
border-color: rgba(74, 158, 255, 0.4);
}
.function-strip .strip-btn.primary {
background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
border: none;
color: #000;
}
.function-strip .strip-btn.primary:hover:not(:disabled) {
filter: brightness(1.1);
}
.function-strip .strip-btn.stop {
background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
border: none;
color: #fff;
}
.function-strip .strip-btn.stop:hover:not(:disabled) {
filter: brightness(1.1);
}
.function-strip .strip-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Status indicator */
.function-strip .strip-status {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
font-size: 10px;
font-weight: 600;
color: var(--text-secondary);
}
.function-strip .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
}
.function-strip .status-dot.inactive {
background: var(--text-muted);
}
.function-strip .status-dot.active,
.function-strip .status-dot.scanning,
.function-strip .status-dot.decoding {
background: var(--accent-cyan);
animation: strip-pulse 1.5s ease-in-out infinite;
}
.function-strip .status-dot.listening,
.function-strip .status-dot.tracking,
.function-strip .status-dot.receiving {
background: var(--accent-green);
animation: strip-pulse 1.5s ease-in-out infinite;
}
.function-strip .status-dot.sweeping {
background: var(--accent-orange);
animation: strip-pulse 1s ease-in-out infinite;
}
.function-strip .status-dot.error {
background: var(--accent-red);
}
@keyframes strip-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
50% { opacity: 0.6; box-shadow: none; }
}
/* Time display */
.function-strip .strip-time {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-muted);
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
white-space: nowrap;
}
/* Mode-specific accent colors */
.function-strip.pager-strip .strip-stat {
background: rgba(255, 193, 7, 0.05);
border-color: rgba(255, 193, 7, 0.15);
}
.function-strip.pager-strip .strip-stat:hover {
background: rgba(255, 193, 7, 0.1);
border-color: rgba(255, 193, 7, 0.3);
}
.function-strip.pager-strip .strip-value {
color: var(--accent-yellow);
}
.function-strip.sensor-strip .strip-stat {
background: rgba(0, 255, 136, 0.05);
border-color: rgba(0, 255, 136, 0.15);
}
.function-strip.sensor-strip .strip-stat:hover {
background: rgba(0, 255, 136, 0.1);
border-color: rgba(0, 255, 136, 0.3);
}
.function-strip.sensor-strip .strip-value {
color: var(--accent-green);
}
.function-strip.bt-strip .strip-stat {
background: rgba(0, 122, 255, 0.05);
border-color: rgba(0, 122, 255, 0.15);
}
.function-strip.bt-strip .strip-stat:hover {
background: rgba(0, 122, 255, 0.1);
border-color: rgba(0, 122, 255, 0.3);
}
.function-strip.bt-strip .strip-value {
color: #0a84ff;
}
.function-strip.wifi-strip .strip-stat {
background: rgba(255, 149, 0, 0.05);
border-color: rgba(255, 149, 0, 0.15);
}
.function-strip.wifi-strip .strip-stat:hover {
background: rgba(255, 149, 0, 0.1);
border-color: rgba(255, 149, 0, 0.3);
}
.function-strip.wifi-strip .strip-value {
color: var(--accent-orange);
}
.function-strip.tscm-strip {
margin-top: 4px; /* Extra clearance to prevent top clipping */
}
.function-strip.tscm-strip .strip-stat {
background: rgba(255, 59, 48, 0.15);
border: 1px solid rgba(255, 59, 48, 0.4);
}
.function-strip.tscm-strip .strip-stat:hover {
background: rgba(255, 59, 48, 0.25);
border-color: rgba(255, 59, 48, 0.6);
}
.function-strip.tscm-strip .strip-value {
color: #ef4444; /* Explicit red color */
}
.function-strip.tscm-strip .strip-label {
color: #9ca3af; /* Explicit light gray */
}
.function-strip.tscm-strip .strip-select {
color: #e8eaed; /* Explicit white for selects */
background: rgba(0, 0, 0, 0.4);
}
.function-strip.tscm-strip .strip-btn {
color: #e8eaed; /* Explicit white for buttons */
}
.function-strip.tscm-strip .strip-tool {
color: #e8eaed; /* Explicit white for tool indicators */
}
.function-strip.tscm-strip .strip-time,
.function-strip.tscm-strip .strip-status span {
color: #9ca3af; /* Explicit gray for status/time */
}
.function-strip.rtlamr-strip .strip-stat {
background: rgba(175, 82, 222, 0.05);
border-color: rgba(175, 82, 222, 0.15);
}
.function-strip.rtlamr-strip .strip-stat:hover {
background: rgba(175, 82, 222, 0.1);
border-color: rgba(175, 82, 222, 0.3);
}
.function-strip.rtlamr-strip .strip-value {
color: #af52de;
}
.function-strip.listening-strip .strip-stat {
background: rgba(74, 158, 255, 0.05);
border-color: rgba(74, 158, 255, 0.15);
}
.function-strip.listening-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.function-strip.listening-strip .strip-value {
color: var(--accent-cyan);
}
/* Threat-colored stats for TSCM */
.function-strip .strip-stat.threat-high .strip-value { color: var(--accent-red); }
.function-strip .strip-stat.threat-review .strip-value { color: var(--accent-orange); }
.function-strip .strip-stat.threat-info .strip-value { color: var(--accent-cyan); }
/* Function Strip (Action Bar) - Shared across modes
* Based on APRS strip pattern, reusable for Pager, Sensor, Bluetooth, WiFi, TSCM, etc.
*/
.function-strip {
background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px 12px;
margin-bottom: 10px;
overflow: visible;
min-height: 44px;
}
.function-strip-inner {
display: flex;
align-items: center;
gap: 8px;
min-width: max-content;
}
/* Stats */
.function-strip .strip-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 10px;
background: rgba(74, 158, 255, 0.05);
border: 1px solid rgba(74, 158, 255, 0.15);
border-radius: 4px;
min-width: 55px;
}
.function-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.function-strip .strip-value {
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
line-height: 1.2;
}
.function-strip .strip-label {
font-size: 8px;
font-weight: 600;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 1px;
}
.function-strip .strip-divider {
width: 1px;
height: 28px;
background: var(--border-color);
margin: 0 4px;
}
/* Signal stat coloring */
.function-strip .signal-stat.good .strip-value { color: var(--accent-green); }
.function-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
.function-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
/* Controls */
.function-strip .strip-control {
display: flex;
align-items: center;
gap: 4px;
}
.function-strip .strip-select {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
cursor: pointer;
}
.function-strip .strip-select:hover {
border-color: var(--accent-cyan);
}
.function-strip .strip-select:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.function-strip .strip-input-label {
font-size: 9px;
color: var(--text-muted);
font-weight: 600;
}
.function-strip .strip-input {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 6px;
border-radius: 4px;
font-size: 10px;
width: 50px;
text-align: center;
}
.function-strip .strip-input:hover,
.function-strip .strip-input:focus {
border-color: var(--accent-cyan);
outline: none;
}
.function-strip .strip-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Wider input for frequency values */
.function-strip .strip-input.wide {
width: 70px;
}
/* Tool Status Indicators */
.function-strip .strip-tools {
display: flex;
gap: 4px;
}
.function-strip .strip-tool {
font-size: 9px;
font-weight: 600;
padding: 3px 6px;
border-radius: 3px;
background: rgba(255, 59, 48, 0.2);
color: var(--accent-red);
border: 1px solid rgba(255, 59, 48, 0.3);
}
.function-strip .strip-tool.ok {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
border-color: rgba(0, 255, 136, 0.3);
}
.function-strip .strip-tool.warn {
background: rgba(255, 193, 7, 0.2);
color: var(--accent-yellow);
border-color: rgba(255, 193, 7, 0.3);
}
/* Buttons */
.function-strip .strip-btn {
background: rgba(74, 158, 255, 0.1);
border: 1px solid rgba(74, 158, 255, 0.2);
color: var(--text-primary);
padding: 6px 12px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.function-strip .strip-btn:hover:not(:disabled) {
background: rgba(74, 158, 255, 0.2);
border-color: rgba(74, 158, 255, 0.4);
}
.function-strip .strip-btn.primary {
background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
border: none;
color: #000;
}
.function-strip .strip-btn.primary:hover:not(:disabled) {
filter: brightness(1.1);
}
.function-strip .strip-btn.stop {
background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
border: none;
color: #fff;
}
.function-strip .strip-btn.stop:hover:not(:disabled) {
filter: brightness(1.1);
}
.function-strip .strip-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Status indicator */
.function-strip .strip-status {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
font-size: 10px;
font-weight: 600;
color: var(--text-secondary);
}
.function-strip .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
}
.function-strip .status-dot.inactive {
background: var(--text-muted);
}
.function-strip .status-dot.active,
.function-strip .status-dot.scanning,
.function-strip .status-dot.decoding {
background: var(--accent-cyan);
animation: strip-pulse 1.5s ease-in-out infinite;
}
.function-strip .status-dot.listening,
.function-strip .status-dot.tracking,
.function-strip .status-dot.receiving {
background: var(--accent-green);
animation: strip-pulse 1.5s ease-in-out infinite;
}
.function-strip .status-dot.sweeping {
background: var(--accent-orange);
animation: strip-pulse 1s ease-in-out infinite;
}
.function-strip .status-dot.error {
background: var(--accent-red);
}
@keyframes strip-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
50% { opacity: 0.6; box-shadow: none; }
}
/* Time display */
.function-strip .strip-time {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-muted);
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
white-space: nowrap;
}
/* Mode-specific accent colors */
.function-strip.pager-strip .strip-stat {
background: rgba(255, 193, 7, 0.05);
border-color: rgba(255, 193, 7, 0.15);
}
.function-strip.pager-strip .strip-stat:hover {
background: rgba(255, 193, 7, 0.1);
border-color: rgba(255, 193, 7, 0.3);
}
.function-strip.pager-strip .strip-value {
color: var(--accent-yellow);
}
.function-strip.sensor-strip .strip-stat {
background: rgba(0, 255, 136, 0.05);
border-color: rgba(0, 255, 136, 0.15);
}
.function-strip.sensor-strip .strip-stat:hover {
background: rgba(0, 255, 136, 0.1);
border-color: rgba(0, 255, 136, 0.3);
}
.function-strip.sensor-strip .strip-value {
color: var(--accent-green);
}
.function-strip.bt-strip .strip-stat {
background: rgba(0, 122, 255, 0.05);
border-color: rgba(0, 122, 255, 0.15);
}
.function-strip.bt-strip .strip-stat:hover {
background: rgba(0, 122, 255, 0.1);
border-color: rgba(0, 122, 255, 0.3);
}
.function-strip.bt-strip .strip-value {
color: #0a84ff;
}
.function-strip.wifi-strip .strip-stat {
background: rgba(255, 149, 0, 0.05);
border-color: rgba(255, 149, 0, 0.15);
}
.function-strip.wifi-strip .strip-stat:hover {
background: rgba(255, 149, 0, 0.1);
border-color: rgba(255, 149, 0, 0.3);
}
.function-strip.wifi-strip .strip-value {
color: var(--accent-orange);
}
.function-strip.tscm-strip {
margin-top: 4px; /* Extra clearance to prevent top clipping */
}
.function-strip.tscm-strip .strip-stat {
background: rgba(255, 59, 48, 0.15);
border: 1px solid rgba(255, 59, 48, 0.4);
}
.function-strip.tscm-strip .strip-stat:hover {
background: rgba(255, 59, 48, 0.25);
border-color: rgba(255, 59, 48, 0.6);
}
.function-strip.tscm-strip .strip-value {
color: #ef4444; /* Explicit red color */
}
.function-strip.tscm-strip .strip-label {
color: #9ca3af; /* Explicit light gray */
}
.function-strip.tscm-strip .strip-select {
color: #e8eaed; /* Explicit white for selects */
background: rgba(0, 0, 0, 0.4);
}
.function-strip.tscm-strip .strip-btn {
color: #e8eaed; /* Explicit white for buttons */
}
.function-strip.tscm-strip .strip-tool {
color: #e8eaed; /* Explicit white for tool indicators */
}
.function-strip.tscm-strip .strip-time,
.function-strip.tscm-strip .strip-status span {
color: #9ca3af; /* Explicit gray for status/time */
}
.function-strip.rtlamr-strip .strip-stat {
background: rgba(175, 82, 222, 0.05);
border-color: rgba(175, 82, 222, 0.15);
}
.function-strip.rtlamr-strip .strip-stat:hover {
background: rgba(175, 82, 222, 0.1);
border-color: rgba(175, 82, 222, 0.3);
}
.function-strip.rtlamr-strip .strip-value {
color: #af52de;
}
.function-strip.listening-strip .strip-stat {
background: rgba(74, 158, 255, 0.05);
border-color: rgba(74, 158, 255, 0.15);
}
.function-strip.listening-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.function-strip.listening-strip .strip-value {
color: var(--accent-cyan);
}
/* Threat-colored stats for TSCM */
.function-strip .strip-stat.threat-high .strip-value { color: var(--accent-red); }
.function-strip .strip-stat.threat-review .strip-value { color: var(--accent-orange); }
.function-strip .strip-stat.threat-info .strip-value { color: var(--accent-cyan); }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -73,8 +73,8 @@
/* ============================================
TYPOGRAPHY
============================================ */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'Terminus', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
/* Font sizes */
--text-xs: 10px;

View File

@@ -33,7 +33,7 @@
src: url('/static/vendor/fonts/Inter-Bold.woff2') format('woff2');
}
/* JetBrains Mono - Monospace/code font */
/* JetBrains Mono - Monospace/code font */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
@@ -58,10 +58,19 @@
src: url('/static/vendor/fonts/JetBrainsMono-SemiBold.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/static/vendor/fonts/JetBrainsMono-Bold.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/static/vendor/fonts/JetBrainsMono-Bold.woff2') format('woff2');
}
/* Terminus - Monospace UI font */
@font-face {
font-family: 'Terminus';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/vendor/fonts/Terminus.ttf') format('truetype');
}

View File

@@ -50,7 +50,7 @@
letter-spacing: 1px;
margin-right: 8px;
font-weight: 500;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.mode-nav-divider {
@@ -80,7 +80,7 @@
.mode-nav-btn .nav-label {
text-transform: uppercase;
letter-spacing: 0.08em;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -127,7 +127,7 @@
.nav-action-btn .nav-label {
text-transform: uppercase;
letter-spacing: 0.08em;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -162,7 +162,7 @@
.mode-nav-dropdown-btn .nav-label {
text-transform: uppercase;
letter-spacing: 0.08em;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
@@ -268,7 +268,7 @@
display: flex;
align-items: center;
gap: 6px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
flex-shrink: 0;
white-space: nowrap;

View File

@@ -1,5 +1,13 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
@font-face {
font-family: 'Terminus';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/vendor/fonts/Terminus.ttf') format('truetype');
}
* {
box-sizing: border-box;
margin: 0;
@@ -259,7 +267,7 @@ body {
}
.welcome-title {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
@@ -269,7 +277,7 @@ body {
}
.welcome-tagline {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.9rem;
color: var(--accent-cyan);
letter-spacing: 0.15em;
@@ -278,7 +286,7 @@ body {
.welcome-version {
display: inline-block;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.65rem;
color: var(--bg-primary);
background: var(--accent-cyan);
@@ -297,7 +305,7 @@ body {
}
.welcome-content h2 {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
@@ -333,7 +341,7 @@ body {
}
.changelog-version {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--accent-cyan);
font-weight: 600;
@@ -364,7 +372,7 @@ body {
position: absolute;
left: -15px;
color: var(--accent-green);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
/* Mode Selection Grid */
@@ -435,7 +443,7 @@ body {
}
.mode-card .mode-name {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 600;
color: var(--text-primary);
@@ -463,7 +471,7 @@ body {
display: flex;
align-items: center;
gap: 8px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
color: var(--text-secondary);
@@ -922,7 +930,7 @@ header h1 {
display: flex;
align-items: center;
gap: 6px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
flex-shrink: 0;
white-space: nowrap;
@@ -1030,7 +1038,7 @@ header h1 {
.version-badge {
font-size: 0.6rem;
font-weight: 500;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
letter-spacing: 0.05em;
color: var(--text-secondary);
background: var(--bg-tertiary);
@@ -1089,7 +1097,7 @@ header h1 .tagline {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
transition: all 0.2s ease;
}
@@ -1578,7 +1586,7 @@ header h1 .tagline {
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-primary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
transition: all 0.15s ease;
}
@@ -1637,7 +1645,7 @@ header h1 .tagline {
border: 1px solid var(--border-color);
color: var(--text-secondary);
cursor: pointer;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
@@ -1757,7 +1765,7 @@ header h1 .tagline {
gap: 8px;
font-size: 10px;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.stats>div {
@@ -1783,7 +1791,7 @@ header h1 .tagline {
flex: 1;
padding: 10px;
overflow-y: auto;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
background: var(--bg-primary);
min-height: 0; /* Allow shrinking in flex context */
@@ -1855,7 +1863,7 @@ header h1 .tagline {
.message .address {
color: var(--accent-green);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
margin-bottom: 8px;
}
@@ -1868,7 +1876,7 @@ header h1 .tagline {
}
.message .content.numeric {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 15px;
letter-spacing: 2px;
color: var(--accent-cyan);
@@ -2357,7 +2365,7 @@ header h1 .tagline {
/* Dark theme for Leaflet */
.leaflet-container {
background: #0a0a0a;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
/* Using actual dark tiles now - no filter needed */
@@ -2394,7 +2402,7 @@ header h1 .tagline {
display: flex;
justify-content: space-between;
z-index: 1000;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
text-shadow: 0 0 5px var(--accent-cyan);
@@ -2411,7 +2419,7 @@ header h1 .tagline {
display: flex;
justify-content: space-between;
z-index: 1000;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: var(--accent-cyan);
text-shadow: 0 0 5px var(--accent-cyan);
@@ -2432,7 +2440,7 @@ header h1 .tagline {
}
.aircraft-popup {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -2476,7 +2484,7 @@ header h1 .tagline {
background: rgba(0, 0, 0, 0.8) !important;
border: 1px solid var(--accent-cyan) !important;
color: var(--accent-cyan) !important;
font-family: 'JetBrains Mono', monospace !important;
font-family: var(--font-mono) !important;
font-size: 10px !important;
padding: 2px 6px !important;
border-radius: 2px !important;
@@ -2720,7 +2728,7 @@ header h1 .tagline {
color: var(--accent-cyan);
font-size: 22px;
font-weight: 700;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
text-shadow: 0 0 15px var(--accent-cyan-dim);
line-height: 1.2;
}
@@ -3114,7 +3122,7 @@ header h1 .tagline {
}
.sensor-card .data-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
color: var(--accent-cyan);
}
@@ -3164,7 +3172,7 @@ header h1 .tagline {
display: flex;
gap: 15px;
font-size: 10px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.recon-stats span {
@@ -3214,14 +3222,14 @@ header h1 .tagline {
.device-id {
color: var(--text-dim);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
}
.device-meta {
text-align: right;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.device-meta.encrypted {
@@ -3297,7 +3305,7 @@ header h1 .tagline {
}
.hex-dump {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-dim);
background: var(--bg-primary);
@@ -3969,7 +3977,7 @@ header h1 .tagline {
}
.bt-detail-address {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: #00d4ff;
}
@@ -3983,7 +3991,7 @@ header h1 .tagline {
}
.bt-detail-rssi-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 20px;
font-weight: 700;
}
@@ -4078,7 +4086,7 @@ header h1 .tagline {
}
.bt-detail-services-list {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 8px;
color: var(--text-dim);
white-space: nowrap;
@@ -4325,7 +4333,7 @@ header h1 .tagline {
}
.bt-rssi-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
min-width: 28px;
@@ -4684,7 +4692,7 @@ header h1 .tagline {
flex-direction: column;
gap: 4px;
font-size: 10px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
.security-legend-item {
@@ -4731,7 +4739,7 @@ header h1 .tagline {
}
.signal-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 28px;
color: var(--accent-cyan);
text-shadow: 0 0 10px var(--accent-cyan-dim);
@@ -5247,7 +5255,7 @@ body::before {
.meter-value {
font-size: 10px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
color: var(--text-secondary);
width: 50px;
text-align: right;
@@ -5404,7 +5412,7 @@ body::before {
}
.freq-digits {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 56px;
font-weight: 700;
color: var(--accent-cyan);
@@ -5425,7 +5433,7 @@ body::before {
}
.freq-unit {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 20px;
color: var(--text-secondary);
margin-left: 8px;
@@ -5569,7 +5577,7 @@ body::before {
}
.knob-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
color: var(--accent-cyan);
@@ -5694,7 +5702,7 @@ body::before {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
border-radius: 4px;
@@ -5756,13 +5764,13 @@ body::before {
}
.signal-arc-label {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 8px;
fill: var(--text-muted);
}
.signal-arc-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
fill: var(--accent-cyan);
@@ -5794,7 +5802,7 @@ body::before {
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
text-align: center;
@@ -5930,7 +5938,7 @@ body::before {
max-height: 200px;
overflow-y: auto;
padding: 10px 15px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -6019,7 +6027,7 @@ body::before {
}
.module-header {
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 10px;
font-weight: 600;
color: var(--accent-cyan);
@@ -6044,7 +6052,7 @@ body::before {
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-primary);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
padding: 6px 8px;
text-align: center;
@@ -6102,7 +6110,7 @@ body::before {
}
.stat-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 22px;
font-weight: bold;
}
@@ -6150,7 +6158,7 @@ body::before {
.tune-btn {
padding: 4px 8px;
font-size: 10px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
background: var(--bg-elevated);
border: 1px solid var(--border-color);
color: var(--text-secondary);
@@ -6180,13 +6188,13 @@ body::before {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
padding: 8px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
/* Listening Mode Selector Buttons */
.radio-mode-btn {
padding: 12px 24px;
font-family: 'Orbitron', 'JetBrains Mono', monospace;
font-family: 'Orbitron', 'Terminus', monospace;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
@@ -6227,7 +6235,7 @@ body::before {
/* Frequency Preset Buttons */
.preset-freq-btn {
padding: 8px 14px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
background: var(--bg-elevated);
border: 1px solid var(--border-color);
@@ -6291,4 +6299,4 @@ body::before {
[data-animations="off"] .logo-dot,
[data-animations="off"] .welcome-logo {
animation: none !important;
}
}

View File

@@ -37,7 +37,7 @@
/* Typography */
.landing-title {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 2.2rem;
font-weight: 700;
letter-spacing: 0.4em;
@@ -48,7 +48,7 @@
}
.landing-tagline {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
color: var(--accent-cyan);
font-size: 0.9rem;
letter-spacing: 0.15em;
@@ -71,7 +71,7 @@
/* Hacker Style Error */
.flash-error {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: var(--accent-red);
background: rgba(239, 68, 68, 0.1);
@@ -94,7 +94,7 @@
color: var(--accent-cyan);
padding: 12px;
margin-bottom: 15px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
outline: none;
box-sizing: border-box; /* Crucial for visibility */
@@ -106,7 +106,7 @@
border: 2px solid var(--accent-cyan);
color: var(--accent-cyan);
padding: 15px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-weight: 600;
letter-spacing: 3px;
cursor: pointer;
@@ -116,7 +116,7 @@
.landing-version {
margin-top: 25px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: rgba(255, 255, 255, 0.3);
letter-spacing: 2px;

View File

@@ -1,328 +1,328 @@
/* APRS Function Bar (Stats Strip) Styles */
.aprs-strip {
background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 6px 12px;
margin-bottom: 10px;
overflow-x: auto;
}
.aprs-strip-inner {
display: flex;
align-items: center;
gap: 8px;
min-width: max-content;
}
.aprs-strip .strip-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 10px;
background: rgba(74, 158, 255, 0.05);
border: 1px solid rgba(74, 158, 255, 0.15);
border-radius: 4px;
min-width: 55px;
}
.aprs-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.aprs-strip .strip-value {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
line-height: 1.2;
}
.aprs-strip .strip-label {
font-size: 8px;
font-weight: 600;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 1px;
}
.aprs-strip .strip-divider {
width: 1px;
height: 28px;
background: var(--border-color);
margin: 0 4px;
}
/* Signal stat coloring */
.aprs-strip .signal-stat.good .strip-value { color: var(--accent-green); }
.aprs-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
.aprs-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
/* Controls */
.aprs-strip .strip-control {
display: flex;
align-items: center;
gap: 4px;
}
.aprs-strip .strip-select {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
cursor: pointer;
}
.aprs-strip .strip-select:hover {
border-color: var(--accent-cyan);
}
.aprs-strip .strip-input-label {
font-size: 9px;
color: var(--text-muted);
font-weight: 600;
}
.aprs-strip .strip-input {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 6px;
border-radius: 4px;
font-size: 10px;
width: 50px;
text-align: center;
}
.aprs-strip .strip-input:hover,
.aprs-strip .strip-input:focus {
border-color: var(--accent-cyan);
outline: none;
}
/* Tool Status Indicators */
.aprs-strip .strip-tools {
display: flex;
gap: 4px;
}
.aprs-strip .strip-tool {
font-size: 9px;
font-weight: 600;
padding: 3px 6px;
border-radius: 3px;
background: rgba(255, 59, 48, 0.2);
color: var(--accent-red);
border: 1px solid rgba(255, 59, 48, 0.3);
}
.aprs-strip .strip-tool.ok {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
border-color: rgba(0, 255, 136, 0.3);
}
/* Buttons */
.aprs-strip .strip-btn {
background: rgba(74, 158, 255, 0.1);
border: 1px solid rgba(74, 158, 255, 0.2);
color: var(--text-primary);
padding: 6px 12px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.aprs-strip .strip-btn:hover:not(:disabled) {
background: rgba(74, 158, 255, 0.2);
border-color: rgba(74, 158, 255, 0.4);
}
.aprs-strip .strip-btn.primary {
background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
border: none;
color: #000;
}
.aprs-strip .strip-btn.primary:hover:not(:disabled) {
filter: brightness(1.1);
}
.aprs-strip .strip-btn.stop {
background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
border: none;
color: #fff;
}
.aprs-strip .strip-btn.stop:hover:not(:disabled) {
filter: brightness(1.1);
}
.aprs-strip .strip-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Status indicator */
.aprs-strip .strip-status {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
font-size: 10px;
font-weight: 600;
color: var(--text-secondary);
}
.aprs-strip .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
}
.aprs-strip .status-dot.listening {
background: var(--accent-cyan);
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
}
.aprs-strip .status-dot.tracking {
background: var(--accent-green);
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
}
.aprs-strip .status-dot.error {
background: var(--accent-red);
}
@keyframes aprs-strip-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
50% { opacity: 0.6; box-shadow: none; }
}
/* Time display */
.aprs-strip .strip-time {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-muted);
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
white-space: nowrap;
}
/* APRS Status Bar Styles (Sidebar - legacy) */
.aprs-status-bar {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.aprs-status-indicator {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.aprs-status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text-muted);
}
.aprs-status-dot.standby { background: var(--text-muted); }
.aprs-status-dot.listening {
background: var(--accent-cyan);
animation: aprs-pulse 1.5s ease-in-out infinite;
}
.aprs-status-dot.tracking { background: var(--accent-green); }
.aprs-status-dot.error { background: var(--accent-red); }
@keyframes aprs-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.7); }
50% { opacity: 0.6; box-shadow: 0 0 8px 4px rgba(74, 158, 255, 0.3); }
}
.aprs-status-text {
font-size: 10px;
font-weight: bold;
letter-spacing: 1px;
}
.aprs-status-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 9px;
}
.aprs-stat {
color: var(--text-secondary);
}
.aprs-stat-label {
color: var(--text-muted);
}
/* Signal Meter Styles */
.aprs-signal-meter {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.aprs-meter-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.aprs-meter-label {
font-size: 10px;
font-weight: bold;
letter-spacing: 1px;
color: var(--text-secondary);
}
.aprs-meter-value {
font-size: 12px;
font-weight: bold;
font-family: monospace;
color: var(--accent-cyan);
min-width: 24px;
}
.aprs-meter-burst {
font-size: 9px;
font-weight: bold;
color: var(--accent-yellow);
background: rgba(255, 193, 7, 0.2);
padding: 2px 6px;
border-radius: 3px;
animation: burst-flash 0.3s ease-out;
}
@keyframes burst-flash {
0% { opacity: 1; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}
.aprs-meter-bar-container {
position: relative;
height: 16px;
background: rgba(0,0,0,0.4);
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
}
.aprs-meter-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg,
var(--accent-green) 0%,
var(--accent-cyan) 50%,
var(--accent-yellow) 75%,
var(--accent-red) 100%
);
border-radius: 3px;
transition: width 0.1s ease-out;
}
.aprs-meter-bar.no-signal {
opacity: 0.3;
}
.aprs-meter-ticks {
display: flex;
justify-content: space-between;
font-size: 8px;
color: var(--text-muted);
padding: 0 2px;
}
.aprs-meter-status {
font-size: 9px;
color: var(--text-muted);
text-align: center;
margin-top: 6px;
}
.aprs-meter-status.active {
color: var(--accent-green);
}
.aprs-meter-status.no-signal {
color: var(--accent-yellow);
}
/* APRS Function Bar (Stats Strip) Styles */
.aprs-strip {
background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 6px 12px;
margin-bottom: 10px;
overflow-x: auto;
}
.aprs-strip-inner {
display: flex;
align-items: center;
gap: 8px;
min-width: max-content;
}
.aprs-strip .strip-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 10px;
background: rgba(74, 158, 255, 0.05);
border: 1px solid rgba(74, 158, 255, 0.15);
border-radius: 4px;
min-width: 55px;
}
.aprs-strip .strip-stat:hover {
background: rgba(74, 158, 255, 0.1);
border-color: rgba(74, 158, 255, 0.3);
}
.aprs-strip .strip-value {
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--accent-cyan);
line-height: 1.2;
}
.aprs-strip .strip-label {
font-size: 8px;
font-weight: 600;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 1px;
}
.aprs-strip .strip-divider {
width: 1px;
height: 28px;
background: var(--border-color);
margin: 0 4px;
}
/* Signal stat coloring */
.aprs-strip .signal-stat.good .strip-value { color: var(--accent-green); }
.aprs-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
.aprs-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
/* Controls */
.aprs-strip .strip-control {
display: flex;
align-items: center;
gap: 4px;
}
.aprs-strip .strip-select {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
cursor: pointer;
}
.aprs-strip .strip-select:hover {
border-color: var(--accent-cyan);
}
.aprs-strip .strip-input-label {
font-size: 9px;
color: var(--text-muted);
font-weight: 600;
}
.aprs-strip .strip-input {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 4px 6px;
border-radius: 4px;
font-size: 10px;
width: 50px;
text-align: center;
}
.aprs-strip .strip-input:hover,
.aprs-strip .strip-input:focus {
border-color: var(--accent-cyan);
outline: none;
}
/* Tool Status Indicators */
.aprs-strip .strip-tools {
display: flex;
gap: 4px;
}
.aprs-strip .strip-tool {
font-size: 9px;
font-weight: 600;
padding: 3px 6px;
border-radius: 3px;
background: rgba(255, 59, 48, 0.2);
color: var(--accent-red);
border: 1px solid rgba(255, 59, 48, 0.3);
}
.aprs-strip .strip-tool.ok {
background: rgba(0, 255, 136, 0.1);
color: var(--accent-green);
border-color: rgba(0, 255, 136, 0.3);
}
/* Buttons */
.aprs-strip .strip-btn {
background: rgba(74, 158, 255, 0.1);
border: 1px solid rgba(74, 158, 255, 0.2);
color: var(--text-primary);
padding: 6px 12px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.aprs-strip .strip-btn:hover:not(:disabled) {
background: rgba(74, 158, 255, 0.2);
border-color: rgba(74, 158, 255, 0.4);
}
.aprs-strip .strip-btn.primary {
background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
border: none;
color: #000;
}
.aprs-strip .strip-btn.primary:hover:not(:disabled) {
filter: brightness(1.1);
}
.aprs-strip .strip-btn.stop {
background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
border: none;
color: #fff;
}
.aprs-strip .strip-btn.stop:hover:not(:disabled) {
filter: brightness(1.1);
}
.aprs-strip .strip-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Status indicator */
.aprs-strip .strip-status {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
font-size: 10px;
font-weight: 600;
color: var(--text-secondary);
}
.aprs-strip .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
}
.aprs-strip .status-dot.listening {
background: var(--accent-cyan);
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
}
.aprs-strip .status-dot.tracking {
background: var(--accent-green);
animation: aprs-strip-pulse 1.5s ease-in-out infinite;
}
.aprs-strip .status-dot.error {
background: var(--accent-red);
}
@keyframes aprs-strip-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
50% { opacity: 0.6; box-shadow: none; }
}
/* Time display */
.aprs-strip .strip-time {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-muted);
padding: 4px 8px;
background: rgba(0,0,0,0.2);
border-radius: 4px;
white-space: nowrap;
}
/* APRS Status Bar Styles (Sidebar - legacy) */
.aprs-status-bar {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.aprs-status-indicator {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.aprs-status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text-muted);
}
.aprs-status-dot.standby { background: var(--text-muted); }
.aprs-status-dot.listening {
background: var(--accent-cyan);
animation: aprs-pulse 1.5s ease-in-out infinite;
}
.aprs-status-dot.tracking { background: var(--accent-green); }
.aprs-status-dot.error { background: var(--accent-red); }
@keyframes aprs-pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.7); }
50% { opacity: 0.6; box-shadow: 0 0 8px 4px rgba(74, 158, 255, 0.3); }
}
.aprs-status-text {
font-size: 10px;
font-weight: bold;
letter-spacing: 1px;
}
.aprs-status-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 9px;
}
.aprs-stat {
color: var(--text-secondary);
}
.aprs-stat-label {
color: var(--text-muted);
}
/* Signal Meter Styles */
.aprs-signal-meter {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.aprs-meter-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.aprs-meter-label {
font-size: 10px;
font-weight: bold;
letter-spacing: 1px;
color: var(--text-secondary);
}
.aprs-meter-value {
font-size: 12px;
font-weight: bold;
font-family: monospace;
color: var(--accent-cyan);
min-width: 24px;
}
.aprs-meter-burst {
font-size: 9px;
font-weight: bold;
color: var(--accent-yellow);
background: rgba(255, 193, 7, 0.2);
padding: 2px 6px;
border-radius: 3px;
animation: burst-flash 0.3s ease-out;
}
@keyframes burst-flash {
0% { opacity: 1; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}
.aprs-meter-bar-container {
position: relative;
height: 16px;
background: rgba(0,0,0,0.4);
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
}
.aprs-meter-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg,
var(--accent-green) 0%,
var(--accent-cyan) 50%,
var(--accent-yellow) 75%,
var(--accent-red) 100%
);
border-radius: 3px;
transition: width 0.1s ease-out;
}
.aprs-meter-bar.no-signal {
opacity: 0.3;
}
.aprs-meter-ticks {
display: flex;
justify-content: space-between;
font-size: 8px;
color: var(--text-muted);
padding: 0 2px;
}
.aprs-meter-status {
font-size: 9px;
color: var(--text-muted);
text-align: center;
margin-top: 6px;
}
.aprs-meter-status.active {
color: var(--accent-green);
}
.aprs-meter-status.no-signal {
color: var(--accent-yellow);
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@
}
.spy-stations-title {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
@@ -101,7 +101,7 @@
}
.spy-station-name {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
@@ -117,7 +117,7 @@
/* Type Badge */
.spy-station-badge {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
@@ -173,7 +173,7 @@
}
.spy-meta-mode {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
color: var(--accent-orange);
}
@@ -186,7 +186,7 @@
}
.spy-freq-list {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
line-height: 1.6;
@@ -199,7 +199,7 @@
}
.spy-freq-item {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
background: var(--bg-secondary);
@@ -236,7 +236,7 @@
}
.spy-freq-select {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 10px;
padding: 6px 8px;
background: var(--bg-secondary);
@@ -273,7 +273,7 @@
display: inline-flex;
align-items: center;
gap: 6px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,16 @@
/* ============================================
RESPONSIVE UTILITIES - iNTERCEPT
Shared responsive foundation for all pages
============================================ */
/* ============================================
RESPONSIVE UTILITIES - iNTERCEPT
Shared responsive foundation for all pages
============================================ */
/* Terminus - bundled monospace */
@font-face {
font-family: 'Terminus';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/vendor/fonts/Terminus.ttf') format('truetype');
}
/* ============== CSS VARIABLES ============== */
:root {

View File

@@ -142,7 +142,7 @@ body {
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 4px;
padding: 4px 10px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -164,7 +164,7 @@ body {
display: flex;
gap: 20px;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}
@@ -457,7 +457,7 @@ body {
}
.telemetry-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 12px;
color: var(--accent-cyan);
}
@@ -543,7 +543,7 @@ body {
}
.pass-time {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
}
/* Bottom controls bar */
@@ -579,7 +579,7 @@ body {
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 4px;
color: var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
font-size: 11px;
}

View File

@@ -1,444 +1,444 @@
/* Settings Modal Styles */
.settings-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 10000;
overflow-y: auto;
backdrop-filter: blur(4px);
}
.settings-modal.active {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
}
.settings-content {
background: var(--bg-dark, #0a0a0f);
border: 1px solid var(--border-color, #1a1a2e);
border-radius: 8px;
max-width: 600px;
width: 100%;
position: relative;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border-color, #1a1a2e);
}
.settings-header h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary, #e0e0e0);
display: flex;
align-items: center;
gap: 8px;
}
.settings-header h2 .icon {
width: 20px;
height: 20px;
color: var(--accent-cyan, #00d4ff);
}
.settings-close {
background: none;
border: none;
color: var(--text-muted, #666);
font-size: 24px;
cursor: pointer;
padding: 4px;
line-height: 1;
transition: color 0.2s;
}
.settings-close:hover {
color: var(--accent-red, #ff4444);
}
/* Settings Tabs */
.settings-tabs {
display: flex;
border-bottom: 1px solid var(--border-color, #1a1a2e);
padding: 0 20px;
gap: 4px;
}
.settings-tab {
background: none;
border: none;
padding: 12px 16px;
color: var(--text-muted, #666);
font-size: 13px;
font-weight: 500;
cursor: pointer;
position: relative;
transition: color 0.2s;
}
.settings-tab:hover {
color: var(--text-primary, #e0e0e0);
}
.settings-tab.active {
color: var(--accent-cyan, #00d4ff);
}
.settings-tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: var(--accent-cyan, #00d4ff);
}
/* Settings Sections */
.settings-section {
display: none;
padding: 20px;
}
.settings-section.active {
display: block;
}
.settings-group {
margin-bottom: 24px;
}
.settings-group:last-child {
margin-bottom: 0;
}
.settings-group-title {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted, #666);
margin-bottom: 12px;
}
/* Settings Row */
.settings-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.settings-row:last-child {
border-bottom: none;
}
.settings-label {
display: flex;
flex-direction: column;
gap: 2px;
}
.settings-label-text {
font-size: 13px;
color: var(--text-primary, #e0e0e0);
}
.settings-label-desc {
font-size: 11px;
color: var(--text-muted, #666);
}
/* Toggle Switch */
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
flex-shrink: 0;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
transition: 0.3s;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: var(--text-muted, #666);
transition: 0.3s;
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background-color: var(--accent-cyan, #00d4ff);
border-color: var(--accent-cyan, #00d4ff);
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(20px);
background-color: white;
}
.toggle-switch input:focus + .toggle-slider {
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.3);
}
/* Select Dropdown */
.settings-select {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
border-radius: 4px;
padding: 8px 12px;
font-size: 13px;
color: var(--text-primary, #e0e0e0);
min-width: 160px;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
padding-right: 32px;
}
.settings-select:focus {
outline: none;
border-color: var(--accent-cyan, #00d4ff);
}
/* Text Input */
.settings-input {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
border-radius: 4px;
padding: 8px 12px;
font-size: 13px;
color: var(--text-primary, #e0e0e0);
width: 200px;
}
.settings-input:focus {
outline: none;
border-color: var(--accent-cyan, #00d4ff);
}
.settings-input::placeholder {
color: var(--text-muted, #666);
}
/* Asset Status */
.asset-status {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 12px;
padding: 12px;
background: var(--bg-secondary, #0f0f1a);
border-radius: 6px;
}
.asset-status-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
}
.asset-name {
color: var(--text-muted, #888);
}
.asset-badge {
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
}
.asset-badge.available {
background: rgba(0, 255, 136, 0.15);
color: var(--accent-green, #00ff88);
}
.asset-badge.missing {
background: rgba(255, 68, 68, 0.15);
color: var(--accent-red, #ff4444);
}
.asset-badge.checking {
background: rgba(255, 170, 0, 0.15);
color: var(--accent-orange, #ffaa00);
}
/* Check Assets Button */
.check-assets-btn {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
color: var(--text-primary, #e0e0e0);
padding: 8px 16px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
margin-top: 12px;
transition: all 0.2s;
}
.check-assets-btn:hover {
border-color: var(--accent-cyan, #00d4ff);
color: var(--accent-cyan, #00d4ff);
}
.check-assets-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* GPS Detection Spinner */
.detecting-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: detecting-spin 0.8s linear infinite;
vertical-align: middle;
margin-right: 6px;
}
@keyframes detecting-spin {
to { transform: rotate(360deg); }
}
/* About Section */
.about-info {
font-size: 13px;
color: var(--text-muted, #888);
line-height: 1.6;
}
.about-info p {
margin: 0 0 12px 0;
}
.about-info a {
color: var(--accent-cyan, #00d4ff);
text-decoration: none;
}
.about-info a:hover {
text-decoration: underline;
}
.about-version {
font-family: 'JetBrains Mono', monospace;
color: var(--accent-cyan, #00d4ff);
}
/* Donate Button */
.donate-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
background: linear-gradient(135deg, var(--accent-amber, #d4a853) 0%, var(--accent-orange, #f59e0b) 100%);
border: none;
border-radius: 6px;
color: #000;
font-size: 13px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(212, 168, 83, 0.3);
}
.donate-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(212, 168, 83, 0.4);
filter: brightness(1.1);
}
.donate-btn:active {
transform: translateY(0);
}
/* Tile Provider Custom URL */
.custom-url-row {
margin-top: 8px;
padding-top: 8px;
}
.custom-url-row .settings-input {
width: 100%;
}
/* Info Callout */
.settings-info {
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 6px;
padding: 12px;
margin-top: 16px;
font-size: 12px;
color: var(--text-muted, #888);
}
.settings-info strong {
color: var(--accent-cyan, #00d4ff);
}
/* Responsive */
@media (max-width: 640px) {
.settings-modal.active {
padding: 20px 10px;
}
.settings-content {
max-width: 100%;
}
.settings-row {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.settings-select,
.settings-input {
width: 100%;
}
}
/* Settings Modal Styles */
.settings-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 10000;
overflow-y: auto;
backdrop-filter: blur(4px);
}
.settings-modal.active {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
}
.settings-content {
background: var(--bg-dark, #0a0a0f);
border: 1px solid var(--border-color, #1a1a2e);
border-radius: 8px;
max-width: 600px;
width: 100%;
position: relative;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border-color, #1a1a2e);
}
.settings-header h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary, #e0e0e0);
display: flex;
align-items: center;
gap: 8px;
}
.settings-header h2 .icon {
width: 20px;
height: 20px;
color: var(--accent-cyan, #00d4ff);
}
.settings-close {
background: none;
border: none;
color: var(--text-muted, #666);
font-size: 24px;
cursor: pointer;
padding: 4px;
line-height: 1;
transition: color 0.2s;
}
.settings-close:hover {
color: var(--accent-red, #ff4444);
}
/* Settings Tabs */
.settings-tabs {
display: flex;
border-bottom: 1px solid var(--border-color, #1a1a2e);
padding: 0 20px;
gap: 4px;
}
.settings-tab {
background: none;
border: none;
padding: 12px 16px;
color: var(--text-muted, #666);
font-size: 13px;
font-weight: 500;
cursor: pointer;
position: relative;
transition: color 0.2s;
}
.settings-tab:hover {
color: var(--text-primary, #e0e0e0);
}
.settings-tab.active {
color: var(--accent-cyan, #00d4ff);
}
.settings-tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: var(--accent-cyan, #00d4ff);
}
/* Settings Sections */
.settings-section {
display: none;
padding: 20px;
}
.settings-section.active {
display: block;
}
.settings-group {
margin-bottom: 24px;
}
.settings-group:last-child {
margin-bottom: 0;
}
.settings-group-title {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted, #666);
margin-bottom: 12px;
}
/* Settings Row */
.settings-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.settings-row:last-child {
border-bottom: none;
}
.settings-label {
display: flex;
flex-direction: column;
gap: 2px;
}
.settings-label-text {
font-size: 13px;
color: var(--text-primary, #e0e0e0);
}
.settings-label-desc {
font-size: 11px;
color: var(--text-muted, #666);
}
/* Toggle Switch */
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
flex-shrink: 0;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
transition: 0.3s;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: var(--text-muted, #666);
transition: 0.3s;
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background-color: var(--accent-cyan, #00d4ff);
border-color: var(--accent-cyan, #00d4ff);
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(20px);
background-color: white;
}
.toggle-switch input:focus + .toggle-slider {
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.3);
}
/* Select Dropdown */
.settings-select {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
border-radius: 4px;
padding: 8px 12px;
font-size: 13px;
color: var(--text-primary, #e0e0e0);
min-width: 160px;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
padding-right: 32px;
}
.settings-select:focus {
outline: none;
border-color: var(--accent-cyan, #00d4ff);
}
/* Text Input */
.settings-input {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
border-radius: 4px;
padding: 8px 12px;
font-size: 13px;
color: var(--text-primary, #e0e0e0);
width: 200px;
}
.settings-input:focus {
outline: none;
border-color: var(--accent-cyan, #00d4ff);
}
.settings-input::placeholder {
color: var(--text-muted, #666);
}
/* Asset Status */
.asset-status {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 12px;
padding: 12px;
background: var(--bg-secondary, #0f0f1a);
border-radius: 6px;
}
.asset-status-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
}
.asset-name {
color: var(--text-muted, #888);
}
.asset-badge {
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
}
.asset-badge.available {
background: rgba(0, 255, 136, 0.15);
color: var(--accent-green, #00ff88);
}
.asset-badge.missing {
background: rgba(255, 68, 68, 0.15);
color: var(--accent-red, #ff4444);
}
.asset-badge.checking {
background: rgba(255, 170, 0, 0.15);
color: var(--accent-orange, #ffaa00);
}
/* Check Assets Button */
.check-assets-btn {
background: var(--bg-tertiary, #1a1a2e);
border: 1px solid var(--border-color, #2a2a3e);
color: var(--text-primary, #e0e0e0);
padding: 8px 16px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
margin-top: 12px;
transition: all 0.2s;
}
.check-assets-btn:hover {
border-color: var(--accent-cyan, #00d4ff);
color: var(--accent-cyan, #00d4ff);
}
.check-assets-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* GPS Detection Spinner */
.detecting-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: detecting-spin 0.8s linear infinite;
vertical-align: middle;
margin-right: 6px;
}
@keyframes detecting-spin {
to { transform: rotate(360deg); }
}
/* About Section */
.about-info {
font-size: 13px;
color: var(--text-muted, #888);
line-height: 1.6;
}
.about-info p {
margin: 0 0 12px 0;
}
.about-info a {
color: var(--accent-cyan, #00d4ff);
text-decoration: none;
}
.about-info a:hover {
text-decoration: underline;
}
.about-version {
font-family: var(--font-mono);
color: var(--accent-cyan, #00d4ff);
}
/* Donate Button */
.donate-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
background: linear-gradient(135deg, var(--accent-amber, #d4a853) 0%, var(--accent-orange, #f59e0b) 100%);
border: none;
border-radius: 6px;
color: #000;
font-size: 13px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(212, 168, 83, 0.3);
}
.donate-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(212, 168, 83, 0.4);
filter: brightness(1.1);
}
.donate-btn:active {
transform: translateY(0);
}
/* Tile Provider Custom URL */
.custom-url-row {
margin-top: 8px;
padding-top: 8px;
}
.custom-url-row .settings-input {
width: 100%;
}
/* Info Callout */
.settings-info {
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.2);
border-radius: 6px;
padding: 12px;
margin-top: 16px;
font-size: 12px;
color: var(--text-muted, #888);
}
.settings-info strong {
color: var(--accent-cyan, #00d4ff);
}
/* Responsive */
@media (max-width: 640px) {
.settings-modal.active {
padding: 20px 10px;
}
.settings-content {
max-width: 100%;
}
.settings-row {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.settings-select,
.settings-input {
width: 100%;
}
}

View File

@@ -1,36 +1,36 @@
/**
* Intercept - Core Application Logic
* Global state, mode switching, and shared functionality
*/
// ============== GLOBAL STATE ==============
// Mode state flags
let eventSource = null;
let isRunning = false;
let isSensorRunning = false;
let isAdsbRunning = false;
let isWifiRunning = false;
let isBtRunning = false;
let currentMode = 'pager';
// Message counters
let msgCount = 0;
let pocsagCount = 0;
let flexCount = 0;
let sensorCount = 0;
let filteredCount = 0;
// Device list (populated from server via Jinja2)
let deviceList = [];
// Auto-scroll setting
let autoScroll = localStorage.getItem('autoScroll') !== 'false';
// Mute setting
let muted = localStorage.getItem('audioMuted') === 'true';
// Observer location (load from localStorage or default to London)
/**
* Intercept - Core Application Logic
* Global state, mode switching, and shared functionality
*/
// ============== GLOBAL STATE ==============
// Mode state flags
let eventSource = null;
let isRunning = false;
let isSensorRunning = false;
let isAdsbRunning = false;
let isWifiRunning = false;
let isBtRunning = false;
let currentMode = 'pager';
// Message counters
let msgCount = 0;
let pocsagCount = 0;
let flexCount = 0;
let sensorCount = 0;
let filteredCount = 0;
// Device list (populated from server via Jinja2)
let deviceList = [];
// Auto-scroll setting
let autoScroll = localStorage.getItem('autoScroll') !== 'false';
// Mute setting
let muted = localStorage.getItem('audioMuted') === 'true';
// Observer location (load from localStorage or default to London)
let observerLocation = (function() {
if (window.ObserverLocation && ObserverLocation.getForModule) {
return ObserverLocation.getForModule('observerLocation');
@@ -44,464 +44,464 @@ let observerLocation = (function() {
}
return { lat: 51.5074, lon: -0.1278 };
})();
// Message storage for export
let allMessages = [];
// Track unique sensor devices
let uniqueDevices = new Set();
// SDR device usage tracking
let sdrDeviceUsage = {};
// ============== DISCLAIMER HANDLING ==============
function checkDisclaimer() {
const accepted = localStorage.getItem('disclaimerAccepted');
if (accepted === 'true') {
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
}
}
function acceptDisclaimer() {
localStorage.setItem('disclaimerAccepted', 'true');
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
}
function declineDisclaimer() {
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
document.getElementById('rejectionPage').classList.remove('disclaimer-hidden');
}
// ============== HEADER CLOCK ==============
function updateHeaderClock() {
const now = new Date();
const utc = now.toISOString().substring(11, 19);
document.getElementById('headerUtcTime').textContent = utc;
}
// ============== MODE SWITCHING ==============
function switchMode(mode) {
// Stop any running scans when switching modes
if (isRunning && typeof stopDecoding === 'function') stopDecoding();
if (isSensorRunning && typeof stopSensorDecoding === 'function') stopSensorDecoding();
if (isWifiRunning && typeof stopWifiScan === 'function') stopWifiScan();
if (isBtRunning && typeof stopBtScan === 'function') stopBtScan();
if (isAdsbRunning && typeof stopAdsbScan === 'function') stopAdsbScan();
currentMode = mode;
// Remove active from all nav buttons, then add to the correct one
document.querySelectorAll('.mode-nav-btn').forEach(btn => btn.classList.remove('active'));
const modeMap = {
'pager': 'pager', 'sensor': '433', 'aircraft': 'aircraft',
'satellite': 'satellite', 'wifi': 'wifi', 'bluetooth': 'bluetooth',
'listening': 'listening', 'meshtastic': 'meshtastic'
};
document.querySelectorAll('.mode-nav-btn').forEach(btn => {
const label = btn.querySelector('.nav-label');
if (label && label.textContent.toLowerCase().includes(modeMap[mode])) {
btn.classList.add('active');
}
});
// Toggle mode content visibility
document.getElementById('pagerMode').classList.toggle('active', mode === 'pager');
document.getElementById('sensorMode').classList.toggle('active', mode === 'sensor');
document.getElementById('aircraftMode')?.classList.toggle('active', mode === 'aircraft');
document.getElementById('satelliteMode').classList.toggle('active', mode === 'satellite');
document.getElementById('wifiMode').classList.toggle('active', mode === 'wifi');
document.getElementById('bluetoothMode').classList.toggle('active', mode === 'bluetooth');
document.getElementById('listeningPostMode').classList.toggle('active', mode === 'listening');
document.getElementById('aprsMode')?.classList.toggle('active', mode === 'aprs');
document.getElementById('tscmMode')?.classList.toggle('active', mode === 'tscm');
document.getElementById('rtlamrMode')?.classList.toggle('active', mode === 'rtlamr');
document.getElementById('spystationsMode')?.classList.toggle('active', mode === 'spystations');
document.getElementById('meshtasticMode')?.classList.toggle('active', mode === 'meshtastic');
// Toggle stats visibility
document.getElementById('pagerStats').style.display = mode === 'pager' ? 'flex' : 'none';
document.getElementById('sensorStats').style.display = mode === 'sensor' ? 'flex' : 'none';
document.getElementById('aircraftStats').style.display = mode === 'aircraft' ? 'flex' : 'none';
document.getElementById('satelliteStats').style.display = mode === 'satellite' ? 'flex' : 'none';
document.getElementById('wifiStats').style.display = mode === 'wifi' ? 'flex' : 'none';
// Hide signal meter - individual panels show signal strength where needed
document.getElementById('signalMeter').style.display = 'none';
// Show/hide dashboard buttons in nav bar
document.getElementById('adsbDashboardBtn').style.display = mode === 'aircraft' ? 'inline-flex' : 'none';
document.getElementById('satelliteDashboardBtn').style.display = mode === 'satellite' ? 'inline-flex' : 'none';
// Update active mode indicator
const modeNames = {
'pager': 'PAGER',
'sensor': '433MHZ',
'aircraft': 'AIRCRAFT',
'satellite': 'SATELLITE',
'wifi': 'WIFI',
'bluetooth': 'BLUETOOTH',
'listening': 'LISTENING POST',
'tscm': 'TSCM',
'aprs': 'APRS',
'meshtastic': 'MESHTASTIC'
};
document.getElementById('activeModeIndicator').innerHTML = '<span class="pulse-dot"></span>' + modeNames[mode];
// Update mobile nav buttons
updateMobileNavButtons(mode);
// Close mobile drawer when mode is switched (on mobile)
if (window.innerWidth < 1024 && typeof window.closeMobileDrawer === 'function') {
window.closeMobileDrawer();
}
// Toggle layout containers
document.getElementById('wifiLayoutContainer').style.display = mode === 'wifi' ? 'flex' : 'none';
document.getElementById('btLayoutContainer').style.display = mode === 'bluetooth' ? 'flex' : 'none';
// Respect the "Show Radar Display" checkbox for aircraft mode
const showRadar = document.getElementById('adsbEnableMap')?.checked;
document.getElementById('aircraftVisuals').style.display = (mode === 'aircraft' && showRadar) ? 'grid' : 'none';
document.getElementById('satelliteVisuals').style.display = mode === 'satellite' ? 'block' : 'none';
document.getElementById('listeningPostVisuals').style.display = mode === 'listening' ? 'grid' : 'none';
// Update output panel title based on mode
const titles = {
'pager': 'Pager Decoder',
'sensor': '433MHz Sensor Monitor',
'aircraft': 'ADS-B Aircraft Tracker',
'satellite': 'Satellite Monitor',
'wifi': 'WiFi Scanner',
'bluetooth': 'Bluetooth Scanner',
'listening': 'Listening Post',
'meshtastic': 'Meshtastic Mesh Monitor'
};
document.getElementById('outputTitle').textContent = titles[mode] || 'Signal Monitor';
// Show/hide Device Intelligence for modes that use it
const reconBtn = document.getElementById('reconBtn');
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');
if (mode === 'satellite' || mode === 'aircraft' || mode === 'listening') {
document.getElementById('reconPanel').style.display = 'none';
if (reconBtn) reconBtn.style.display = 'none';
if (intelBtn) intelBtn.style.display = 'none';
} else {
if (reconBtn) reconBtn.style.display = 'inline-block';
if (intelBtn) intelBtn.style.display = 'inline-block';
if (typeof reconEnabled !== 'undefined' && reconEnabled) {
document.getElementById('reconPanel').style.display = 'block';
}
}
// Show RTL-SDR device section for modes that use it
document.getElementById('rtlDeviceSection').style.display =
(mode === 'pager' || mode === 'sensor' || mode === 'aircraft' || mode === 'listening') ? 'block' : 'none';
// Toggle mode-specific tool status displays
document.getElementById('toolStatusPager').style.display = (mode === 'pager') ? 'grid' : 'none';
document.getElementById('toolStatusSensor').style.display = (mode === 'sensor') ? 'grid' : 'none';
document.getElementById('toolStatusAircraft').style.display = (mode === 'aircraft') ? 'grid' : 'none';
// Hide waterfall and output console for modes with their own visualizations
document.querySelector('.waterfall-container').style.display =
(mode === 'satellite' || mode === 'listening' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
document.getElementById('output').style.display =
(mode === 'satellite' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
document.querySelector('.status-bar').style.display = (mode === 'satellite' || mode === 'tscm' || mode === 'meshtastic' || mode === 'aprs' || mode === 'spystations') ? 'none' : 'flex';
// Load interfaces and initialize visualizations when switching modes
if (mode === 'wifi') {
if (typeof refreshWifiInterfaces === 'function') refreshWifiInterfaces();
if (typeof initRadar === 'function') initRadar();
if (typeof initWatchList === 'function') initWatchList();
} else if (mode === 'bluetooth') {
if (typeof refreshBtInterfaces === 'function') refreshBtInterfaces();
if (typeof initBtRadar === 'function') initBtRadar();
} else if (mode === 'aircraft') {
if (typeof checkAdsbTools === 'function') checkAdsbTools();
if (typeof initAircraftRadar === 'function') initAircraftRadar();
} else if (mode === 'satellite') {
if (typeof initPolarPlot === 'function') initPolarPlot();
if (typeof initSatelliteList === 'function') initSatelliteList();
} else if (mode === 'listening') {
if (typeof checkScannerTools === 'function') checkScannerTools();
if (typeof checkAudioTools === 'function') checkAudioTools();
if (typeof populateScannerDeviceSelect === 'function') populateScannerDeviceSelect();
if (typeof populateAudioDeviceSelect === 'function') populateAudioDeviceSelect();
} else if (mode === 'meshtastic') {
if (typeof Meshtastic !== 'undefined' && Meshtastic.init) Meshtastic.init();
}
}
// ============== SECTION COLLAPSE ==============
function toggleSection(el) {
el.closest('.section').classList.toggle('collapsed');
}
// ============== THEME MANAGEMENT ==============
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update button text
const btn = document.getElementById('themeToggle');
if (btn) {
btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
}
}
function loadTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
const btn = document.getElementById('themeToggle');
if (btn) {
btn.textContent = savedTheme === 'light' ? '🌙' : '☀️';
}
}
// ============== AUTO-SCROLL ==============
function toggleAutoScroll() {
autoScroll = !autoScroll;
localStorage.setItem('autoScroll', autoScroll);
updateAutoScrollButton();
}
function updateAutoScrollButton() {
const btn = document.getElementById('autoScrollBtn');
if (btn) {
btn.innerHTML = autoScroll ? '⬇ AUTO-SCROLL ON' : '⬇ AUTO-SCROLL OFF';
btn.classList.toggle('active', autoScroll);
}
}
// ============== SDR DEVICE MANAGEMENT ==============
function getSelectedDevice() {
return document.getElementById('deviceSelect').value;
}
function getSelectedSDRType() {
return document.getElementById('sdrTypeSelect').value;
}
function reserveDevice(deviceIndex, modeId) {
sdrDeviceUsage[modeId] = deviceIndex;
}
function releaseDevice(modeId) {
delete sdrDeviceUsage[modeId];
}
function checkDeviceAvailability(requestingMode) {
const selectedDevice = parseInt(getSelectedDevice());
for (const [mode, device] of Object.entries(sdrDeviceUsage)) {
if (mode !== requestingMode && device === selectedDevice) {
alert(`Device ${selectedDevice} is currently in use by ${mode} mode. Please select a different device or stop the other scan first.`);
return false;
}
}
return true;
}
// ============== BIAS-T SETTINGS ==============
function saveBiasTSetting() {
const enabled = document.getElementById('biasT')?.checked || false;
localStorage.setItem('biasTEnabled', enabled);
}
function getBiasTEnabled() {
return document.getElementById('biasT')?.checked || false;
}
function loadBiasTSetting() {
const saved = localStorage.getItem('biasTEnabled');
if (saved === 'true') {
const checkbox = document.getElementById('biasT');
if (checkbox) checkbox.checked = true;
}
}
// ============== REMOTE SDR ==============
function toggleRemoteSDR() {
const useRemote = document.getElementById('useRemoteSDR').checked;
const configDiv = document.getElementById('remoteSDRConfig');
const localControls = document.querySelectorAll('#sdrTypeSelect, #deviceSelect');
if (useRemote) {
configDiv.style.display = 'block';
localControls.forEach(el => el.disabled = true);
} else {
configDiv.style.display = 'none';
localControls.forEach(el => el.disabled = false);
}
}
function getRemoteSDRConfig() {
const useRemote = document.getElementById('useRemoteSDR')?.checked;
if (!useRemote) return null;
const host = document.getElementById('rtlTcpHost')?.value || 'localhost';
const port = parseInt(document.getElementById('rtlTcpPort')?.value || '1234');
if (!host || isNaN(port)) {
alert('Please enter valid rtl_tcp host and port');
return false;
}
return { host, port };
}
// ============== OUTPUT DISPLAY ==============
function showInfo(text) {
const output = document.getElementById('output');
if (!output) return;
const placeholder = output.querySelector('.placeholder');
if (placeholder) placeholder.remove();
const infoEl = document.createElement('div');
infoEl.className = 'info-msg';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.textContent = text;
output.insertBefore(infoEl, output.firstChild);
}
function showError(text) {
const output = document.getElementById('output');
if (!output) return;
const placeholder = output.querySelector('.placeholder');
if (placeholder) placeholder.remove();
const errorEl = document.createElement('div');
errorEl.className = 'error-msg';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.textContent = '⚠ ' + text;
output.insertBefore(errorEl, output.firstChild);
}
// ============== INITIALIZATION ==============
// ============== MOBILE NAVIGATION ==============
function initMobileNav() {
const hamburgerBtn = document.getElementById('hamburgerBtn');
const sidebar = document.getElementById('mainSidebar');
const overlay = document.getElementById('drawerOverlay');
if (!hamburgerBtn || !sidebar || !overlay) return;
function openDrawer() {
sidebar.classList.add('open');
overlay.classList.add('visible');
hamburgerBtn.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeDrawer() {
sidebar.classList.remove('open');
overlay.classList.remove('visible');
hamburgerBtn.classList.remove('active');
document.body.style.overflow = '';
}
function toggleDrawer() {
if (sidebar.classList.contains('open')) {
closeDrawer();
} else {
openDrawer();
}
}
hamburgerBtn.addEventListener('click', toggleDrawer);
overlay.addEventListener('click', closeDrawer);
// Close drawer when resizing to desktop
window.addEventListener('resize', () => {
if (window.innerWidth >= 1024) {
closeDrawer();
}
});
// Expose for external use
window.toggleMobileDrawer = toggleDrawer;
window.closeMobileDrawer = closeDrawer;
}
function setViewportHeight() {
// Fix for iOS Safari address bar height
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
function updateMobileNavButtons(mode) {
// Update mobile nav bar buttons
document.querySelectorAll('.mobile-nav-btn').forEach(btn => {
const btnMode = btn.getAttribute('data-mode');
btn.classList.toggle('active', btnMode === mode);
});
}
function initApp() {
// Check disclaimer
checkDisclaimer();
// Load theme
loadTheme();
// Start clock
updateHeaderClock();
setInterval(updateHeaderClock, 1000);
// Load bias-T setting
loadBiasTSetting();
// Initialize observer location inputs
const adsbLatInput = document.getElementById('adsbObsLat');
const adsbLonInput = document.getElementById('adsbObsLon');
const obsLatInput = document.getElementById('obsLat');
const obsLonInput = document.getElementById('obsLon');
if (adsbLatInput) adsbLatInput.value = observerLocation.lat.toFixed(4);
if (adsbLonInput) adsbLonInput.value = observerLocation.lon.toFixed(4);
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
// Update UI state
updateAutoScrollButton();
// Make sections collapsible
document.querySelectorAll('.section h3').forEach(h3 => {
h3.addEventListener('click', function() {
this.parentElement.classList.toggle('collapsed');
});
});
// Collapse all sections by default (except SDR Device which is first)
document.querySelectorAll('.section').forEach((section, index) => {
if (index > 0) {
section.classList.add('collapsed');
}
});
// Initialize mobile navigation
initMobileNav();
// Set viewport height for mobile browsers
setViewportHeight();
window.addEventListener('resize', setViewportHeight);
}
// Run initialization when DOM is ready
document.addEventListener('DOMContentLoaded', initApp);
// Message storage for export
let allMessages = [];
// Track unique sensor devices
let uniqueDevices = new Set();
// SDR device usage tracking
let sdrDeviceUsage = {};
// ============== DISCLAIMER HANDLING ==============
function checkDisclaimer() {
const accepted = localStorage.getItem('disclaimerAccepted');
if (accepted === 'true') {
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
}
}
function acceptDisclaimer() {
localStorage.setItem('disclaimerAccepted', 'true');
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
}
function declineDisclaimer() {
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
document.getElementById('rejectionPage').classList.remove('disclaimer-hidden');
}
// ============== HEADER CLOCK ==============
function updateHeaderClock() {
const now = new Date();
const utc = now.toISOString().substring(11, 19);
document.getElementById('headerUtcTime').textContent = utc;
}
// ============== MODE SWITCHING ==============
function switchMode(mode) {
// Stop any running scans when switching modes
if (isRunning && typeof stopDecoding === 'function') stopDecoding();
if (isSensorRunning && typeof stopSensorDecoding === 'function') stopSensorDecoding();
if (isWifiRunning && typeof stopWifiScan === 'function') stopWifiScan();
if (isBtRunning && typeof stopBtScan === 'function') stopBtScan();
if (isAdsbRunning && typeof stopAdsbScan === 'function') stopAdsbScan();
currentMode = mode;
// Remove active from all nav buttons, then add to the correct one
document.querySelectorAll('.mode-nav-btn').forEach(btn => btn.classList.remove('active'));
const modeMap = {
'pager': 'pager', 'sensor': '433', 'aircraft': 'aircraft',
'satellite': 'satellite', 'wifi': 'wifi', 'bluetooth': 'bluetooth',
'listening': 'listening', 'meshtastic': 'meshtastic'
};
document.querySelectorAll('.mode-nav-btn').forEach(btn => {
const label = btn.querySelector('.nav-label');
if (label && label.textContent.toLowerCase().includes(modeMap[mode])) {
btn.classList.add('active');
}
});
// Toggle mode content visibility
document.getElementById('pagerMode').classList.toggle('active', mode === 'pager');
document.getElementById('sensorMode').classList.toggle('active', mode === 'sensor');
document.getElementById('aircraftMode')?.classList.toggle('active', mode === 'aircraft');
document.getElementById('satelliteMode').classList.toggle('active', mode === 'satellite');
document.getElementById('wifiMode').classList.toggle('active', mode === 'wifi');
document.getElementById('bluetoothMode').classList.toggle('active', mode === 'bluetooth');
document.getElementById('listeningPostMode').classList.toggle('active', mode === 'listening');
document.getElementById('aprsMode')?.classList.toggle('active', mode === 'aprs');
document.getElementById('tscmMode')?.classList.toggle('active', mode === 'tscm');
document.getElementById('rtlamrMode')?.classList.toggle('active', mode === 'rtlamr');
document.getElementById('spystationsMode')?.classList.toggle('active', mode === 'spystations');
document.getElementById('meshtasticMode')?.classList.toggle('active', mode === 'meshtastic');
// Toggle stats visibility
document.getElementById('pagerStats').style.display = mode === 'pager' ? 'flex' : 'none';
document.getElementById('sensorStats').style.display = mode === 'sensor' ? 'flex' : 'none';
document.getElementById('aircraftStats').style.display = mode === 'aircraft' ? 'flex' : 'none';
document.getElementById('satelliteStats').style.display = mode === 'satellite' ? 'flex' : 'none';
document.getElementById('wifiStats').style.display = mode === 'wifi' ? 'flex' : 'none';
// Hide signal meter - individual panels show signal strength where needed
document.getElementById('signalMeter').style.display = 'none';
// Show/hide dashboard buttons in nav bar
document.getElementById('adsbDashboardBtn').style.display = mode === 'aircraft' ? 'inline-flex' : 'none';
document.getElementById('satelliteDashboardBtn').style.display = mode === 'satellite' ? 'inline-flex' : 'none';
// Update active mode indicator
const modeNames = {
'pager': 'PAGER',
'sensor': '433MHZ',
'aircraft': 'AIRCRAFT',
'satellite': 'SATELLITE',
'wifi': 'WIFI',
'bluetooth': 'BLUETOOTH',
'listening': 'LISTENING POST',
'tscm': 'TSCM',
'aprs': 'APRS',
'meshtastic': 'MESHTASTIC'
};
document.getElementById('activeModeIndicator').innerHTML = '<span class="pulse-dot"></span>' + modeNames[mode];
// Update mobile nav buttons
updateMobileNavButtons(mode);
// Close mobile drawer when mode is switched (on mobile)
if (window.innerWidth < 1024 && typeof window.closeMobileDrawer === 'function') {
window.closeMobileDrawer();
}
// Toggle layout containers
document.getElementById('wifiLayoutContainer').style.display = mode === 'wifi' ? 'flex' : 'none';
document.getElementById('btLayoutContainer').style.display = mode === 'bluetooth' ? 'flex' : 'none';
// Respect the "Show Radar Display" checkbox for aircraft mode
const showRadar = document.getElementById('adsbEnableMap')?.checked;
document.getElementById('aircraftVisuals').style.display = (mode === 'aircraft' && showRadar) ? 'grid' : 'none';
document.getElementById('satelliteVisuals').style.display = mode === 'satellite' ? 'block' : 'none';
document.getElementById('listeningPostVisuals').style.display = mode === 'listening' ? 'grid' : 'none';
// Update output panel title based on mode
const titles = {
'pager': 'Pager Decoder',
'sensor': '433MHz Sensor Monitor',
'aircraft': 'ADS-B Aircraft Tracker',
'satellite': 'Satellite Monitor',
'wifi': 'WiFi Scanner',
'bluetooth': 'Bluetooth Scanner',
'listening': 'Listening Post',
'meshtastic': 'Meshtastic Mesh Monitor'
};
document.getElementById('outputTitle').textContent = titles[mode] || 'Signal Monitor';
// Show/hide Device Intelligence for modes that use it
const reconBtn = document.getElementById('reconBtn');
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');
if (mode === 'satellite' || mode === 'aircraft' || mode === 'listening') {
document.getElementById('reconPanel').style.display = 'none';
if (reconBtn) reconBtn.style.display = 'none';
if (intelBtn) intelBtn.style.display = 'none';
} else {
if (reconBtn) reconBtn.style.display = 'inline-block';
if (intelBtn) intelBtn.style.display = 'inline-block';
if (typeof reconEnabled !== 'undefined' && reconEnabled) {
document.getElementById('reconPanel').style.display = 'block';
}
}
// Show RTL-SDR device section for modes that use it
document.getElementById('rtlDeviceSection').style.display =
(mode === 'pager' || mode === 'sensor' || mode === 'aircraft' || mode === 'listening') ? 'block' : 'none';
// Toggle mode-specific tool status displays
document.getElementById('toolStatusPager').style.display = (mode === 'pager') ? 'grid' : 'none';
document.getElementById('toolStatusSensor').style.display = (mode === 'sensor') ? 'grid' : 'none';
document.getElementById('toolStatusAircraft').style.display = (mode === 'aircraft') ? 'grid' : 'none';
// Hide waterfall and output console for modes with their own visualizations
document.querySelector('.waterfall-container').style.display =
(mode === 'satellite' || mode === 'listening' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
document.getElementById('output').style.display =
(mode === 'satellite' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
document.querySelector('.status-bar').style.display = (mode === 'satellite' || mode === 'tscm' || mode === 'meshtastic' || mode === 'aprs' || mode === 'spystations') ? 'none' : 'flex';
// Load interfaces and initialize visualizations when switching modes
if (mode === 'wifi') {
if (typeof refreshWifiInterfaces === 'function') refreshWifiInterfaces();
if (typeof initRadar === 'function') initRadar();
if (typeof initWatchList === 'function') initWatchList();
} else if (mode === 'bluetooth') {
if (typeof refreshBtInterfaces === 'function') refreshBtInterfaces();
if (typeof initBtRadar === 'function') initBtRadar();
} else if (mode === 'aircraft') {
if (typeof checkAdsbTools === 'function') checkAdsbTools();
if (typeof initAircraftRadar === 'function') initAircraftRadar();
} else if (mode === 'satellite') {
if (typeof initPolarPlot === 'function') initPolarPlot();
if (typeof initSatelliteList === 'function') initSatelliteList();
} else if (mode === 'listening') {
if (typeof checkScannerTools === 'function') checkScannerTools();
if (typeof checkAudioTools === 'function') checkAudioTools();
if (typeof populateScannerDeviceSelect === 'function') populateScannerDeviceSelect();
if (typeof populateAudioDeviceSelect === 'function') populateAudioDeviceSelect();
} else if (mode === 'meshtastic') {
if (typeof Meshtastic !== 'undefined' && Meshtastic.init) Meshtastic.init();
}
}
// ============== SECTION COLLAPSE ==============
function toggleSection(el) {
el.closest('.section').classList.toggle('collapsed');
}
// ============== THEME MANAGEMENT ==============
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update button text
const btn = document.getElementById('themeToggle');
if (btn) {
btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
}
}
function loadTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
const btn = document.getElementById('themeToggle');
if (btn) {
btn.textContent = savedTheme === 'light' ? '🌙' : '☀️';
}
}
// ============== AUTO-SCROLL ==============
function toggleAutoScroll() {
autoScroll = !autoScroll;
localStorage.setItem('autoScroll', autoScroll);
updateAutoScrollButton();
}
function updateAutoScrollButton() {
const btn = document.getElementById('autoScrollBtn');
if (btn) {
btn.innerHTML = autoScroll ? '⬇ AUTO-SCROLL ON' : '⬇ AUTO-SCROLL OFF';
btn.classList.toggle('active', autoScroll);
}
}
// ============== SDR DEVICE MANAGEMENT ==============
function getSelectedDevice() {
return document.getElementById('deviceSelect').value;
}
function getSelectedSDRType() {
return document.getElementById('sdrTypeSelect').value;
}
function reserveDevice(deviceIndex, modeId) {
sdrDeviceUsage[modeId] = deviceIndex;
}
function releaseDevice(modeId) {
delete sdrDeviceUsage[modeId];
}
function checkDeviceAvailability(requestingMode) {
const selectedDevice = parseInt(getSelectedDevice());
for (const [mode, device] of Object.entries(sdrDeviceUsage)) {
if (mode !== requestingMode && device === selectedDevice) {
alert(`Device ${selectedDevice} is currently in use by ${mode} mode. Please select a different device or stop the other scan first.`);
return false;
}
}
return true;
}
// ============== BIAS-T SETTINGS ==============
function saveBiasTSetting() {
const enabled = document.getElementById('biasT')?.checked || false;
localStorage.setItem('biasTEnabled', enabled);
}
function getBiasTEnabled() {
return document.getElementById('biasT')?.checked || false;
}
function loadBiasTSetting() {
const saved = localStorage.getItem('biasTEnabled');
if (saved === 'true') {
const checkbox = document.getElementById('biasT');
if (checkbox) checkbox.checked = true;
}
}
// ============== REMOTE SDR ==============
function toggleRemoteSDR() {
const useRemote = document.getElementById('useRemoteSDR').checked;
const configDiv = document.getElementById('remoteSDRConfig');
const localControls = document.querySelectorAll('#sdrTypeSelect, #deviceSelect');
if (useRemote) {
configDiv.style.display = 'block';
localControls.forEach(el => el.disabled = true);
} else {
configDiv.style.display = 'none';
localControls.forEach(el => el.disabled = false);
}
}
function getRemoteSDRConfig() {
const useRemote = document.getElementById('useRemoteSDR')?.checked;
if (!useRemote) return null;
const host = document.getElementById('rtlTcpHost')?.value || 'localhost';
const port = parseInt(document.getElementById('rtlTcpPort')?.value || '1234');
if (!host || isNaN(port)) {
alert('Please enter valid rtl_tcp host and port');
return false;
}
return { host, port };
}
// ============== OUTPUT DISPLAY ==============
function showInfo(text) {
const output = document.getElementById('output');
if (!output) return;
const placeholder = output.querySelector('.placeholder');
if (placeholder) placeholder.remove();
const infoEl = document.createElement('div');
infoEl.className = 'info-msg';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "Terminus", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.textContent = text;
output.insertBefore(infoEl, output.firstChild);
}
function showError(text) {
const output = document.getElementById('output');
if (!output) return;
const placeholder = output.querySelector('.placeholder');
if (placeholder) placeholder.remove();
const errorEl = document.createElement('div');
errorEl.className = 'error-msg';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "Terminus", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.textContent = '⚠ ' + text;
output.insertBefore(errorEl, output.firstChild);
}
// ============== INITIALIZATION ==============
// ============== MOBILE NAVIGATION ==============
function initMobileNav() {
const hamburgerBtn = document.getElementById('hamburgerBtn');
const sidebar = document.getElementById('mainSidebar');
const overlay = document.getElementById('drawerOverlay');
if (!hamburgerBtn || !sidebar || !overlay) return;
function openDrawer() {
sidebar.classList.add('open');
overlay.classList.add('visible');
hamburgerBtn.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeDrawer() {
sidebar.classList.remove('open');
overlay.classList.remove('visible');
hamburgerBtn.classList.remove('active');
document.body.style.overflow = '';
}
function toggleDrawer() {
if (sidebar.classList.contains('open')) {
closeDrawer();
} else {
openDrawer();
}
}
hamburgerBtn.addEventListener('click', toggleDrawer);
overlay.addEventListener('click', closeDrawer);
// Close drawer when resizing to desktop
window.addEventListener('resize', () => {
if (window.innerWidth >= 1024) {
closeDrawer();
}
});
// Expose for external use
window.toggleMobileDrawer = toggleDrawer;
window.closeMobileDrawer = closeDrawer;
}
function setViewportHeight() {
// Fix for iOS Safari address bar height
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
function updateMobileNavButtons(mode) {
// Update mobile nav bar buttons
document.querySelectorAll('.mobile-nav-btn').forEach(btn => {
const btnMode = btn.getAttribute('data-mode');
btn.classList.toggle('active', btnMode === mode);
});
}
function initApp() {
// Check disclaimer
checkDisclaimer();
// Load theme
loadTheme();
// Start clock
updateHeaderClock();
setInterval(updateHeaderClock, 1000);
// Load bias-T setting
loadBiasTSetting();
// Initialize observer location inputs
const adsbLatInput = document.getElementById('adsbObsLat');
const adsbLonInput = document.getElementById('adsbObsLon');
const obsLatInput = document.getElementById('obsLat');
const obsLonInput = document.getElementById('obsLon');
if (adsbLatInput) adsbLatInput.value = observerLocation.lat.toFixed(4);
if (adsbLonInput) adsbLonInput.value = observerLocation.lon.toFixed(4);
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
// Update UI state
updateAutoScrollButton();
// Make sections collapsible
document.querySelectorAll('.section h3').forEach(h3 => {
h3.addEventListener('click', function() {
this.parentElement.classList.toggle('collapsed');
});
});
// Collapse all sections by default (except SDR Device which is first)
document.querySelectorAll('.section').forEach((section, index) => {
if (index > 0) {
section.classList.add('collapsed');
}
});
// Initialize mobile navigation
initMobileNav();
// Set viewport height for mobile browsers
setViewportHeight();
window.addEventListener('resize', setViewportHeight);
}
// Run initialization when DOM is ready
document.addEventListener('DOMContentLoaded', initApp);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -84,7 +84,7 @@ const SpyStations = (function() {
modeContainer.innerHTML = modes.map(m => `
<label class="inline-checkbox">
<input type="checkbox" data-mode="${m}" checked onchange="SpyStations.applyFilters()">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 10px;">${m}</span>
<span style="font-family: 'Terminus', monospace; font-size: 10px;">${m}</span>
</label>
`).join('');
}

BIN
static/vendor/fonts/Terminus.ttf vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -466,7 +466,7 @@
if (!points.length) {
ctx.fillStyle = 'rgba(156, 163, 175, 0.6)';
ctx.font = '12px "JetBrains Mono", monospace';
ctx.font = '12px "Terminus", monospace';
ctx.fillText(`No ${label.toLowerCase()} data`, 12, height / 2);
return;
}
@@ -474,7 +474,7 @@
const series = points.map(p => p[field]).filter(v => v !== null && v !== undefined);
if (!series.length) {
ctx.fillStyle = 'rgba(156, 163, 175, 0.6)';
ctx.font = '12px "JetBrains Mono", monospace';
ctx.font = '12px "Terminus", monospace';
ctx.fillText(`No ${label.toLowerCase()} data`, 12, height / 2);
return;
}
@@ -515,7 +515,7 @@
}
ctx.fillStyle = 'rgba(226, 232, 240, 0.8)';
ctx.font = '11px "JetBrains Mono", monospace';
ctx.font = '11px "Terminus", monospace';
ctx.fillText(`${maxVal} ${unit}`, 12, padding);
ctx.fillText(`${minVal} ${unit}`, 12, height - padding);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1499,7 +1499,7 @@
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono);
cursor: pointer;
}
.agent-select-sm:focus {

View File

@@ -290,7 +290,7 @@
╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚══════╝╚═════╝</pre>
<div style="margin: 25px 0; padding: 15px; background: #0a0a0a; border-left: 3px solid var(--accent-red);">
<p
style="font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #888; text-align: left; margin: 0;">
style="font-family: var(--font-mono); font-size: 11px; color: #888; text-align: left; margin: 0;">
<span style="color: var(--accent-red);">root@intercepted:</span><span
style="color: var(--accent-cyan);">~#</span> sudo access --grant-permission<br>
<span style="color: #666;">[sudo] password for user: ********</span><br>
@@ -989,7 +989,7 @@
style="color: var(--accent-orange); text-shadow: 0 0 10px var(--accent-orange); margin-bottom: 8px;">
PACKET LOG</h5>
<div id="aprsPacketLog"
style="flex: 1; overflow-y: auto; font-family: 'JetBrains Mono', monospace; font-size: 10px; background: rgba(0,0,0,0.3); padding: 8px; border-radius: 4px;">
style="flex: 1; overflow-y: auto; font-family: var(--font-mono); font-size: 10px; background: rgba(0,0,0,0.3); padding: 8px; border-radius: 4px;">
<div style="color: var(--text-muted);">Waiting for packets...</div>
</div>
</div>
@@ -1009,7 +1009,7 @@
STOPPED</div>
<div style="display: flex; justify-content: center; align-items: baseline; gap: 8px;">
<div class="freq-digits" id="mainScannerFreq"
style="font-size: 52px; font-weight: bold; color: var(--accent-cyan); text-shadow: 0 0 30px var(--accent-cyan); font-family: 'JetBrains Mono', monospace; letter-spacing: 3px;">
style="font-size: 52px; font-weight: bold; color: var(--accent-cyan); text-shadow: 0 0 30px var(--accent-cyan); font-family: var(--font-mono); letter-spacing: 3px;">
118.000</div>
<span class="freq-unit"
style="font-size: 20px; color: var(--text-secondary); font-weight: 500;">MHz</span>
@@ -1237,7 +1237,7 @@
START</div>
<input type="number" id="radioScanStart" value="118" step="0.1"
class="radio-input"
style="width: 100%; font-size: 16px; padding: 8px 6px; text-align: center; font-family: 'JetBrains Mono', monospace; font-weight: bold; color: var(--accent-cyan);">
style="width: 100%; font-size: 16px; padding: 8px 6px; text-align: center; font-family: var(--font-mono); font-weight: bold; color: var(--accent-cyan);">
</div>
<span
style="color: var(--text-muted); font-size: 16px; padding-top: 12px;"></span>
@@ -1246,7 +1246,7 @@
END</div>
<input type="number" id="radioScanEnd" value="137" step="0.1"
class="radio-input"
style="width: 100%; font-size: 16px; padding: 8px 6px; text-align: center; font-family: 'JetBrains Mono', monospace; font-weight: bold; color: var(--accent-cyan);">
style="width: 100%; font-size: 16px; padding: 8px 6px; text-align: center; font-family: var(--font-mono); font-weight: bold; color: var(--accent-cyan);">
</div>
</div>
</div>
@@ -1312,19 +1312,19 @@
<div style="display: flex; align-items: center; justify-content: space-between;">
<span style="font-size: 9px; color: var(--text-muted);">SIGNALS</span>
<span
style="color: var(--accent-green); font-size: 18px; font-weight: bold; font-family: 'JetBrains Mono', monospace;"
style="color: var(--accent-green); font-size: 18px; font-weight: bold; font-family: var(--font-mono);"
id="mainSignalCount">0</span>
</div>
<div style="display: flex; align-items: center; justify-content: space-between;">
<span style="font-size: 9px; color: var(--text-muted);">SCANNED</span>
<span
style="color: var(--accent-cyan); font-size: 18px; font-weight: bold; font-family: 'JetBrains Mono', monospace;"
style="color: var(--accent-cyan); font-size: 18px; font-weight: bold; font-family: var(--font-mono);"
id="mainFreqsScanned">0</span>
</div>
<div style="display: flex; align-items: center; justify-content: space-between;">
<span style="font-size: 9px; color: var(--text-muted);">CYCLES</span>
<span
style="color: var(--accent-orange); font-size: 18px; font-weight: bold; font-family: 'JetBrains Mono', monospace;"
style="color: var(--accent-orange); font-size: 18px; font-weight: bold; font-family: var(--font-mono);"
id="mainScanCycles">0</span>
</div>
</div>
@@ -4223,7 +4223,7 @@
const infoEl = document.createElement('div');
infoEl.className = 'info-msg';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "Terminus", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.textContent = text;
output.insertBefore(infoEl, output.firstChild);
}
@@ -4239,7 +4239,7 @@
const errorEl = document.createElement('div');
errorEl.className = 'error-msg';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "Terminus", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.textContent = '⚠ ' + text;
output.insertBefore(errorEl, output.firstChild);
}
@@ -6855,7 +6855,7 @@
// Draw total in center
ctx.fillStyle = '#fff';
ctx.font = 'bold 16px JetBrains Mono';
ctx.font = 'bold 16px Terminus';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(total, cx, cy);
@@ -8625,7 +8625,7 @@
// Label
if (el > 0) {
ctx.fillStyle = '#444';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Terminus';
ctx.textAlign = 'center';
ctx.fillText(el + '°', cx, cy - r + 12);
}
@@ -8698,7 +8698,7 @@
// Label
ctx.fillStyle = '#fff';
ctx.font = '11px JetBrains Mono';
ctx.font = '11px Terminus';
ctx.fillText(pass.satellite, maxX + 10, maxY - 5);
}
}
@@ -12145,7 +12145,7 @@
<textarea id="tleInput" placeholder="ISS (ZARYA)
1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.9163 0006703 296.5855 63.4606 15.49995465478450"
style="width: 100%; height: 150px; background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; font-family: 'JetBrains Mono', monospace; font-size: 11px; resize: vertical;"></textarea>
style="width: 100%; height: 150px; background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; font-family: var(--font-mono); font-size: 11px; resize: vertical;"></textarea>
<button class="preset-btn" onclick="addFromTLE()" style="margin-top: 10px; width: 100%;">Add
Satellite</button>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Frequency</span>
<span id="lpQuickFreq" style="font-size: 14px; font-family: 'JetBrains Mono', monospace; color: var(--text-primary);">---.--- MHz</span>
<span id="lpQuickFreq" style="font-size: 14px; font-family: var(--font-mono); color: var(--text-primary);">---.--- MHz</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Signals</span>

View File

@@ -1,318 +1,318 @@
<!-- Settings Modal -->
<div id="settingsModal" class="settings-modal" onclick="if(event.target === this) hideSettings()">
<div class="settings-content">
<div class="settings-header">
<h2>
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
Settings
</h2>
<button class="settings-close" onclick="hideSettings()">&times;</button>
</div>
<div class="settings-tabs">
<button class="settings-tab active" data-tab="offline" onclick="switchSettingsTab('offline')">Offline</button>
<button class="settings-tab" data-tab="location" onclick="switchSettingsTab('location')">Location</button>
<button class="settings-tab" data-tab="display" onclick="switchSettingsTab('display')">Display</button>
<button class="settings-tab" data-tab="updates" onclick="switchSettingsTab('updates')">Updates</button>
<button class="settings-tab" data-tab="tools" onclick="switchSettingsTab('tools')">Tools</button>
<button class="settings-tab" data-tab="about" onclick="switchSettingsTab('about')">About</button>
</div>
<!-- Offline Section -->
<div id="settings-offline" class="settings-section active">
<div class="settings-group">
<div class="settings-group-title">Offline Mode</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Enable Offline Mode</span>
<span class="settings-label-desc">Use local assets instead of CDN</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="offlineEnabled" onchange="Settings.toggleOfflineMode(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Asset Sources</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">JavaScript/CSS Libraries</span>
<span class="settings-label-desc">Leaflet, Chart.js</span>
</div>
<select id="assetsSource" class="settings-select" onchange="Settings.setAssetSource(this.value)">
<option value="cdn">CDN (Online)</option>
<option value="local">Local</option>
</select>
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Web Fonts</span>
<span class="settings-label-desc">Inter, JetBrains Mono</span>
</div>
<select id="fontsSource" class="settings-select" onchange="Settings.setFontsSource(this.value)">
<option value="cdn">Google Fonts (Online)</option>
<option value="local">Local</option>
</select>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Map Tiles</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Tile Provider</span>
<span class="settings-label-desc">Map background imagery</span>
</div>
<select id="tileProvider" class="settings-select" onchange="Settings.setTileProvider(this.value)">
<option value="openstreetmap">OpenStreetMap</option>
<option value="cartodb_dark">CartoDB Dark</option>
<option value="cartodb_light">CartoDB Positron</option>
<option value="esri_world">ESRI World Imagery</option>
<option value="custom">Custom URL</option>
</select>
</div>
<div class="settings-row custom-url-row" id="customTileUrlRow" style="display: none;">
<div class="settings-label" style="width: 100%;">
<span class="settings-label-text">Custom Tile URL</span>
<span class="settings-label-desc">e.g., http://localhost:8080/{z}/{x}/{y}.png</span>
<input type="text" id="customTileUrl" class="settings-input"
placeholder="http://tile-server/{z}/{x}/{y}.png"
onchange="Settings.setCustomTileUrl(this.value)">
</div>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Local Asset Status</div>
<div class="asset-status" id="assetStatus">
<div class="asset-status-row">
<span class="asset-name">Leaflet JS/CSS</span>
<span class="asset-badge checking" id="statusLeaflet">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">Chart.js</span>
<span class="asset-badge checking" id="statusChartjs">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">Inter Font</span>
<span class="asset-badge checking" id="statusInter">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">JetBrains Mono</span>
<span class="asset-badge checking" id="statusJetbrains">Checking...</span>
</div>
</div>
<button class="check-assets-btn" onclick="Settings.checkAssets()">
Check Assets
</button>
</div>
<div class="settings-info">
<strong>Note:</strong> Changes to asset sources require a page reload to take effect.
Local assets must be available in <code>/static/vendor/</code>.
</div>
</div>
<!-- Location Section -->
<div id="settings-location" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Observer Location</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
Set your geographic coordinates for satellite pass predictions and ISS tracking.
</p>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Latitude</span>
<span class="settings-label-desc">Decimal degrees (-90 to 90)</span>
</div>
<input type="number" id="observerLatInput" class="settings-input"
step="0.0001" min="-90" max="90" placeholder="51.5074"
style="width: 120px; text-align: right;">
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Longitude</span>
<span class="settings-label-desc">Decimal degrees (-180 to 180)</span>
</div>
<input type="number" id="observerLonInput" class="settings-input"
step="0.0001" min="-180" max="180" placeholder="-0.1278"
style="width: 120px; text-align: right;">
</div>
<div style="display: flex; gap: 10px; margin-top: 15px;">
<button class="check-assets-btn" onclick="detectLocationGPS(this)" style="flex: 1;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px;">
<circle cx="12" cy="12" r="10"/>
<circle cx="12" cy="12" r="3"/>
<line x1="12" y1="2" x2="12" y2="6"/>
<line x1="12" y1="18" x2="12" y2="22"/>
<line x1="2" y1="12" x2="6" y2="12"/>
<line x1="18" y1="12" x2="22" y2="12"/>
</svg>
Use GPS
</button>
<button class="check-assets-btn" onclick="saveObserverLocation()" style="flex: 1; background: var(--accent-cyan); color: #000;">
Save Location
</button>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Current Location</div>
<div id="currentLocationDisplay" style="padding: 12px; background: var(--bg-tertiary); border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 12px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span style="color: var(--text-dim);">Latitude</span>
<span id="currentLatDisplay" style="color: var(--accent-cyan);">Not set</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: var(--text-dim);">Longitude</span>
<span id="currentLonDisplay" style="color: var(--accent-cyan);">Not set</span>
</div>
</div>
</div>
<div class="settings-info">
<strong>Note:</strong> Location is used for ISS pass predictions in SSTV mode and satellite tracking.
Your location is stored locally and never sent to external servers.
</div>
</div>
<!-- Display Section -->
<div id="settings-display" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Visual Preferences</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Theme</span>
<span class="settings-label-desc">Color scheme preference</span>
</div>
<select id="themeSelect" class="settings-select" onchange="setThemePreference(this.value)">
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Animations</span>
<span class="settings-label-desc">Enable visual effects and animations</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="animationsEnabled" checked onchange="setAnimationsEnabled(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<!-- Updates Section -->
<div id="settings-updates" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Update Status</div>
<div id="updateStatusContent" style="padding: 10px 0;">
<div style="text-align: center; padding: 20px; color: var(--text-dim);">
Loading update status...
</div>
</div>
<button class="check-assets-btn" onclick="checkForUpdatesManual()" style="margin-top: 10px;">
Check Now
</button>
</div>
<div class="settings-group">
<div class="settings-group-title">Update Settings</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Auto-Check for Updates</span>
<span class="settings-label-desc">Periodically check GitHub for new releases</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="updateCheckEnabled" checked onchange="toggleUpdateCheck(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-info">
<strong>Note:</strong> Updates are fetched from GitHub and applied via git pull.
Make sure you have git installed and the application is in a git repository.
</div>
</div>
<!-- Tools Section -->
<div id="settings-tools" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Tool Dependencies</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
Check which external tools are installed for each mode.
<span style="color: var(--accent-green);"></span> = Installed,
<span style="color: var(--accent-red);"></span> = Missing
</p>
<div id="settingsToolsContent" style="max-height: 45vh; overflow-y: auto;">
<div style="text-align: center; padding: 30px; color: var(--text-dim);">
Loading dependencies...
</div>
</div>
</div>
<div class="settings-group" style="margin-top: 15px;">
<div class="settings-group-title">Quick Install (Debian/Ubuntu)</div>
<div style="background: var(--bg-tertiary); padding: 10px; border-radius: 4px; font-family: var(--font-mono); font-size: 10px; overflow-x: auto;">
<div>sudo apt install rtl-sdr multimon-ng rtl-433 aircrack-ng bluez dump1090-mutability hcxdumptool hcxtools</div>
<div style="margin-top: 5px;">pip install skyfield flask</div>
</div>
<div style="margin-top: 10px; font-size: 11px; color: var(--text-dim);">
<strong>Note:</strong> ACARS decoding requires <code>acarsdec</code> which must be built from source.
See <a href="https://github.com/TLeconte/acarsdec" target="_blank" style="color: var(--accent-cyan);">github.com/TLeconte/acarsdec</a> or run <code>./setup.sh</code> for automated installation.
</div>
</div>
</div>
<!-- About Section -->
<div id="settings-about" class="settings-section">
<div class="settings-group">
<div class="about-info">
<p><strong>iNTERCEPT</strong> - Signal Intelligence Platform</p>
<p>Version: <span class="about-version">{{ version }}</span></p>
<p>
A unified web interface for software-defined radio (SDR) tools,
supporting pager decoding, sensor monitoring, aircraft tracking,
WiFi/Bluetooth scanning, and more.
</p>
<p>
<a href="https://github.com/smittix/intercept" target="_blank">GitHub Repository</a>
</p>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Support the Project</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
If you find iNTERCEPT useful, consider supporting its development.
</p>
<a href="https://buymeacoffee.com/smittix" target="_blank" rel="noopener noreferrer" class="donate-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 18px; height: 18px; vertical-align: -3px; margin-right: 8px;">
<path d="M17 8h1a4 4 0 1 1 0 8h-1"/>
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/>
<line x1="6" y1="2" x2="6" y2="4"/>
<line x1="10" y1="2" x2="10" y2="4"/>
<line x1="14" y1="2" x2="14" y2="4"/>
</svg>
Buy Me a Coffee
</a>
</div>
</div>
</div>
</div>
<!-- Settings Modal -->
<div id="settingsModal" class="settings-modal" onclick="if(event.target === this) hideSettings()">
<div class="settings-content">
<div class="settings-header">
<h2>
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
Settings
</h2>
<button class="settings-close" onclick="hideSettings()">&times;</button>
</div>
<div class="settings-tabs">
<button class="settings-tab active" data-tab="offline" onclick="switchSettingsTab('offline')">Offline</button>
<button class="settings-tab" data-tab="location" onclick="switchSettingsTab('location')">Location</button>
<button class="settings-tab" data-tab="display" onclick="switchSettingsTab('display')">Display</button>
<button class="settings-tab" data-tab="updates" onclick="switchSettingsTab('updates')">Updates</button>
<button class="settings-tab" data-tab="tools" onclick="switchSettingsTab('tools')">Tools</button>
<button class="settings-tab" data-tab="about" onclick="switchSettingsTab('about')">About</button>
</div>
<!-- Offline Section -->
<div id="settings-offline" class="settings-section active">
<div class="settings-group">
<div class="settings-group-title">Offline Mode</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Enable Offline Mode</span>
<span class="settings-label-desc">Use local assets instead of CDN</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="offlineEnabled" onchange="Settings.toggleOfflineMode(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Asset Sources</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">JavaScript/CSS Libraries</span>
<span class="settings-label-desc">Leaflet, Chart.js</span>
</div>
<select id="assetsSource" class="settings-select" onchange="Settings.setAssetSource(this.value)">
<option value="cdn">CDN (Online)</option>
<option value="local">Local</option>
</select>
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Web Fonts</span>
<span class="settings-label-desc">Inter, Terminus</span>
</div>
<select id="fontsSource" class="settings-select" onchange="Settings.setFontsSource(this.value)">
<option value="cdn">Google Fonts (Online)</option>
<option value="local">Local</option>
</select>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Map Tiles</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Tile Provider</span>
<span class="settings-label-desc">Map background imagery</span>
</div>
<select id="tileProvider" class="settings-select" onchange="Settings.setTileProvider(this.value)">
<option value="openstreetmap">OpenStreetMap</option>
<option value="cartodb_dark">CartoDB Dark</option>
<option value="cartodb_light">CartoDB Positron</option>
<option value="esri_world">ESRI World Imagery</option>
<option value="custom">Custom URL</option>
</select>
</div>
<div class="settings-row custom-url-row" id="customTileUrlRow" style="display: none;">
<div class="settings-label" style="width: 100%;">
<span class="settings-label-text">Custom Tile URL</span>
<span class="settings-label-desc">e.g., http://localhost:8080/{z}/{x}/{y}.png</span>
<input type="text" id="customTileUrl" class="settings-input"
placeholder="http://tile-server/{z}/{x}/{y}.png"
onchange="Settings.setCustomTileUrl(this.value)">
</div>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Local Asset Status</div>
<div class="asset-status" id="assetStatus">
<div class="asset-status-row">
<span class="asset-name">Leaflet JS/CSS</span>
<span class="asset-badge checking" id="statusLeaflet">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">Chart.js</span>
<span class="asset-badge checking" id="statusChartjs">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">Inter Font</span>
<span class="asset-badge checking" id="statusInter">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">Terminus</span>
<span class="asset-badge checking" id="statusJetbrains">Checking...</span>
</div>
</div>
<button class="check-assets-btn" onclick="Settings.checkAssets()">
Check Assets
</button>
</div>
<div class="settings-info">
<strong>Note:</strong> Changes to asset sources require a page reload to take effect.
Local assets must be available in <code>/static/vendor/</code>.
</div>
</div>
<!-- Location Section -->
<div id="settings-location" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Observer Location</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
Set your geographic coordinates for satellite pass predictions and ISS tracking.
</p>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Latitude</span>
<span class="settings-label-desc">Decimal degrees (-90 to 90)</span>
</div>
<input type="number" id="observerLatInput" class="settings-input"
step="0.0001" min="-90" max="90" placeholder="51.5074"
style="width: 120px; text-align: right;">
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Longitude</span>
<span class="settings-label-desc">Decimal degrees (-180 to 180)</span>
</div>
<input type="number" id="observerLonInput" class="settings-input"
step="0.0001" min="-180" max="180" placeholder="-0.1278"
style="width: 120px; text-align: right;">
</div>
<div style="display: flex; gap: 10px; margin-top: 15px;">
<button class="check-assets-btn" onclick="detectLocationGPS(this)" style="flex: 1;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px;">
<circle cx="12" cy="12" r="10"/>
<circle cx="12" cy="12" r="3"/>
<line x1="12" y1="2" x2="12" y2="6"/>
<line x1="12" y1="18" x2="12" y2="22"/>
<line x1="2" y1="12" x2="6" y2="12"/>
<line x1="18" y1="12" x2="22" y2="12"/>
</svg>
Use GPS
</button>
<button class="check-assets-btn" onclick="saveObserverLocation()" style="flex: 1; background: var(--accent-cyan); color: #000;">
Save Location
</button>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Current Location</div>
<div id="currentLocationDisplay" style="padding: 12px; background: var(--bg-tertiary); border-radius: 6px; font-family: var(--font-mono); font-size: 12px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span style="color: var(--text-dim);">Latitude</span>
<span id="currentLatDisplay" style="color: var(--accent-cyan);">Not set</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: var(--text-dim);">Longitude</span>
<span id="currentLonDisplay" style="color: var(--accent-cyan);">Not set</span>
</div>
</div>
</div>
<div class="settings-info">
<strong>Note:</strong> Location is used for ISS pass predictions in SSTV mode and satellite tracking.
Your location is stored locally and never sent to external servers.
</div>
</div>
<!-- Display Section -->
<div id="settings-display" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Visual Preferences</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Theme</span>
<span class="settings-label-desc">Color scheme preference</span>
</div>
<select id="themeSelect" class="settings-select" onchange="setThemePreference(this.value)">
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Animations</span>
<span class="settings-label-desc">Enable visual effects and animations</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="animationsEnabled" checked onchange="setAnimationsEnabled(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<!-- Updates Section -->
<div id="settings-updates" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Update Status</div>
<div id="updateStatusContent" style="padding: 10px 0;">
<div style="text-align: center; padding: 20px; color: var(--text-dim);">
Loading update status...
</div>
</div>
<button class="check-assets-btn" onclick="checkForUpdatesManual()" style="margin-top: 10px;">
Check Now
</button>
</div>
<div class="settings-group">
<div class="settings-group-title">Update Settings</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Auto-Check for Updates</span>
<span class="settings-label-desc">Periodically check GitHub for new releases</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="updateCheckEnabled" checked onchange="toggleUpdateCheck(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-info">
<strong>Note:</strong> Updates are fetched from GitHub and applied via git pull.
Make sure you have git installed and the application is in a git repository.
</div>
</div>
<!-- Tools Section -->
<div id="settings-tools" class="settings-section">
<div class="settings-group">
<div class="settings-group-title">Tool Dependencies</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
Check which external tools are installed for each mode.
<span style="color: var(--accent-green);"></span> = Installed,
<span style="color: var(--accent-red);"></span> = Missing
</p>
<div id="settingsToolsContent" style="max-height: 45vh; overflow-y: auto;">
<div style="text-align: center; padding: 30px; color: var(--text-dim);">
Loading dependencies...
</div>
</div>
</div>
<div class="settings-group" style="margin-top: 15px;">
<div class="settings-group-title">Quick Install (Debian/Ubuntu)</div>
<div style="background: var(--bg-tertiary); padding: 10px; border-radius: 4px; font-family: var(--font-mono); font-size: 10px; overflow-x: auto;">
<div>sudo apt install rtl-sdr multimon-ng rtl-433 aircrack-ng bluez dump1090-mutability hcxdumptool hcxtools</div>
<div style="margin-top: 5px;">pip install skyfield flask</div>
</div>
<div style="margin-top: 10px; font-size: 11px; color: var(--text-dim);">
<strong>Note:</strong> ACARS decoding requires <code>acarsdec</code> which must be built from source.
See <a href="https://github.com/TLeconte/acarsdec" target="_blank" style="color: var(--accent-cyan);">github.com/TLeconte/acarsdec</a> or run <code>./setup.sh</code> for automated installation.
</div>
</div>
</div>
<!-- About Section -->
<div id="settings-about" class="settings-section">
<div class="settings-group">
<div class="about-info">
<p><strong>iNTERCEPT</strong> - Signal Intelligence Platform</p>
<p>Version: <span class="about-version">{{ version }}</span></p>
<p>
A unified web interface for software-defined radio (SDR) tools,
supporting pager decoding, sensor monitoring, aircraft tracking,
WiFi/Bluetooth scanning, and more.
</p>
<p>
<a href="https://github.com/smittix/intercept" target="_blank">GitHub Repository</a>
</p>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Support the Project</div>
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
If you find iNTERCEPT useful, consider supporting its development.
</p>
<a href="https://buymeacoffee.com/smittix" target="_blank" rel="noopener noreferrer" class="donate-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 18px; height: 18px; vertical-align: -3px; margin-right: 8px;">
<path d="M17 8h1a4 4 0 1 1 0 8h-1"/>
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/>
<line x1="6" y1="2" x2="6" y2="4"/>
<line x1="10" y1="2" x2="10" y2="4"/>
<line x1="14" y1="2" x2="14" y2="4"/>
</svg>
Buy Me a Coffee
</a>
</div>
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff