fix: resolve two-window hang and sweep UI/theming updates

Fix app becoming unresponsive when two browser windows are open: the
root cause was HTTP/1.1 connection pool exhaustion (6-connection limit
per origin). VoiceAlerts was opening 3 SSE streams per window by
default, so two windows produced 8 connections and permanently starved
all regular HTTP requests.

- voice-alerts.js: default all streams to false (opt-in) to stay within
  the browser connection limit; existing user preferences in localStorage
  are preserved
- routes/alerts.py: replace direct AlertManager.stream_events() with
  sse_stream_fanout so both windows receive every alert instead of
  competing for the same queue
- routes/bluetooth_v2.py: same fanout fix via subscribe_fanout_queue,
  preserving named SSE events (device_update, scan_started, etc.)

Also includes accumulated UI/theming changes: accent-cyan CSS variable
sweep across mode CSS/JS files, standalone dashboard pages, template
updates, satellite TLE data refresh, and tile provider default rename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-05-20 22:01:10 +01:00
parent 5100f55586
commit a3f2fa7b88
48 changed files with 1524 additions and 943 deletions
+3 -3
View File
@@ -209,7 +209,7 @@ def inject_offline_settings():
"enabled": get_setting("offline.enabled", False), "enabled": get_setting("offline.enabled", False),
"assets_source": assets_source, "assets_source": assets_source,
"fonts_source": fonts_source, "fonts_source": fonts_source,
"tile_provider": get_setting("offline.tile_provider", "cartodb_dark_cyan"), "tile_provider": get_setting("offline.tile_provider", "cartodb_dark_nolabels"),
"tile_server_url": get_setting("offline.tile_server_url", ""), "tile_server_url": get_setting("offline.tile_server_url", ""),
} }
} }
@@ -440,8 +440,8 @@ def get_sdr_device_status() -> dict[str, str]:
@app.before_request @app.before_request
def require_login(): def require_login():
# Skip auth entirely when INTERCEPT_DISABLE_AUTH is set # Skip auth entirely when INTERCEPT_DISABLE_AUTH is set
if os.environ.get('INTERCEPT_DISABLE_AUTH', '').lower() in ('1', 'true', 'yes'): if os.environ.get("INTERCEPT_DISABLE_AUTH", "").lower() in ("1", "true", "yes"):
session['logged_in'] = True session["logged_in"] = True
return None return None
# Routes that don't require login (to avoid infinite redirect loop) # Routes that don't require login (to avoid infinite redirect loop)
+45 -27
View File
@@ -2,31 +2,49 @@
# To update: click "Update TLE" in satellite dashboard or SSTV mode # To update: click "Update TLE" in satellite dashboard or SSTV mode
# Data source: CelesTrak (celestrak.org) # Data source: CelesTrak (celestrak.org)
TLE_SATELLITES = { TLE_SATELLITES = {
'ISS': ('ISS (ZARYA)', "ISS": (
'1 25544U 98067A 25029.51432176 .00020818 00000+0 36919-3 0 9991', "ISS (ZARYA)",
'2 25544 51.6400 157.5640 0002671 123.5041 236.6291 15.49988902492099'), "1 25544U 98067A 26140.52007258 .00005164 00000+0 10084-3 0 9993",
'NOAA-15': ('NOAA 15', "2 25544 51.6328 77.0641 0007497 79.3410 280.8422 15.49283153567468",
'1 25338U 98030A 25028.84157420 .00000535 00000+0 26168-3 0 9999', ),
'2 25338 98.5676 356.1853 0009968 282.2567 77.7505 14.26225252390049'), "NOAA-15": (
'NOAA-18': ('NOAA 18', "NOAA 15",
'1 28654U 05018A 25028.87364583 .00000454 00000+0 25082-3 0 9996', "1 25338U 98030A 25028.84157420 .00000535 00000+0 26168-3 0 9999",
'2 28654 98.8801 59.1618 0013609 281.7181 78.2479 14.13003043 24668'), "2 25338 98.5676 356.1853 0009968 282.2567 77.7505 14.26225252390049",
'NOAA-19': ('NOAA 19', ),
'1 33591U 09005A 25028.82370718 .00000425 00000+0 24556-3 0 9998', "NOAA-18": (
'2 33591 99.0905 25.2347 0013428 265.3457 94.6190 14.13019285827447'), "NOAA 18",
'NOAA-20': ('NOAA 20 (JPSS-1)', "1 28654U 05018A 25028.87364583 .00000454 00000+0 25082-3 0 9996",
'1 43013U 17073A 25028.83917428 .00000284 00000+0 15698-3 0 9995', "2 28654 98.8801 59.1618 0013609 281.7181 78.2479 14.13003043 24668",
'2 43013 98.7104 59.9558 0001165 102.5891 257.5432 14.19571458378899'), ),
'NOAA-21': ('NOAA 21 (JPSS-2)', "NOAA-19": (
'1 54234U 22150A 25028.86292604 .00000268 00000+0 14911-3 0 9995', "NOAA 19",
'2 54234 98.7064 59.6648 0001271 88.4689 271.6646 14.19545810114699'), "1 33591U 09005A 25028.82370718 .00000425 00000+0 24556-3 0 9998",
'METEOR-M2': ('METEOR-M 2', "2 33591 99.0905 25.2347 0013428 265.3457 94.6190 14.13019285827447",
'1 40069U 14037A 25028.47802083 .00000099 00000+0 69422-4 0 9990', ),
'2 40069 98.4752 356.8632 0003942 251.7291 108.3489 14.20719440555299'), "NOAA-20": (
'METEOR-M2-3': ('METEOR-M2 3', "NOAA 20 (JPSS-1)",
'1 57166U 23091A 25028.81539352 .00000157 00000+0 94432-4 0 9993', "1 43013U 17073A 26140.44110773 .00000055 00000+0 46930-4 0 9994",
'2 57166 98.7690 91.9652 0001790 107.4859 252.6519 14.23646028 77844'), "2 43013 98.7764 80.1520 0001265 43.4537 316.6738 14.19505991440534",
'METEOR-M2-4': ('METEOR-M2 4', ),
'1 59051U 24039A 26061.19281216 .00000032 00000+0 34037-4 0 9998', "NOAA-21": (
'2 59051 98.6892 21.9068 0008025 115.2158 244.9852 14.22415711104050'), "NOAA 21 (JPSS-2)",
"1 54234U 22150A 26140.47502274 .00000020 00000+0 29984-4 0 9999",
"2 54234 98.7052 79.7311 0000538 296.4939 63.6182 14.19559760182618",
),
"METEOR-M2": (
"METEOR-M 2",
"1 40069U 14037A 26140.48222780 .00000329 00000+0 16961-3 0 9999",
"2 40069 98.5104 117.2052 0006833 111.5029 248.6878 14.21453950615385",
),
"METEOR-M2-3": (
"METEOR-M2 3",
"1 57166U 23091A 26140.55562749 -.00000013 00000+0 13331-4 0 9995",
"2 57166 98.6097 196.0965 0002883 242.0522 118.0365 14.24044155150583",
),
"METEOR-M2-4": (
"METEOR-M2 4",
"1 59051U 24039A 26140.53898488 .00000003 00000+0 20858-4 0 9993",
"2 59051 98.6996 100.1874 0005955 247.0139 113.0410 14.22426327115336",
),
} }
+31 -30
View File
@@ -2,74 +2,75 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
from flask import Blueprint, Response, request from flask import Blueprint, Response, request
from utils.alerts import get_alert_manager from utils.alerts import get_alert_manager
from utils.responses import api_error, api_success from utils.responses import api_error, api_success
from utils.sse import format_sse from utils.sse import sse_stream_fanout
alerts_bp = Blueprint('alerts', __name__, url_prefix='/alerts') alerts_bp = Blueprint("alerts", __name__, url_prefix="/alerts")
@alerts_bp.route('/rules', methods=['GET']) @alerts_bp.route("/rules", methods=["GET"])
def list_rules(): def list_rules():
manager = get_alert_manager() manager = get_alert_manager()
include_disabled = request.args.get('all') in ('1', 'true', 'yes') include_disabled = request.args.get("all") in ("1", "true", "yes")
return api_success(data={'rules': manager.list_rules(include_disabled=include_disabled)}) return api_success(data={"rules": manager.list_rules(include_disabled=include_disabled)})
@alerts_bp.route('/rules', methods=['POST']) @alerts_bp.route("/rules", methods=["POST"])
def create_rule(): def create_rule():
data = request.get_json() or {} data = request.get_json() or {}
if not isinstance(data.get('match', {}), dict): if not isinstance(data.get("match", {}), dict):
return api_error('match must be a JSON object', 400) return api_error("match must be a JSON object", 400)
manager = get_alert_manager() manager = get_alert_manager()
rule_id = manager.add_rule(data) rule_id = manager.add_rule(data)
return api_success(data={'rule_id': rule_id}) return api_success(data={"rule_id": rule_id})
@alerts_bp.route('/rules/<int:rule_id>', methods=['PUT', 'PATCH']) @alerts_bp.route("/rules/<int:rule_id>", methods=["PUT", "PATCH"])
def update_rule(rule_id: int): def update_rule(rule_id: int):
data = request.get_json() or {} data = request.get_json() or {}
manager = get_alert_manager() manager = get_alert_manager()
ok = manager.update_rule(rule_id, data) ok = manager.update_rule(rule_id, data)
if not ok: if not ok:
return api_error('Rule not found or no changes', 404) return api_error("Rule not found or no changes", 404)
return api_success() return api_success()
@alerts_bp.route('/rules/<int:rule_id>', methods=['DELETE']) @alerts_bp.route("/rules/<int:rule_id>", methods=["DELETE"])
def delete_rule(rule_id: int): def delete_rule(rule_id: int):
manager = get_alert_manager() manager = get_alert_manager()
ok = manager.delete_rule(rule_id) ok = manager.delete_rule(rule_id)
if not ok: if not ok:
return api_error('Rule not found', 404) return api_error("Rule not found", 404)
return api_success() return api_success()
@alerts_bp.route('/events', methods=['GET']) @alerts_bp.route("/events", methods=["GET"])
def list_events(): def list_events():
manager = get_alert_manager() manager = get_alert_manager()
limit = request.args.get('limit', default=100, type=int) limit = request.args.get("limit", default=100, type=int)
mode = request.args.get('mode') mode = request.args.get("mode")
severity = request.args.get('severity') severity = request.args.get("severity")
events = manager.list_events(limit=limit, mode=mode, severity=severity) events = manager.list_events(limit=limit, mode=mode, severity=severity)
return api_success(data={'events': events}) return api_success(data={"events": events})
@alerts_bp.route('/stream', methods=['GET']) @alerts_bp.route("/stream", methods=["GET"])
def stream_alerts() -> Response: def stream_alerts() -> Response:
manager = get_alert_manager() manager = get_alert_manager()
response = Response(
def generate() -> Generator[str, None, None]: sse_stream_fanout(
for event in manager.stream_events(timeout=1.0): source_queue=manager._queue,
yield format_sse(event) channel_key="alerts",
timeout=1.0,
response = Response(generate(), mimetype='text/event-stream') keepalive_interval=30.0,
response.headers['Cache-Control'] = 'no-cache' ),
response.headers['X-Accel-Buffering'] = 'no' mimetype="text/event-stream",
response.headers['Connection'] = 'keep-alive' )
response.headers["Cache-Control"] = "no-cache"
response.headers["X-Accel-Buffering"] = "no"
response.headers["Connection"] = "keep-alive"
return response return response
+542 -523
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -1031,7 +1031,7 @@ body {
width: 100%; width: 100%;
object-fit: cover; object-fit: cover;
border-radius: 6px; border-radius: 6px;
border: 1px solid rgba(0, 212, 255, 0.3); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
} }
.selected-callsign { .selected-callsign {
@@ -2227,6 +2227,10 @@ body {
color: var(--text-inverse); color: var(--text-inverse);
} }
html[data-ui-tier="enhanced"] .strip-btn.primary {
background: linear-gradient(135deg, rgba(46, 125, 138, 0.85) 0%, rgba(20, 88, 100, 0.80) 100%);
}
.strip-btn.primary:hover { .strip-btn.primary:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(var(--accent-cyan-rgb), 0.3); box-shadow: 0 4px 12px rgba(var(--accent-cyan-rgb), 0.3);
@@ -2462,19 +2466,18 @@ body {
} }
/* ============================================ /* ============================================
ENHANCED TIER — amber military console ENHANCED TIER — signals teal console
============================================ */ ============================================ */
html[data-ui-tier="enhanced"] { html[data-ui-tier="enhanced"] {
--bg-dark: #080600; --bg-dark: #000000;
--bg-panel: #0c0a04; --bg-panel: #020404;
--bg-card: #0e0b05; --bg-card: #020404;
--radar-bg: #0c0a04; --radar-bg: #020404;
--radar-cyan: #c89628; --radar-cyan: #2e7d8a;
--border-glow: rgba(200, 150, 40, 0.25); --border-glow: rgba(46, 125, 138, 0.20);
--border-color: rgba(200, 150, 40, 0.2); --border-color: rgba(46, 125, 138, 0.18);
--grid-line: rgba(200, 150, 40, 0.07); --grid-line: rgba(46, 125, 138, 0.07);
--accent-cyan: #c89628; --accent-cyan: #2e7d8a;
--accent-green: #c89628;
} }
/* ============================================ /* ============================================
+25
View File
@@ -721,3 +721,28 @@ body {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
html[data-ui-tier="enhanced"] {
--accent-cyan: #2e7d8a;
--accent-cyan-rgb: 46, 125, 138;
--border-color: rgba(46, 125, 138, 0.18);
--border-glow: rgba(46, 125, 138, 0.20);
--grid-line: rgba(46, 125, 138, 0.07);
--bg-dark: #000000;
--bg-panel: #020404;
--bg-card: #020404;
}
html[data-ui-tier="enhanced"] body {
background: #000000;
}
html[data-ui-tier="enhanced"] .session-strip {
background: linear-gradient(120deg, rgba(4, 8, 8, 0.95), rgba(6, 10, 10, 0.95));
}
html[data-ui-tier="lean"] {
--border-color: #2a2a2a;
--border-glow: transparent;
--grid-line: transparent;
}
+8 -8
View File
@@ -10,15 +10,15 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 6px 12px; padding: 6px 12px;
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border: 1px solid rgba(0, 212, 255, 0.3); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
border-radius: 20px; border-radius: 20px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
} }
.agent-indicator:hover { .agent-indicator:hover {
background: rgba(0, 212, 255, 0.2); background: rgba(var(--accent-cyan-rgb), 0.2);
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
} }
@@ -49,7 +49,7 @@
.agent-indicator-count { .agent-indicator-count {
font-size: 10px; font-size: 10px;
padding: 2px 6px; padding: 2px 6px;
background: rgba(0, 212, 255, 0.2); background: rgba(var(--accent-cyan-rgb), 0.2);
border-radius: 10px; border-radius: 10px;
color: var(--accent-cyan); color: var(--accent-cyan);
} }
@@ -123,11 +123,11 @@
} }
.agent-selector-item:hover { .agent-selector-item:hover {
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
} }
.agent-selector-item.selected { .agent-selector-item.selected {
background: rgba(0, 212, 255, 0.15); background: rgba(var(--accent-cyan-rgb), 0.15);
border-left: 3px solid var(--accent-cyan); border-left: 3px solid var(--accent-cyan);
} }
@@ -188,7 +188,7 @@
gap: 4px; gap: 4px;
padding: 2px 8px; padding: 2px 8px;
font-size: 10px; font-size: 10px;
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
color: var(--accent-cyan); color: var(--accent-cyan);
border-radius: 10px; border-radius: 10px;
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -201,7 +201,7 @@
} }
.agent-badge.agent-remote { .agent-badge.agent-remote {
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
color: var(--accent-cyan); color: var(--accent-cyan);
} }
+10 -11
View File
@@ -1375,19 +1375,18 @@ body {
} }
/* ============================================ /* ============================================
ENHANCED TIER amber military console ENHANCED TIER signals teal console
============================================ */ ============================================ */
html[data-ui-tier="enhanced"] { html[data-ui-tier="enhanced"] {
--bg-dark: #080600; --bg-dark: #000000;
--bg-panel: #0c0a04; --bg-panel: #020404;
--bg-card: #0e0b05; --bg-card: #020404;
--radar-bg: #0c0a04; --radar-bg: #020404;
--radar-cyan: #c89628; --radar-cyan: #2e7d8a;
--border-glow: rgba(200, 150, 40, 0.25); --border-glow: rgba(46, 125, 138, 0.20);
--border-color: rgba(200, 150, 40, 0.2); --border-color: rgba(46, 125, 138, 0.18);
--grid-line: rgba(200, 150, 40, 0.07); --grid-line: rgba(46, 125, 138, 0.07);
--accent-cyan: #c89628; --accent-cyan: #2e7d8a;
--accent-green: #c89628;
} }
/* ============================================ /* ============================================
+1 -1
View File
@@ -38,7 +38,7 @@
.device-card:hover { .device-card:hover {
border-color: var(--accent-cyan, #00d4ff); border-color: var(--accent-cyan, #00d4ff);
box-shadow: 0 0 0 1px rgba(0, 212, 255, 0.2); box-shadow: 0 0 0 1px rgba(var(--accent-cyan-rgb), 0.2);
} }
.device-card:active { .device-card:active {
+2 -2
View File
@@ -162,8 +162,8 @@
} }
.heatmap-row.selected { .heatmap-row.selected {
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
outline: 1px solid rgba(0, 212, 255, 0.3); outline: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
} }
.heatmap-header { .heatmap-header {
+1 -1
View File
@@ -1645,7 +1645,7 @@
.signal-card.signal-card-clickable:hover { .signal-card.signal-card-clickable:hover {
border-color: var(--accent-cyan, #00d4ff); border-color: var(--accent-cyan, #00d4ff);
box-shadow: 0 0 0 1px rgba(0, 212, 255, 0.2); box-shadow: 0 0 0 1px rgba(var(--accent-cyan-rgb), 0.2);
} }
.signal-card.signal-card-clickable:active { .signal-card.signal-card-clickable:active {
+36
View File
@@ -406,6 +406,42 @@
color: var(--text-primary, #e6edf5); color: var(--text-primary, #e6edf5);
} }
/* ---- Enhanced tier overrides ---- */
html[data-ui-tier="enhanced"] .run-state-strip {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.96), rgba(2, 4, 4, 0.97));
border-color: rgba(46, 125, 138, 0.28);
}
html[data-ui-tier="enhanced"] .run-state-chip {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.82), rgba(2, 4, 4, 0.84));
border-color: rgba(46, 125, 138, 0.22);
}
html[data-ui-tier="enhanced"] .run-state-chip.active {
border-color: rgba(46, 125, 138, 0.60);
box-shadow: inset 0 0 0 1px rgba(46, 125, 138, 0.16);
}
html[data-ui-tier="enhanced"] .run-state-chip .dot {
background: rgba(46, 125, 138, 0.40);
box-shadow: none;
}
html[data-ui-tier="enhanced"] .run-state-chip.running .dot {
background: var(--accent-green, #38c180);
box-shadow: 0 0 0 4px rgba(56, 193, 128, 0.16), 0 0 12px rgba(56, 193, 128, 0.35);
}
html[data-ui-tier="enhanced"] .run-state-btn {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.9), rgba(2, 4, 4, 0.92));
border-color: rgba(46, 125, 138, 0.40);
}
html[data-ui-tier="enhanced"] .run-state-btn:hover {
background: rgba(46, 125, 138, 0.12);
border-color: rgba(46, 125, 138, 0.65);
}
/* ---- Light theme overrides ---- */ /* ---- Light theme overrides ---- */
[data-theme="light"] .run-state-chip { [data-theme="light"] .run-state-chip {
background: linear-gradient(180deg, rgba(233, 238, 245, 0.9), rgba(225, 232, 242, 0.92)); background: linear-gradient(180deg, rgba(233, 238, 245, 0.9), rgba(225, 232, 242, 0.92));
+8 -3
View File
@@ -1206,12 +1206,12 @@ textarea:focus {
} }
/* ============================================ /* ============================================
ENHANCED TIER amber left-border card accents ENHANCED TIER signals teal card accents
============================================ */ ============================================ */
[data-ui-tier="enhanced"] .stat-card, [data-ui-tier="enhanced"] .stat-card,
[data-ui-tier="enhanced"] .data-card { [data-ui-tier="enhanced"] .data-card {
border-left: 2px solid var(--accent-cyan); border-left: 2px solid var(--accent-cyan);
background: rgba(200, 150, 40, 0.03); background: rgba(46, 125, 138, 0.03);
} }
[data-ui-tier="enhanced"] .stat-value, [data-ui-tier="enhanced"] .stat-value,
@@ -1226,5 +1226,10 @@ textarea:focus {
letter-spacing: 2px; letter-spacing: 2px;
text-transform: uppercase; text-transform: uppercase;
font-size: var(--text-xs); font-size: var(--text-xs);
color: rgba(200, 150, 40, 0.5); color: rgba(46, 125, 138, 0.55);
}
html[data-ui-tier="enhanced"] .card-header,
html[data-ui-tier="enhanced"] .panel-header {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.88) 0%, rgba(2, 4, 4, 0.9) 100%);
} }
+12 -12
View File
@@ -1188,10 +1188,10 @@ a.nav-dashboard-btn:hover {
} }
/* ============================================ /* ============================================
ENHANCED TIER amber console nav framing ENHANCED TIER signals teal console nav framing
============================================ */ ============================================ */
[data-ui-tier="enhanced"] .mode-nav { [data-ui-tier="enhanced"] .mode-nav {
background: linear-gradient(180deg, rgba(14, 11, 3, 0.95), rgba(8, 6, 0, 0.92)); background: linear-gradient(180deg, rgba(4, 8, 8, 0.95), rgba(2, 4, 4, 0.92));
} }
[data-ui-tier="enhanced"] .mode-nav::after, [data-ui-tier="enhanced"] .mode-nav::after,
@@ -1201,16 +1201,16 @@ a.nav-dashboard-btn:hover {
} }
[data-ui-tier="enhanced"] .mode-nav-btn.active { [data-ui-tier="enhanced"] .mode-nav-btn.active {
background: rgba(200, 150, 40, 0.08); background: rgba(46, 125, 138, 0.08);
color: var(--accent-cyan); color: var(--accent-cyan);
border-left: 2px solid var(--accent-cyan); border-left: 2px solid var(--accent-cyan);
box-shadow: -2px 0 8px rgba(200, 150, 40, 0.15); box-shadow: -2px 0 8px rgba(46, 125, 138, 0.15);
padding-left: 12px; padding-left: 12px;
} }
[data-ui-tier="enhanced"] .nav-clock .utc-time { [data-ui-tier="enhanced"] .nav-clock .utc-time {
color: var(--accent-cyan); color: var(--accent-cyan);
text-shadow: 0 0 8px rgba(200, 150, 40, 0.3); text-shadow: 0 0 8px rgba(46, 125, 138, 0.28);
font-weight: 700; font-weight: 700;
} }
@@ -1236,14 +1236,14 @@ a.nav-dashboard-btn:hover {
/* Enhanced tier toggle button styling */ /* Enhanced tier toggle button styling */
[data-ui-tier="enhanced"] .nav-tier-btn { [data-ui-tier="enhanced"] .nav-tier-btn {
background: rgba(200, 150, 40, 0.10); background: rgba(46, 125, 138, 0.10);
border-color: rgba(200, 150, 40, 0.4); border-color: rgba(46, 125, 138, 0.38);
color: #c89628; color: #2e7d8a;
box-shadow: 0 0 8px rgba(200, 150, 40, 0.08); box-shadow: 0 0 8px rgba(46, 125, 138, 0.07);
text-shadow: 0 0 6px rgba(200, 150, 40, 0.3); text-shadow: 0 0 6px rgba(46, 125, 138, 0.25);
} }
[data-ui-tier="enhanced"] .nav-tier-btn:hover { [data-ui-tier="enhanced"] .nav-tier-btn:hover {
background: rgba(200, 150, 40, 0.16); background: rgba(46, 125, 138, 0.16);
border-color: rgba(200, 150, 40, 0.6); border-color: rgba(46, 125, 138, 0.55);
} }
+156 -108
View File
@@ -29,6 +29,7 @@
/* Accent colors */ /* Accent colors */
--accent-cyan: #4aa3ff; --accent-cyan: #4aa3ff;
--accent-cyan-rgb: 74, 163, 255; --accent-cyan-rgb: 74, 163, 255;
--primary-color: var(--accent-cyan);
--accent-cyan-dim: rgba(var(--accent-cyan-rgb), 0.16); --accent-cyan-dim: rgba(var(--accent-cyan-rgb), 0.16);
--accent-cyan-hover: #6bb3ff; --accent-cyan-hover: #6bb3ff;
--accent-cyan-glow: rgba(var(--accent-cyan-rgb), 0.12); --accent-cyan-glow: rgba(var(--accent-cyan-rgb), 0.12);
@@ -108,7 +109,7 @@
/* ============================================ /* ============================================
TYPOGRAPHY TYPOGRAPHY
============================================ */ ============================================ */
--font-sans: 'Roboto Condensed', 'Arial Narrow', Roboto, 'Helvetica Neue', Arial, sans-serif; --font-sans: 'Inter', 'Roboto Condensed', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', Consolas, monospace; --font-mono: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', Consolas, monospace;
/* Font sizes */ /* Font sizes */
@@ -178,85 +179,6 @@
--content-max-width: 1400px; --content-max-width: 1400px;
} }
/* ============================================
LIGHT THEME OVERRIDES
============================================ */
[data-theme="light"] {
--bg-primary: #f4f7fb;
--bg-secondary: #e9eef5;
--bg-tertiary: #dde5f0;
--bg-card: #ffffff;
--bg-elevated: #f1f4f9;
--bg-overlay: rgba(244, 247, 251, 0.92);
--surface-glass: rgba(255, 255, 255, 0.84);
--surface-panel-gradient: linear-gradient(160deg, rgba(255, 255, 255, 0.96) 0%, rgba(241, 245, 251, 0.97) 100%);
--ambient-top-left: rgba(31, 95, 168, 0.1);
--ambient-top-right: rgba(31, 138, 87, 0.06);
--ambient-bottom: rgba(181, 134, 58, 0.05);
/* Background aliases for components */
--bg-dark: var(--bg-primary);
--bg-panel: var(--bg-secondary);
--accent-cyan: #1f5fa8;
--accent-cyan-rgb: 31, 95, 168;
--accent-cyan-dim: rgba(31, 95, 168, 0.12);
--accent-cyan-hover: #2c73bf;
--accent-green: #1f8a57;
--accent-green-hover: #167a4a;
--accent-green-dim: rgba(31, 138, 87, 0.12);
--accent-red: #c74444;
--accent-red-hover: #b33a3a;
--accent-red-dim: rgba(199, 68, 68, 0.12);
--accent-orange: #b5863a;
--accent-orange-dim: rgba(181, 134, 58, 0.12);
--accent-amber: #b5863a;
--accent-amber-dim: rgba(181, 134, 58, 0.12);
--accent-yellow: #9a8420;
--accent-purple: #6b5ba8;
/* Status colors - light theme */
--status-online: #1f8a57;
--status-warning: #b5863a;
--status-error: #c74444;
--status-offline: #6b7c93;
--status-info: #1f5fa8;
/* Severity colors */
--severity-critical: #c74444;
--severity-high: #b5863a;
--severity-medium: #9a8420;
--severity-low: #1f8a57;
/* Data visualization neon replacements */
--neon-green: #1a8a50;
--neon-yellow: #9a8420;
--neon-orange: #b5863a;
--neon-red: #c74444;
--text-primary: #122034;
--text-secondary: #3a4a5f;
--text-dim: #566a7f;
--text-muted: #7a8a9e;
--text-inverse: #f4f7fb;
--border-color: #d1d9e6;
--border-light: #c1ccdb;
--border-glow: rgba(31, 95, 168, 0.12);
--grid-line: rgba(31, 95, 168, 0.14);
--grid-dot: rgba(12, 18, 24, 0.06);
--noise-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='40'%20height='40'%20viewBox='0%200%2040%2040'%3E%3Cg%20fill='%23000000'%20fill-opacity='0.05'%3E%3Ccircle%20cx='3'%20cy='5'%20r='1'/%3E%3Ccircle%20cx='11'%20cy='9'%20r='1'/%3E%3Ccircle%20cx='18'%20cy='3'%20r='1'/%3E%3Ccircle%20cx='26'%20cy='12'%20r='1'/%3E%3Ccircle%20cx='34'%20cy='6'%20r='1'/%3E%3Ccircle%20cx='7'%20cy='19'%20r='1'/%3E%3Ccircle%20cx='15'%20cy='24'%20r='1'/%3E%3Ccircle%20cx='28'%20cy='22'%20r='1'/%3E%3Ccircle%20cx='36'%20cy='18'%20r='1'/%3E%3Ccircle%20cx='5'%20cy='33'%20r='1'/%3E%3Ccircle%20cx='19'%20cy='32'%20r='1'/%3E%3Ccircle%20cx='31'%20cy='34'%20r='1'/%3E%3C/g%3E%3C/svg%3E");
--accent-cyan-glow: rgba(31, 95, 168, 0.08);
--scanline: none;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15);
--shadow-glow: 0 0 18px rgba(31, 95, 168, 0.1);
}
/* ============================================ /* ============================================
REDUCED MOTION REDUCED MOTION
============================================ */ ============================================ */
@@ -304,40 +226,166 @@ html[data-ui-tier="lean"] {
} }
/* ============================================ /* ============================================
ENHANCED TIER amber military console ENHANCED TIER signals teal console
============================================ */ ============================================ */
html[data-ui-tier="enhanced"] { html[data-ui-tier="enhanced"] {
--bg-primary: #080600; --bg-primary: #000000;
--bg-secondary: #0c0a04; --bg-secondary: #020404;
--bg-tertiary: #100d06; --bg-tertiary: #040808;
--bg-card: #0e0b05; --bg-card: #020404;
--bg-elevated: #141008; --bg-elevated: #060a0a;
--bg-overlay: rgba(8, 6, 0, 0.82); --bg-overlay: rgba(0, 0, 0, 0.88);
--surface-glass: rgba(14, 11, 5, 0.82); --surface-glass: rgba(2, 4, 4, 0.90);
--surface-panel-gradient: linear-gradient(160deg, rgba(20, 16, 8, 0.94) 0%, rgba(12, 10, 4, 0.96) 100%); --surface-panel-gradient: linear-gradient(160deg, rgba(6, 10, 10, 0.96) 0%, rgba(2, 4, 4, 0.98) 100%);
--accent-cyan: #c89628; --accent-cyan: #2e7d8a;
--accent-cyan-rgb: 200, 150, 40; --accent-cyan-rgb: 46, 125, 138;
--accent-cyan-dim: rgba(200, 150, 40, 0.14); --accent-cyan-dim: rgba(46, 125, 138, 0.14);
--accent-cyan-hover: #e0aa30; --accent-cyan-hover: #3a9aaa;
--accent-cyan-glow: rgba(200, 150, 40, 0.10); --accent-cyan-glow: rgba(46, 125, 138, 0.09);
--accent-green: #c89628; /* red/green intentionally unchanged — semantic status only */
--accent-green-hover: #e0aa30;
--accent-green-dim: rgba(200, 150, 40, 0.14);
/* red is intentionally unchanged — critical alerts only */ --ambient-top-left: rgba(46, 125, 138, 0.07);
--ambient-top-right: rgba(46, 125, 138, 0.04);
--ambient-bottom: rgba(46, 125, 138, 0.03);
--ambient-top-left: rgba(200, 150, 40, 0.08); --grid-line: rgba(46, 125, 138, 0.07);
--ambient-top-right: rgba(200, 150, 40, 0.05); --border-color: rgba(46, 125, 138, 0.18);
--ambient-bottom: rgba(200, 150, 40, 0.04); --border-light: rgba(46, 125, 138, 0.28);
--border-glow: rgba(46, 125, 138, 0.20);
--border-focus: #2e7d8a;
--grid-line: rgba(200, 150, 40, 0.07); --status-info: #2e7d8a;
--border-color: rgba(200, 150, 40, 0.2);
--border-light: rgba(200, 150, 40, 0.3);
--border-glow: rgba(200, 150, 40, 0.25);
--border-focus: #c89628;
--status-online: #c89628; --font-sans: 'Inter', 'Roboto Condensed', 'Helvetica Neue', Arial, sans-serif;
--status-info: #c89628; }
/* ============================================
LIGHT THEME OVERRIDES
Placed after tier blocks so html[data-theme="light"]
(specificity 0,1,1) beats both tier selectors when active.
============================================ */
html[data-theme="light"] {
--bg-primary: #f4f7fb;
--bg-secondary: #e9eef5;
--bg-tertiary: #dde5f0;
--bg-card: #ffffff;
--bg-elevated: #f1f4f9;
--bg-overlay: rgba(244, 247, 251, 0.92);
--surface-glass: rgba(255, 255, 255, 0.84);
--surface-panel-gradient: linear-gradient(160deg, rgba(255, 255, 255, 0.96) 0%, rgba(241, 245, 251, 0.97) 100%);
--ambient-top-left: rgba(31, 95, 168, 0.1);
--ambient-top-right: rgba(31, 138, 87, 0.06);
--ambient-bottom: rgba(181, 134, 58, 0.05);
--bg-dark: var(--bg-primary);
--bg-panel: var(--bg-secondary);
--accent-cyan: #1f5fa8;
--accent-cyan-rgb: 31, 95, 168;
--accent-cyan-dim: rgba(31, 95, 168, 0.12);
--accent-cyan-hover: #2c73bf;
--accent-green: #1f8a57;
--accent-green-hover: #167a4a;
--accent-green-dim: rgba(31, 138, 87, 0.12);
--accent-red: #c74444;
--accent-red-hover: #b33a3a;
--accent-red-dim: rgba(199, 68, 68, 0.12);
--accent-orange: #b5863a;
--accent-orange-dim: rgba(181, 134, 58, 0.12);
--accent-amber: #b5863a;
--accent-amber-dim: rgba(181, 134, 58, 0.12);
--accent-yellow: #9a8420;
--accent-purple: #6b5ba8;
--status-online: #1f8a57;
--status-warning: #b5863a;
--status-error: #c74444;
--status-offline: #6b7c93;
--status-info: #1f5fa8;
--severity-critical: #c74444;
--severity-high: #b5863a;
--severity-medium: #9a8420;
--severity-low: #1f8a57;
--neon-green: #1a8a50;
--neon-yellow: #9a8420;
--neon-orange: #b5863a;
--neon-red: #c74444;
--text-primary: #122034;
--text-secondary: #3a4a5f;
--text-dim: #566a7f;
--text-muted: #7a8a9e;
--text-inverse: #f4f7fb;
--border-color: #d1d9e6;
--border-light: #c1ccdb;
--border-glow: rgba(31, 95, 168, 0.12);
--grid-line: rgba(31, 95, 168, 0.14);
--grid-dot: rgba(12, 18, 24, 0.06);
--noise-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='40'%20height='40'%20viewBox='0%200%2040%2040'%3E%3Cg%20fill='%23000000'%20fill-opacity='0.05'%3E%3Ccircle%20cx='3'%20cy='5'%20r='1'/%3E%3Ccircle%20cx='11'%20cy='9'%20r='1'/%3E%3Ccircle%20cx='18'%20cy='3'%20r='1'/%3E%3Ccircle%20cx='26'%20cy='12'%20r='1'/%3E%3Ccircle%20cx='34'%20cy='6'%20r='1'/%3E%3Ccircle%20cx='7'%20cy='19'%20r='1'/%3E%3Ccircle%20cx='15'%20cy='24'%20r='1'/%3E%3Ccircle%20cx='28'%20cy='22'%20r='1'/%3E%3Ccircle%20cx='36'%20cy='18'%20r='1'/%3E%3Ccircle%20cx='5'%20cy='33'%20r='1'/%3E%3Ccircle%20cx='19'%20cy='32'%20r='1'/%3E%3Ccircle%20cx='31'%20cy='34'%20r='1'/%3E%3C/g%3E%3C/svg%3E");
--accent-cyan-glow: rgba(31, 95, 168, 0.08);
--scanline: none;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15);
--shadow-glow: 0 0 18px rgba(31, 95, 168, 0.1);
}
/* Lean tier + light: specificity (0,2,1) beats lean-only (0,1,1) */
html[data-ui-tier="lean"][data-theme="light"] {
--bg-primary: #f4f7fb;
--bg-secondary: #e9eef5;
--bg-tertiary: #dde5f0;
--bg-card: #ffffff;
--bg-elevated: #f1f4f9;
--bg-dark: #f4f7fb;
--bg-panel: #e9eef5;
--bg-overlay: rgba(244, 247, 251, 0.92);
--surface-glass: rgba(255, 255, 255, 0.84);
--surface-panel-gradient: linear-gradient(160deg, rgba(255, 255, 255, 0.96) 0%, rgba(241, 245, 251, 0.97) 100%);
--accent-cyan: #1f5fa8;
--accent-cyan-rgb: 31, 95, 168;
--accent-cyan-dim: rgba(31, 95, 168, 0.12);
--accent-green: #1f8a57;
--text-primary: #122034;
--text-secondary: #3a4a5f;
--text-dim: #566a7f;
--text-muted: #7a8a9e;
--border-color: #d1d9e6;
--border-light: #c1ccdb;
--border-glow: rgba(31, 95, 168, 0.12);
--grid-line: rgba(31, 95, 168, 0.14);
--ambient-top-left: rgba(31, 95, 168, 0.1);
--ambient-top-right: rgba(31, 138, 87, 0.06);
--ambient-bottom: rgba(181, 134, 58, 0.05);
}
/* Enhanced tier + light: cool whites with signals teal accents */
html[data-ui-tier="enhanced"][data-theme="light"] {
--bg-primary: #f4f7fb;
--bg-secondary: #e9eef5;
--bg-tertiary: #dde5f0;
--bg-card: #ffffff;
--bg-elevated: #f1f4f9;
--bg-dark: #f4f7fb;
--bg-panel: #e9eef5;
--bg-overlay: rgba(244, 247, 251, 0.92);
--surface-glass: rgba(255, 255, 255, 0.84);
--text-primary: #0a1a1e;
--text-secondary: #1c3a42;
--text-dim: #3a5a62;
--border-color: rgba(30, 100, 112, 0.28);
--grid-line: rgba(30, 100, 112, 0.12);
--accent-cyan: #1e6470;
--accent-cyan-rgb: 30, 100, 112;
--accent-cyan-dim: rgba(30, 100, 112, 0.12);
--accent-cyan-hover: #25808e;
--accent-cyan-glow: rgba(30, 100, 112, 0.08);
} }
+67 -1
View File
@@ -1,6 +1,6 @@
/* Local font declarations for offline mode */ /* Local font declarations for offline mode */
/* Roboto Condensed - variable font, one file covers all weights */
/* Roboto Condensed - variable font, one file covers all weights */
@font-face { @font-face {
font-family: 'Roboto Condensed'; font-family: 'Roboto Condensed';
font-style: normal; font-style: normal;
@@ -18,3 +18,69 @@
src: url('/static/vendor/fonts/RobotoCondensed-LatinExt.woff2') format('woff2'); src: url('/static/vendor/fonts/RobotoCondensed-LatinExt.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
} }
/* Inter - used by enhanced tier */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/vendor/fonts/Inter-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/static/vendor/fonts/Inter-Medium.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/static/vendor/fonts/Inter-SemiBold.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/static/vendor/fonts/Inter-Bold.woff2') format('woff2');
}
/* JetBrains Mono - used by enhanced tier and all --font-mono elements */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/vendor/fonts/JetBrainsMono-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/static/vendor/fonts/JetBrainsMono-Medium.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
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');
}
+89 -18
View File
@@ -262,10 +262,10 @@ body {
@keyframes logoPulse { @keyframes logoPulse {
0%, 100% { 0%, 100% {
filter: drop-shadow(0 0 15px rgba(0, 212, 255, 0.3)); filter: drop-shadow(0 0 15px rgba(var(--accent-cyan-rgb), 0.3));
} }
50% { 50% {
filter: drop-shadow(0 0 30px rgba(0, 212, 255, 0.6)); filter: drop-shadow(0 0 30px rgba(var(--accent-cyan-rgb), 0.6));
} }
} }
@@ -304,7 +304,7 @@ body {
color: var(--text-primary); color: var(--text-primary);
letter-spacing: 0.2em; letter-spacing: 0.2em;
margin: 0; margin: 0;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.3); text-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.3);
white-space: nowrap; white-space: nowrap;
} }
@@ -453,7 +453,7 @@ body {
background: var(--bg-elevated); background: var(--bg-elevated);
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.15); box-shadow: 0 4px 20px rgba(var(--accent-cyan-rgb), 0.15);
} }
.mode-card:hover .mode-icon { .mode-card:hover .mode-icon {
@@ -2529,7 +2529,7 @@ header h1 .tagline {
border: 1px solid var(--accent-cyan); border: 1px solid var(--accent-cyan);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.2);
} }
#aircraftMap { #aircraftMap {
@@ -2863,7 +2863,7 @@ header h1 .tagline {
border-radius: 8px; border-radius: 8px;
padding: 15px; padding: 15px;
margin-bottom: 15px; margin-bottom: 15px;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.1); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.1);
} }
.countdown-satellite-name { .countdown-satellite-name {
@@ -5879,7 +5879,7 @@ body::before {
max-width: 550px; max-width: 550px;
padding: 30px; padding: 30px;
text-align: center; text-align: center;
box-shadow: 0 0 50px rgba(0, 212, 255, 0.3); box-shadow: 0 0 50px rgba(var(--accent-cyan-rgb), 0.3);
pointer-events: auto; pointer-events: auto;
position: relative; position: relative;
z-index: 100000; z-index: 100000;
@@ -5939,7 +5939,7 @@ body::before {
.disclaimer-modal .accept-btn:hover { .disclaimer-modal .accept-btn:hover {
background: #fff; background: #fff;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.5); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.5);
} }
.disclaimer-hidden { .disclaimer-hidden {
@@ -6190,7 +6190,7 @@ body::before {
/* Map Clustering */ /* Map Clustering */
.marker-cluster { .marker-cluster {
background: rgba(0, 212, 255, 0.6); background: rgba(var(--accent-cyan-rgb), 0.6);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -7045,7 +7045,7 @@ body::before {
.radio-module-box.scanner-main { .radio-module-box.scanner-main {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0,20,30,0.95) 100%); background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0,20,30,0.95) 100%);
border: 1px solid var(--accent-cyan-dim); border: 1px solid var(--accent-cyan-dim);
box-shadow: 0 0 20px rgba(0, 212, 255, 0.1), inset 0 0 40px rgba(0, 0, 0, 0.3); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.1), inset 0 0 40px rgba(0, 0, 0, 0.3);
} }
.radio-module-box.scanner-main::before { .radio-module-box.scanner-main::before {
@@ -7224,7 +7224,7 @@ body::before {
} }
.radio-module-box table tbody tr:hover { .radio-module-box table tbody tr:hover {
background: rgba(0, 212, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
} }
/* Log Content Compact */ /* Log Content Compact */
@@ -7257,14 +7257,14 @@ body::before {
.radio-mode-btn:hover { .radio-mode-btn:hover {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
color: var(--accent-cyan); color: var(--accent-cyan);
background: rgba(0, 212, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
} }
.radio-mode-btn.active { .radio-mode-btn.active {
background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(0, 255, 136, 0.1)); background: linear-gradient(135deg, rgba(var(--accent-cyan-rgb), 0.2), rgba(0, 255, 136, 0.1));
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
color: var(--accent-cyan); color: var(--accent-cyan);
box-shadow: 0 0 20px rgba(0, 212, 255, 0.2), inset 0 0 20px rgba(0, 212, 255, 0.05); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.2), inset 0 0 20px rgba(var(--accent-cyan-rgb), 0.05);
} }
/* Listening Mode Panels */ /* Listening Mode Panels */
@@ -7292,7 +7292,7 @@ body::before {
.preset-freq-btn:hover { .preset-freq-btn:hover {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
color: var(--accent-cyan); color: var(--accent-cyan);
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
} }
.preset-freq-btn:active { .preset-freq-btn:active {
@@ -7652,6 +7652,11 @@ body[data-mode="tscm"] {
background-size: unset; background-size: unset;
} }
html[data-ui-tier="lean"][data-theme="light"] body {
background-color: var(--bg-primary);
background-image: none;
}
[data-ui-tier="lean"] *, [data-ui-tier="lean"] *,
[data-ui-tier="lean"] *::before, [data-ui-tier="lean"] *::before,
[data-ui-tier="lean"] *::after { [data-ui-tier="lean"] *::after {
@@ -7677,9 +7682,75 @@ body[data-mode="tscm"] {
/* ============================================ /* ============================================
ENHANCED TIER visual edge/glow overrides ENHANCED TIER panel surface overrides
Replaces hardcoded cool-dark navy gradients
with warm near-black amber equivalents.
============================================ */ ============================================ */
html[data-ui-tier="enhanced"] { html[data-ui-tier="enhanced"] {
--visual-edge-cyan: rgba(200, 150, 40, 0.34); --visual-edge-cyan: rgba(46, 125, 138, 0.30);
--visual-glow-cyan: 0 0 24px rgba(200, 150, 40, 0.16); --visual-glow-cyan: 0 0 24px rgba(46, 125, 138, 0.12);
--visual-surface-soft: linear-gradient(180deg, rgba(6, 10, 10, 0.9) 0%, rgba(3, 5, 5, 0.95) 100%);
--visual-surface-panel: linear-gradient(160deg, rgba(6, 10, 10, 0.95) 0%, rgba(2, 4, 4, 0.96) 100%);
}
html[data-ui-tier="enhanced"] .mode-nav {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.96) 0%, rgba(2, 4, 4, 0.98) 100%);
}
html[data-ui-tier="enhanced"] .mode-nav-dropdown-menu {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.98) 0%, rgba(2, 4, 4, 0.99) 100%);
border-color: rgba(46, 125, 138, 0.20);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(46, 125, 138, 0.09);
}
html[data-ui-tier="enhanced"] .run-state-strip {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.96) 0%, rgba(2, 4, 4, 0.97) 100%);
}
html[data-ui-tier="enhanced"] .section h3 {
background: linear-gradient(180deg, rgba(6, 10, 10, 1) 0%, rgba(4, 8, 8, 1) 100%);
}
html[data-ui-tier="enhanced"] .section h3::after {
background: rgba(2, 4, 4, 0.9);
}
html[data-ui-tier="enhanced"] .form-group input,
html[data-ui-tier="enhanced"] .form-group select {
background: rgba(2, 4, 4, 0.72);
}
html[data-ui-tier="enhanced"] .preset-btn,
html[data-ui-tier="enhanced"] .control-btn,
html[data-ui-tier="enhanced"] .clear-btn {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.88) 0%, rgba(2, 4, 4, 0.9) 100%);
}
html[data-ui-tier="enhanced"] .output-panel {
background: linear-gradient(180deg, rgba(2, 4, 4, 0.98) 0%, rgba(1, 2, 2, 0.99) 100%);
}
html[data-ui-tier="enhanced"] .output-header {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.95) 0%, rgba(2, 4, 4, 0.98) 100%);
}
html[data-ui-tier="enhanced"] .output-content {
background: linear-gradient(180deg, rgba(2, 4, 4, 0.6) 0%, rgba(2, 4, 4, 0.9) 100%);
}
html[data-ui-tier="enhanced"] .stats > div {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.8) 0%, rgba(2, 4, 4, 0.82) 100%);
}
html[data-ui-tier="enhanced"] .message {
background: linear-gradient(180deg, rgba(4, 8, 8, 0.8) 0%, rgba(2, 4, 4, 0.82) 100%);
}
html[data-ui-tier="enhanced"] .status-bar {
background: linear-gradient(180deg, rgba(3, 6, 6, 0.96) 0%, rgba(2, 4, 4, 0.97) 100%);
}
html[data-ui-tier="enhanced"] .status-indicator,
html[data-ui-tier="enhanced"] .control-group {
background: linear-gradient(180deg, rgba(3, 6, 6, 0.78) 0%, rgba(2, 4, 4, 0.8) 100%);
} }
+4 -4
View File
@@ -284,7 +284,7 @@
#btLocateMap { #btLocateMap {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: #1a1a2e; background: var(--bg-primary, #07090e);
} }
.btl-map-overlay-controls { .btl-map-overlay-controls {
@@ -442,8 +442,8 @@
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
color: var(--accent-cyan, #00d4ff); color: var(--accent-cyan, #00d4ff);
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border: 1px solid rgba(0, 212, 255, 0.3); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
@@ -452,7 +452,7 @@
} }
.btl-detect-irk-btn:hover { .btl-detect-irk-btn:hover {
background: rgba(0, 212, 255, 0.2); background: rgba(var(--accent-cyan-rgb), 0.2);
border-color: var(--accent-cyan, #00d4ff); border-color: var(--accent-cyan, #00d4ff);
} }
+23
View File
@@ -471,3 +471,26 @@
height: 40px; height: 40px;
} }
} }
html[data-ui-tier="enhanced"] .meteor-visuals-container {
--ms-border: rgba(46, 125, 138, 0.22);
--ms-surface: linear-gradient(180deg, rgba(2, 6, 6, 0.97) 0%, rgba(1, 3, 3, 0.98) 100%);
--ms-accent: #2e7d8a;
--ms-accent-dim: rgba(46, 125, 138, 0.12);
background: radial-gradient(circle at 14% -18%, rgba(46, 125, 138, 0.10) 0%, rgba(46, 125, 138, 0) 38%),
radial-gradient(circle at 86% -26%, rgba(46, 125, 138, 0.07) 0%, rgba(46, 125, 138, 0) 36%),
#000202;
}
html[data-ui-tier="enhanced"] .ms-headline,
html[data-ui-tier="enhanced"] .ms-events-panel {
background: rgba(2, 6, 6, 0.9);
}
html[data-ui-tier="enhanced"] .ms-events-table th {
background: rgba(2, 6, 6, 0.95);
}
html[data-ui-tier="enhanced"] .ms-events-table tr:hover td {
background: rgba(46, 125, 138, 0.04);
}
+14
View File
@@ -199,3 +199,17 @@
align-items: stretch; align-items: stretch;
} }
} }
html[data-ui-tier="enhanced"] .morse-raw-panel {
border-color: rgba(46, 125, 138, 0.18);
background: rgba(1, 4, 4, 0.9);
}
html[data-ui-tier="enhanced"] .morse-raw-text {
color: rgba(70, 180, 200, 0.90);
}
html[data-ui-tier="enhanced"] .morse-metrics-panel span {
border-color: rgba(46, 125, 138, 0.18);
background: rgba(1, 4, 4, 0.88);
}
+4 -4
View File
@@ -160,14 +160,14 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 4px 10px; padding: 4px 10px;
background: rgba(0, 229, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
border: 1px solid rgba(0, 229, 255, 0.15); border: 1px solid rgba(var(--accent-cyan-rgb), 0.15);
border-radius: 4px; border-radius: 4px;
min-width: 55px; min-width: 55px;
} }
.radiosonde-strip .strip-stat:hover { .radiosonde-strip .strip-stat:hover {
background: rgba(0, 229, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border-color: rgba(0, 229, 255, 0.3); border-color: rgba(var(--accent-cyan-rgb), 0.3);
} }
.radiosonde-strip .strip-value { .radiosonde-strip .strip-value {
font-family: var(--font-mono); font-family: var(--font-mono);
+1 -1
View File
@@ -342,7 +342,7 @@
.sstv-general-image-card:hover { .sstv-general-image-card:hover {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.2); box-shadow: 0 4px 12px rgba(var(--accent-cyan-rgb), 0.2);
} }
.sstv-general-image-card-inner { .sstv-general-image-card-inner {
+2 -2
View File
@@ -401,7 +401,7 @@
.sstv-image-card:hover { .sstv-image-card:hover {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.2); box-shadow: 0 4px 12px rgba(var(--accent-cyan-rgb), 0.2);
} }
.sstv-image-card-inner { .sstv-image-card-inner {
@@ -688,7 +688,7 @@
font-weight: 700; font-weight: 700;
color: var(--accent-cyan); color: var(--accent-cyan);
letter-spacing: 2px; letter-spacing: 2px;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.3); text-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.3);
} }
.sstv-countdown-value.imminent { .sstv-countdown-value.imminent {
+29 -29
View File
@@ -300,7 +300,7 @@
} }
.subghz-btn.active { .subghz-btn.active {
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border-color: var(--accent-cyan, var(--accent-cyan)); border-color: var(--accent-cyan, var(--accent-cyan));
color: var(--accent-cyan, var(--accent-cyan)); color: var(--accent-cyan, var(--accent-cyan));
} }
@@ -384,9 +384,9 @@
} }
.subghz-capture-card.selected { .subghz-capture-card.selected {
border-color: rgba(0, 212, 255, 0.85); border-color: rgba(var(--accent-cyan-rgb), 0.85);
box-shadow: 0 0 0 1px rgba(0, 212, 255, 0.3); box-shadow: 0 0 0 1px rgba(var(--accent-cyan-rgb), 0.3);
background: rgba(0, 212, 255, 0.06); background: rgba(var(--accent-cyan-rgb), 0.06);
} }
.subghz-capture-header { .subghz-capture-header {
@@ -455,9 +455,9 @@
} }
.subghz-capture-tag.auto { .subghz-capture-tag.auto {
border-color: rgba(0, 212, 255, 0.55); border-color: rgba(var(--accent-cyan-rgb), 0.55);
color: var(--accent-cyan); color: var(--accent-cyan);
background: rgba(0, 212, 255, 0.12); background: rgba(var(--accent-cyan-rgb), 0.12);
} }
.subghz-capture-tag.hint { .subghz-capture-tag.hint {
@@ -536,13 +536,13 @@
} }
.subghz-capture-actions button.select-btn { .subghz-capture-actions button.select-btn {
border-color: rgba(0, 212, 255, 0.5); border-color: rgba(var(--accent-cyan-rgb), 0.5);
color: var(--accent-cyan); color: var(--accent-cyan);
} }
.subghz-capture-actions button.select-btn.selected { .subghz-capture-actions button.select-btn.selected {
border-color: rgba(0, 212, 255, 0.9); border-color: rgba(var(--accent-cyan-rgb), 0.9);
background: rgba(0, 212, 255, 0.18); background: rgba(var(--accent-cyan-rgb), 0.18);
color: #7beeff; color: #7beeff;
} }
@@ -781,14 +781,14 @@
height: 26px; height: 26px;
border: 1px solid var(--border-color, #2a3040); border: 1px solid var(--border-color, #2a3040);
border-radius: 4px; border-radius: 4px;
background: linear-gradient(90deg, rgba(0, 212, 255, 0.07), rgba(255, 170, 0, 0.07)); background: linear-gradient(90deg, rgba(var(--accent-cyan-rgb), 0.07), rgba(255, 170, 0, 0.07));
margin-bottom: 8px; margin-bottom: 8px;
overflow: hidden; overflow: hidden;
} }
.subghz-tx-burst-timeline.dragging { .subghz-tx-burst-timeline.dragging {
border-color: rgba(0, 212, 255, 0.65); border-color: rgba(var(--accent-cyan-rgb), 0.65);
box-shadow: 0 0 0 1px rgba(0, 212, 255, 0.25) inset; box-shadow: 0 0 0 1px rgba(var(--accent-cyan-rgb), 0.25) inset;
} }
.subghz-tx-burst-selection { .subghz-tx-burst-selection {
@@ -796,8 +796,8 @@
top: 3px; top: 3px;
bottom: 3px; bottom: 3px;
border-radius: 3px; border-radius: 3px;
border: 1px solid rgba(0, 212, 255, 0.95); border: 1px solid rgba(var(--accent-cyan-rgb), 0.95);
background: rgba(0, 212, 255, 0.22); background: rgba(var(--accent-cyan-rgb), 0.22);
pointer-events: none; pointer-events: none;
display: none; display: none;
z-index: 2; z-index: 2;
@@ -823,8 +823,8 @@
} }
.subghz-tx-burst-marker:hover { .subghz-tx-burst-marker:hover {
background: rgba(0, 212, 255, 0.85); background: rgba(var(--accent-cyan-rgb), 0.85);
border-color: rgba(0, 212, 255, 1); border-color: rgba(var(--accent-cyan-rgb), 1);
} }
.subghz-tx-burst-list { .subghz-tx-burst-list {
@@ -861,7 +861,7 @@
.subghz-tx-burst-item button { .subghz-tx-burst-item button {
padding: 2px 8px; padding: 2px 8px;
border: 1px solid rgba(0, 212, 255, 0.5); border: 1px solid rgba(var(--accent-cyan-rgb), 0.5);
border-radius: 3px; border-radius: 3px;
background: transparent; background: transparent;
color: var(--accent-cyan); color: var(--accent-cyan);
@@ -871,7 +871,7 @@
} }
.subghz-tx-burst-item button:hover { .subghz-tx-burst-item button:hover {
background: rgba(0, 212, 255, 0.12); background: rgba(var(--accent-cyan-rgb), 0.12);
} }
.subghz-tx-modal-actions { .subghz-tx-modal-actions {
@@ -901,13 +901,13 @@
} }
.subghz-tx-trim-btn { .subghz-tx-trim-btn {
background: rgba(0, 212, 255, 0.14); background: rgba(var(--accent-cyan-rgb), 0.14);
color: var(--accent-cyan); color: var(--accent-cyan);
border-color: rgba(0, 212, 255, 0.55) !important; border-color: rgba(var(--accent-cyan-rgb), 0.55) !important;
} }
.subghz-tx-trim-btn:hover { .subghz-tx-trim-btn:hover {
background: rgba(0, 212, 255, 0.26); background: rgba(var(--accent-cyan-rgb), 0.26);
} }
.subghz-tx-cancel-btn { .subghz-tx-cancel-btn {
@@ -1043,7 +1043,7 @@
} }
.subghz-action-btn.decode:hover { .subghz-action-btn.decode:hover {
background: rgba(0, 212, 255, 0.12); background: rgba(var(--accent-cyan-rgb), 0.12);
border-color: var(--accent-cyan, var(--accent-cyan)); border-color: var(--accent-cyan, var(--accent-cyan));
color: var(--accent-cyan, var(--accent-cyan)); color: var(--accent-cyan, var(--accent-cyan));
} }
@@ -1275,7 +1275,7 @@
.subghz-phase-step.active { .subghz-phase-step.active {
color: var(--accent-cyan, var(--accent-cyan)); color: var(--accent-cyan, var(--accent-cyan));
text-shadow: 0 0 6px rgba(0, 212, 255, 0.3); text-shadow: 0 0 6px rgba(var(--accent-cyan-rgb), 0.3);
} }
.subghz-phase-step.completed { .subghz-phase-step.completed {
@@ -1328,9 +1328,9 @@
} }
.subghz-burst-indicator.recent { .subghz-burst-indicator.recent {
border-color: rgba(0, 212, 255, 0.45); border-color: rgba(var(--accent-cyan-rgb), 0.45);
color: var(--accent-cyan); color: var(--accent-cyan);
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
} }
.subghz-burst-indicator.recent .subghz-burst-dot { .subghz-burst-indicator.recent .subghz-burst-dot {
@@ -1445,8 +1445,8 @@
transform: translateY(0); transform: translateY(0);
} }
.subghz-hub-card--cyan { border-color: rgba(0, 212, 255, 0.2); } .subghz-hub-card--cyan { border-color: rgba(var(--accent-cyan-rgb), 0.2); }
.subghz-hub-card--cyan:hover { border-color: var(--accent-cyan, var(--accent-cyan)); background: rgba(0, 212, 255, 0.05); } .subghz-hub-card--cyan:hover { border-color: var(--accent-cyan, var(--accent-cyan)); background: rgba(var(--accent-cyan-rgb), 0.05); }
.subghz-hub-card--cyan .subghz-hub-icon { color: var(--accent-cyan, var(--accent-cyan)); } .subghz-hub-card--cyan .subghz-hub-icon { color: var(--accent-cyan, var(--accent-cyan)); }
.subghz-hub-card--green { border-color: rgba(0, 255, 136, 0.2); } .subghz-hub-card--green { border-color: rgba(0, 255, 136, 0.2); }
@@ -1736,8 +1736,8 @@
.subghz-rx-burst-pill.recent { .subghz-rx-burst-pill.recent {
color: var(--accent-cyan); color: var(--accent-cyan);
border-color: rgba(0, 212, 255, 0.65); border-color: rgba(var(--accent-cyan-rgb), 0.65);
background: rgba(0, 212, 255, 0.12); background: rgba(var(--accent-cyan-rgb), 0.12);
} }
.subghz-rx-level-label { .subghz-rx-level-label {
+2 -2
View File
@@ -17,7 +17,7 @@
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.12em; letter-spacing: 0.12em;
color: var(--accent-cyan, #00d4ff); color: var(--accent-cyan, #00d4ff);
border-bottom: 1px solid rgba(0, 212, 255, 0.2); border-bottom: 1px solid rgba(var(--accent-cyan-rgb), 0.2);
padding-bottom: 6px; padding-bottom: 6px;
margin-top: 8px; margin-top: 8px;
} }
@@ -221,7 +221,7 @@
fill: none; fill: none;
stroke: var(--accent-cyan, #00d4ff); stroke: var(--accent-cyan, #00d4ff);
stroke-width: 1.5; stroke-width: 1.5;
filter: drop-shadow(0 0 2px rgba(0, 212, 255, 0.4)); filter: drop-shadow(0 0 2px rgba(var(--accent-cyan-rgb), 0.4));
} }
.sys-sparkline-area { .sys-sparkline-area {
+136
View File
@@ -1180,3 +1180,139 @@
font-family: var(--font-mono, monospace); font-family: var(--font-mono, monospace);
font-size: 9px; font-size: 9px;
} }
/* ---- Enhanced tier: replace blue palette with signals teal ---- */
html[data-ui-tier="enhanced"] .wf-container {
--wf-border: rgba(46, 125, 138, 0.22);
--wf-surface: linear-gradient(180deg, rgba(2, 6, 6, 0.97) 0%, rgba(1, 3, 3, 0.98) 100%);
background: radial-gradient(circle at 14% -18%, rgba(46, 125, 138, 0.08) 0%, rgba(46, 125, 138, 0) 38%),
radial-gradient(circle at 86% -26%, rgba(46, 125, 138, 0.05) 0%, rgba(46, 125, 138, 0) 36%),
#000202;
}
html[data-ui-tier="enhanced"] .wf-headline {
background: rgba(2, 6, 6, 0.86);
}
html[data-ui-tier="enhanced"] .wf-headline-tag {
color: rgba(70, 185, 200, 0.90);
}
html[data-ui-tier="enhanced"] .wf-rx-vfo {
border-color: rgba(46, 125, 138, 0.25);
background: linear-gradient(180deg, rgba(3, 8, 8, 0.92) 0%, rgba(1, 4, 4, 0.95) 100%);
}
html[data-ui-tier="enhanced"] .wf-rx-vfo-status {
color: rgba(65, 175, 192, 0.88);
}
html[data-ui-tier="enhanced"] .wf-rx-vfo-readout {
color: rgba(80, 190, 205, 0.92);
}
html[data-ui-tier="enhanced"] #wfRxFreqReadout {
text-shadow: 0 0 16px rgba(46, 125, 138, 0.25);
}
html[data-ui-tier="enhanced"] .wf-rx-modebank {
border-color: rgba(46, 125, 138, 0.22);
background: rgba(1, 4, 4, 0.86);
}
html[data-ui-tier="enhanced"] .wf-mode-btn {
border-color: rgba(46, 125, 138, 0.24);
background: linear-gradient(180deg, rgba(4, 8, 8, 0.95) 0%, rgba(2, 5, 5, 0.95) 100%);
color: rgba(65, 175, 192, 0.90);
}
html[data-ui-tier="enhanced"] .wf-mode-btn:hover {
border-color: rgba(46, 125, 138, 0.48);
}
html[data-ui-tier="enhanced"] .wf-mode-btn.is-active,
html[data-ui-tier="enhanced"] .wf-mode-btn.active {
border-color: rgba(46, 125, 138, 0.62);
background: linear-gradient(180deg, rgba(6, 16, 18, 0.92) 0%, rgba(4, 12, 14, 0.95) 100%);
color: rgba(100, 210, 222, 0.96);
box-shadow: 0 0 14px rgba(46, 125, 138, 0.22);
}
html[data-ui-tier="enhanced"] .wf-rx-levels,
html[data-ui-tier="enhanced"] .wf-rx-meter-wrap,
html[data-ui-tier="enhanced"] .wf-rx-actions {
border-color: rgba(46, 125, 138, 0.20);
background: rgba(1, 4, 4, 0.85);
}
html[data-ui-tier="enhanced"] .wf-monitor-select {
border-color: rgba(46, 125, 138, 0.25);
background: rgba(1, 3, 3, 0.8);
}
html[data-ui-tier="enhanced"] .wf-rx-smeter-fill {
box-shadow: 0 0 10px rgba(46, 125, 138, 0.22);
}
html[data-ui-tier="enhanced"] .wf-monitor-btn-secondary {
border-color: rgba(46, 125, 138, 0.45);
background: linear-gradient(180deg, rgba(4, 12, 14, 0.95) 0%, rgba(2, 8, 10, 0.95) 100%);
color: rgba(80, 190, 205, 0.90);
}
html[data-ui-tier="enhanced"] .wf-freq-bar {
background: rgba(2, 6, 6, 0.78);
}
html[data-ui-tier="enhanced"] .wf-spectrum-canvas-wrap {
background: radial-gradient(circle at 50% -120%, rgba(46, 125, 138, 0.06) 0%, rgba(46, 125, 138, 0) 65%);
}
html[data-ui-tier="enhanced"] .wf-band-strip {
background: linear-gradient(180deg, rgba(2, 6, 6, 0.96) 0%, rgba(1, 3, 3, 0.98) 100%);
}
html[data-ui-tier="enhanced"] .wf-band-block {
border-color: rgba(46, 125, 138, 0.42);
color: rgba(80, 190, 205, 0.92);
}
html[data-ui-tier="enhanced"] .wf-band-edge {
color: rgba(65, 175, 192, 0.88);
}
html[data-ui-tier="enhanced"] .wf-band-marker::before {
background: rgba(46, 125, 138, 0.58);
box-shadow: 0 0 5px rgba(46, 125, 138, 0.30);
}
html[data-ui-tier="enhanced"] .wf-band-marker-label {
border-color: rgba(46, 125, 138, 0.48);
background: rgba(2, 5, 5, 0.95);
color: rgba(80, 190, 205, 0.90);
}
html[data-ui-tier="enhanced"] .wf-tune-line {
background: rgba(46, 125, 138, 0.72);
}
html[data-ui-tier="enhanced"] .wf-freq-axis {
background: rgba(2, 6, 6, 0.86);
}
html[data-ui-tier="enhanced"] .wf-side .section.wf-side-hero {
background: linear-gradient(180deg, rgba(3, 8, 8, 0.95) 0%, rgba(1, 4, 4, 0.97) 100%);
border-color: rgba(46, 125, 138, 0.30);
box-shadow: 0 8px 24px rgba(0, 8, 10, 0.30), inset 0 0 0 1px rgba(255, 255, 255, 0.03);
}
html[data-ui-tier="enhanced"] .wf-side-chip {
color: rgba(65, 175, 192, 0.88);
border-color: rgba(46, 125, 138, 0.32);
background: rgba(6, 16, 18, 0.30);
}
html[data-ui-tier="enhanced"] .wf-side-stat {
border-color: rgba(46, 125, 138, 0.20);
}
+9 -9
View File
@@ -308,7 +308,7 @@
opacity: 1; opacity: 1;
} }
.wxsat-timeline-pass.apt { background: rgba(0, 212, 255, 0.6); } .wxsat-timeline-pass.apt { background: rgba(var(--accent-cyan-rgb), 0.6); }
.wxsat-timeline-pass.lrpt { background: rgba(0, 255, 136, 0.6); } .wxsat-timeline-pass.lrpt { background: rgba(0, 255, 136, 0.6); }
.wxsat-timeline-pass.scheduled { border: 1px solid var(--accent-yellow); } .wxsat-timeline-pass.scheduled { border: 1px solid var(--accent-yellow); }
@@ -585,7 +585,7 @@
} }
.wxsat-pass-mode.apt { .wxsat-pass-mode.apt {
background: rgba(0, 212, 255, 0.15); background: rgba(var(--accent-cyan-rgb), 0.15);
color: var(--accent-cyan); color: var(--accent-cyan);
} }
@@ -626,7 +626,7 @@
} }
.wxsat-pass-quality.good { .wxsat-pass-quality.good {
background: rgba(0, 212, 255, 0.15); background: rgba(var(--accent-cyan-rgb), 0.15);
color: var(--accent-cyan); color: var(--accent-cyan);
} }
@@ -759,16 +759,16 @@
.wxsat-map-tooltip { .wxsat-map-tooltip {
background: rgba(5, 15, 32, 0.92); background: rgba(5, 15, 32, 0.92);
border: 1px solid rgba(102, 229, 255, 0.65); border: 1px solid rgba(var(--accent-cyan-rgb), 0.65);
border-radius: 4px; border-radius: 4px;
color: #8fe8ff; color: var(--accent-cyan);
box-shadow: 0 0 12px rgba(0, 210, 255, 0.24); box-shadow: 0 0 12px rgba(0, 210, 255, 0.24);
font-size: 10px; font-size: 10px;
letter-spacing: 0.25px; letter-spacing: 0.25px;
} }
.wxsat-map-tooltip.leaflet-tooltip-top:before { .wxsat-map-tooltip.leaflet-tooltip-top:before {
border-top-color: rgba(102, 229, 255, 0.65); border-top-color: rgba(var(--accent-cyan-rgb), 0.65);
} }
/* ===== Image Gallery Panel ===== */ /* ===== Image Gallery Panel ===== */
@@ -1221,8 +1221,8 @@
.wxsat-phase-step.completed { .wxsat-phase-step.completed {
color: var(--accent-cyan, #00d4ff); color: var(--accent-cyan, #00d4ff);
border-color: rgba(0, 212, 255, 0.3); border-color: rgba(var(--accent-cyan-rgb), 0.3);
background: rgba(0, 212, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
opacity: 0.7; opacity: 0.7;
} }
@@ -1267,7 +1267,7 @@
.wxsat-console-filter.active { .wxsat-console-filter.active {
border-color: var(--accent-cyan, #00d4ff); border-color: var(--accent-cyan, #00d4ff);
color: var(--accent-cyan, #00d4ff); color: var(--accent-cyan, #00d4ff);
background: rgba(0, 212, 255, 0.08); background: rgba(var(--accent-cyan-rgb), 0.08);
} }
.wxsat-console-actions { .wxsat-console-actions {
+30 -31
View File
@@ -157,7 +157,7 @@ body {
.stat-badge { .stat-badge {
background: var(--bg-card); background: var(--bg-card);
border: 1px solid rgba(0, 212, 255, 0.3); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
border-radius: 4px; border-radius: 4px;
padding: 4px 10px; padding: 4px 10px;
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -309,7 +309,7 @@ body {
/* Panels */ /* Panels */
.panel { .panel {
background: var(--bg-panel); background: var(--bg-panel);
border: 1px solid rgba(0, 212, 255, 0.2); border: 1px solid rgba(var(--accent-cyan-rgb), 0.2);
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
@@ -326,8 +326,8 @@ body {
.panel-header { .panel-header {
padding: 10px 15px; padding: 10px 15px;
background: rgba(0, 212, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
border-bottom: 1px solid rgba(0, 212, 255, 0.1); border-bottom: 1px solid rgba(var(--accent-cyan-rgb), 0.1);
font-family: 'Orbitron', monospace; font-family: 'Orbitron', monospace;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
@@ -955,7 +955,7 @@ body {
.packet-entry { .packet-entry {
padding: 7px 10px; padding: 7px 10px;
border-bottom: 1px solid rgba(0, 212, 255, 0.08); border-bottom: 1px solid rgba(var(--accent-cyan-rgb), 0.08);
font-size: 10px; font-size: 10px;
font-family: var(--font-mono); font-family: var(--font-mono);
word-break: break-word; word-break: break-word;
@@ -1010,7 +1010,7 @@ body {
.packet-modal { .packet-modal {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 2000; z-index: 5000;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -1140,7 +1140,7 @@ body {
gap: 10px; gap: 10px;
padding: 10px; padding: 10px;
background: var(--bg-panel); background: var(--bg-panel);
border-bottom: 1px solid rgba(0, 212, 255, 0.2); border-bottom: 1px solid rgba(var(--accent-cyan-rgb), 0.2);
} }
.satellite-selector label { .satellite-selector label {
@@ -1153,7 +1153,7 @@ body {
.satellite-selector select { .satellite-selector select {
flex: 1; flex: 1;
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border: 1px solid var(--accent-cyan); border: 1px solid var(--accent-cyan);
border-radius: 4px; border-radius: 4px;
padding: 8px 12px; padding: 8px 12px;
@@ -1166,12 +1166,12 @@ body {
.satellite-selector select:focus { .satellite-selector select:focus {
outline: none; outline: none;
box-shadow: 0 0 15px rgba(0, 212, 255, 0.3); box-shadow: 0 0 15px rgba(var(--accent-cyan-rgb), 0.3);
} }
#satRefreshBtn { #satRefreshBtn {
background: transparent; background: transparent;
border: 1px solid rgba(0, 212, 255, 0.4); border: 1px solid rgba(var(--accent-cyan-rgb), 0.4);
border-radius: 4px; border-radius: 4px;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
@@ -1199,7 +1199,7 @@ body {
/* Countdown panel */ /* Countdown panel */
.countdown-panel { .countdown-panel {
flex-shrink: 0; flex-shrink: 0;
background: linear-gradient(135deg, rgba(0, 212, 255, 0.1) 0%, rgba(0, 255, 136, 0.05) 100%); background: linear-gradient(135deg, rgba(var(--accent-cyan-rgb), 0.1) 0%, rgba(0, 255, 136, 0.05) 100%);
} }
.countdown-display { .countdown-display {
@@ -1232,7 +1232,7 @@ body {
.countdown-block { .countdown-block {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 212, 255, 0.2); border: 1px solid rgba(var(--accent-cyan-rgb), 0.2);
border-radius: 4px; border-radius: 4px;
padding: 8px 4px; padding: 8px 4px;
text-align: center; text-align: center;
@@ -1359,7 +1359,7 @@ body {
.pass-item { .pass-item {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 212, 255, 0.15); border: 1px solid rgba(var(--accent-cyan-rgb), 0.15);
border-radius: 4px; border-radius: 4px;
padding: 8px 10px; padding: 8px 10px;
margin-bottom: 6px; margin-bottom: 6px;
@@ -1369,13 +1369,13 @@ body {
.pass-item:hover { .pass-item:hover {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
background: rgba(0, 212, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
} }
.pass-item.active { .pass-item.active {
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
box-shadow: 0 0 15px rgba(0, 212, 255, 0.2); box-shadow: 0 0 15px rgba(var(--accent-cyan-rgb), 0.2);
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
} }
.pass-item-header { .pass-item-header {
@@ -1407,7 +1407,7 @@ body {
} }
.pass-quality.good { .pass-quality.good {
background: rgba(0, 212, 255, 0.2); background: rgba(var(--accent-cyan-rgb), 0.2);
color: var(--accent-cyan); color: var(--accent-cyan);
} }
@@ -1434,7 +1434,7 @@ body {
gap: 20px; gap: 20px;
padding: 10px 20px; padding: 10px 20px;
background: var(--bg-panel); background: var(--bg-panel);
border-top: 1px solid rgba(0, 212, 255, 0.3); border-top: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
} }
.control-group { .control-group {
@@ -1455,7 +1455,7 @@ body {
width: 90px; width: 90px;
padding: 6px 8px; padding: 6px 8px;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 212, 255, 0.3); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3);
border-radius: 4px; border-radius: 4px;
color: var(--accent-cyan); color: var(--accent-cyan);
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -1465,13 +1465,13 @@ body {
.control-group input:focus { .control-group input:focus {
outline: none; outline: none;
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.2); box-shadow: 0 0 10px rgba(var(--accent-cyan-rgb), 0.2);
} }
.btn { .btn {
padding: 8px 16px; padding: 8px 16px;
border: 1px solid var(--accent-cyan); border: 1px solid var(--accent-cyan);
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
color: var(--accent-cyan); color: var(--accent-cyan);
font-family: 'Orbitron', monospace; font-family: 'Orbitron', monospace;
font-size: 11px; font-size: 11px;
@@ -1486,7 +1486,7 @@ body {
.btn:hover { .btn:hover {
background: var(--accent-cyan); background: var(--accent-cyan);
color: var(--bg-dark); color: var(--bg-dark);
box-shadow: 0 0 20px rgba(0, 212, 255, 0.3); box-shadow: 0 0 20px rgba(var(--accent-cyan-rgb), 0.3);
} }
.btn.primary { .btn.primary {
@@ -1780,17 +1780,16 @@ body.embedded .controls-bar {
} }
/* ============================================ /* ============================================
ENHANCED TIER amber military console ENHANCED TIER signals teal console
============================================ */ ============================================ */
html[data-ui-tier="enhanced"] { html[data-ui-tier="enhanced"] {
--bg-dark: #080600; --bg-dark: #000000;
--bg-panel: #0c0a04; --bg-panel: #020404;
--bg-card: #0e0b05; --bg-card: #020404;
--border-glow: rgba(200, 150, 40, 0.25); --border-glow: rgba(46, 125, 138, 0.20);
--border-color: rgba(200, 150, 40, 0.2); --border-color: rgba(46, 125, 138, 0.18);
--grid-line: rgba(200, 150, 40, 0.07); --grid-line: rgba(46, 125, 138, 0.07);
--accent-cyan: #c89628; --accent-cyan: #2e7d8a;
--accent-green: #c89628;
} }
/* ============================================ /* ============================================
+68 -9
View File
@@ -266,7 +266,7 @@
} }
.toggle-switch input:focus + .toggle-slider { .toggle-switch input:focus + .toggle-slider {
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.3); box-shadow: 0 0 0 2px rgba(var(--accent-cyan-rgb), 0.3);
} }
/* Select Dropdown */ /* Select Dropdown */
@@ -461,8 +461,8 @@
/* Info Callout */ /* Info Callout */
.settings-info { .settings-info {
background: rgba(0, 212, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border: 1px solid rgba(0, 212, 255, 0.2); border: 1px solid rgba(var(--accent-cyan-rgb), 0.2);
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
margin-top: 16px; margin-top: 16px;
@@ -474,25 +474,32 @@
color: var(--accent-cyan, #00d4ff); color: var(--accent-cyan, #00d4ff);
} }
/* Map tile variants */ /* Map tile variants — teal tint, skipped in lean mode */
.tile-layer-cyan { html:not([data-ui-tier="lean"]) .tile-layer-cyan {
filter: sepia(0.35) hue-rotate(185deg) saturate(1.75) brightness(1.06) contrast(1.05); filter: sepia(0.35) hue-rotate(185deg) saturate(1.75) brightness(1.06) contrast(1.05);
} }
/* Global Leaflet map theme: cyber overlay */ /* Lean mode: suppress every map filter unconditionally */
html[data-ui-tier="lean"] .leaflet-tile-pane,
html[data-ui-tier="lean"] .leaflet-tile,
html[data-ui-tier="lean"] .tile-layer-cyan {
filter: none !important;
}
/* Global Leaflet map theme: cyber overlay — default and enhanced only, not lean */
.leaflet-container.map-theme-cyber { .leaflet-container.map-theme-cyber {
position: relative; position: relative;
background: #020813; background: #020813;
isolation: isolate; isolation: isolate;
} }
.leaflet-container.map-theme-cyber .leaflet-tile-pane { html:not([data-ui-tier="lean"]) .leaflet-container.map-theme-cyber .leaflet-tile-pane {
filter: sepia(0.74) hue-rotate(176deg) saturate(1.72) brightness(1.05) contrast(1.08); filter: sepia(0.74) hue-rotate(176deg) saturate(1.72) brightness(1.05) contrast(1.08);
opacity: 1; opacity: 1;
} }
/* Hard global fallback: enforce cyber tint on all Leaflet tile images */ /* Hard global fallback: enforce cyber tint on all Leaflet tile images — not lean */
html.map-cyber-enabled .leaflet-container .leaflet-tile { html:not([data-ui-tier="lean"]).map-cyber-enabled .leaflet-container .leaflet-tile {
filter: sepia(0.74) hue-rotate(176deg) saturate(1.72) brightness(1.05) contrast(1.08) !important; filter: sepia(0.74) hue-rotate(176deg) saturate(1.72) brightness(1.05) contrast(1.08) !important;
} }
@@ -527,6 +534,58 @@ html.map-cyber-enabled .leaflet-container::after {
background-size: 52px 52px, 52px 52px; background-size: 52px 52px, 52px 52px;
} }
/* Lean tier: no overlays, no filters — original tile colors */
html[data-ui-tier="lean"] .leaflet-container::before,
html[data-ui-tier="lean"] .leaflet-container::after {
background: none !important;
background-image: none !important;
}
/* Enhanced tier: signals teal map tint overrides — global (all Leaflet maps) */
html[data-ui-tier="enhanced"] .leaflet-container {
background: #000000;
}
html[data-ui-tier="enhanced"] .leaflet-container::before {
background: none !important;
}
html[data-ui-tier="enhanced"] .leaflet-tile-pane {
filter: sepia(0.85) hue-rotate(150deg) saturate(1.35) brightness(0.78) contrast(1.08);
}
/* Narrower overrides for cyber-specific classes (same filter, kept for !important precedence) */
html[data-ui-tier="enhanced"] .leaflet-container.map-theme-cyber {
background: #000000;
}
html[data-ui-tier="enhanced"] .tile-layer-cyan {
filter: sepia(0.85) hue-rotate(150deg) saturate(1.35) brightness(0.78) contrast(1.08);
}
html[data-ui-tier="enhanced"] .leaflet-container.map-theme-cyber .leaflet-tile-pane {
filter: sepia(0.85) hue-rotate(150deg) saturate(1.35) brightness(0.78) contrast(1.08);
}
html[data-ui-tier="enhanced"].map-cyber-enabled .leaflet-container {
background: #000000;
}
html[data-ui-tier="enhanced"].map-cyber-enabled .leaflet-container .leaflet-tile {
filter: sepia(0.85) hue-rotate(150deg) saturate(1.35) brightness(0.78) contrast(1.08) !important;
}
html[data-ui-tier="enhanced"].map-cyber-enabled .leaflet-container::before {
background: none;
}
html[data-ui-tier="enhanced"].map-cyber-enabled .leaflet-container::after {
background-image:
linear-gradient(rgba(46, 125, 138, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(46, 125, 138, 0.1) 1px, transparent 1px);
background-size: 52px 52px, 52px 52px;
}
/* Responsive */ /* Responsive */
@media (max-width: 1023px) { @media (max-width: 1023px) {
.settings-tabs { .settings-tabs {
+3 -3
View File
@@ -216,9 +216,9 @@ const FirstRunSetup = (function() {
el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#aaa;font-family:monospace;"><span>INTERCEPT</span><span style="color:#4a4;">● LIVE</span></div><div style="font-size:7px;color:#888;font-family:monospace;margin-top:2px;">ADS-B ........... 247<br>TSCM ............ 3 ⚠</div>'; el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#aaa;font-family:monospace;"><span>INTERCEPT</span><span style="color:#4a4;">● LIVE</span></div><div style="font-size:7px;color:#888;font-family:monospace;margin-top:2px;">ADS-B ........... 247<br>TSCM ............ 3 ⚠</div>';
}); });
const enhancedBtn = makeTierBtn('enhanced', 'Enhanced', 'Amber military console — for desktop or laptop.', (el) => { const enhancedBtn = makeTierBtn('enhanced', 'Enhanced', 'Signals teal console — for desktop or laptop.', (el) => {
el.style.cssText += 'background:#080600;border:1px solid rgba(200,150,40,0.3);display:flex;flex-direction:column;justify-content:center;padding:6px;gap:3px;'; el.style.cssText += 'background:#000202;border:1px solid rgba(46,125,138,0.3);display:flex;flex-direction:column;justify-content:center;padding:6px;gap:3px;';
el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#c89628;font-family:monospace;letter-spacing:2px;"><span>INTERCEPT</span><span style="opacity:0.6;">14:27Z</span></div><div style="border-left:2px solid #c89628;padding-left:4px;margin-top:4px;font-size:8px;color:#c89628;font-family:monospace;font-weight:700;">247 ADS-B</div>'; el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#2e7d8a;font-family:monospace;letter-spacing:2px;"><span>INTERCEPT</span><span style="opacity:0.6;">14:27Z</span></div><div style="border-left:2px solid #2e7d8a;padding-left:4px;margin-top:4px;font-size:8px;color:#2e7d8a;font-family:monospace;font-weight:700;">247 ADS-B</div>';
}); });
btnWrap.appendChild(leanBtn); btnWrap.appendChild(leanBtn);
+7 -6
View File
@@ -16,17 +16,18 @@ const VoiceAlerts = (function () {
const PITCH_MIN = 0.5; const PITCH_MIN = 0.5;
const PITCH_MAX = 2.0; const PITCH_MAX = 2.0;
// Default config // Default config — streams are opt-in to avoid saturating the browser's
// HTTP/1.1 per-origin connection limit (6) when multiple tabs are open.
let _config = { let _config = {
rate: 1.1, rate: 1.1,
pitch: 0.9, pitch: 0.9,
voiceName: '', voiceName: '',
streams: { streams: {
pager: true, pager: false,
tscm: true, tscm: false,
bluetooth: true, bluetooth: false,
adsb_military: true, adsb_military: false,
squawks: true, squawks: false,
}, },
}; };
+2 -21
View File
@@ -197,30 +197,11 @@ const BtLocate = (function() {
// Init map // Init map
const mapEl = document.getElementById('btLocateMap'); const mapEl = document.getElementById('btLocateMap');
if (mapEl && typeof L !== 'undefined') { if (mapEl && typeof L !== 'undefined' && typeof MapUtils !== 'undefined') {
map = L.map('btLocateMap', { map = MapUtils.init('btLocateMap', {
center: [0, 0], center: [0, 0],
zoom: 2, zoom: 2,
zoomControl: true,
}); });
let tileLayer = null;
// Use tile provider from user settings
if (typeof Settings !== 'undefined' && Settings.createTileLayer) {
tileLayer = Settings.createTileLayer();
tileLayer.addTo(map);
Settings.registerMap(map);
} else {
tileLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
maxZoom: 19,
attribution: '&copy; OSM &copy; CARTO'
});
tileLayer.addTo(map);
}
if (tileLayer && typeof tileLayer.on === 'function') {
tileLayer.on('load', () => {
scheduleMapStabilization(8);
});
}
ensureHeatLayer(); ensureHeatLayer();
syncMovementLayer(); syncMovementLayer();
syncHeatLayer(); syncHeatLayer();
+3
View File
@@ -43,6 +43,9 @@ var OokMode = (function () {
} }
function destroy() { function destroy() {
if (state.running) {
stop();
}
disconnectSSE(); disconnectSSE();
} }
+5 -1
View File
@@ -874,7 +874,11 @@ const SSTVGeneral = (function() {
* Destroy close SSE stream and stop scope animation for clean mode switching. * Destroy close SSE stream and stop scope animation for clean mode switching.
*/ */
function destroy() { function destroy() {
stopStream(); if (isRunning) {
stop().catch(() => {});
} else {
stopStream();
}
} }
// Public API // Public API
+7 -3
View File
@@ -1428,9 +1428,13 @@ const SSTV = (function() {
* Destroy close SSE stream and clear ISS tracking/countdown timers for clean mode switching. * Destroy close SSE stream and clear ISS tracking/countdown timers for clean mode switching.
*/ */
function destroy() { function destroy() {
if (eventSource) { if (isRunning) {
eventSource.close(); stop().catch(() => {});
eventSource = null; } else {
if (eventSource) {
eventSource.close();
eventSource = null;
}
} }
stopIssTracking(); stopIssTracking();
stopCountdown(); stopCountdown();
+10 -2
View File
@@ -3014,10 +3014,14 @@ const Waterfall = (function () {
} }
// Backend stop is fire-and-forget; UI is already updated above. // Backend stop is fire-and-forget; UI is already updated above.
const _audioStopCtrl = new AbortController();
const _audioStopTid = setTimeout(() => _audioStopCtrl.abort(), 3000);
try { try {
await fetch('/receiver/audio/stop', { method: 'POST' }); await fetch('/receiver/audio/stop', { method: 'POST', signal: _audioStopCtrl.signal });
} catch (_) { } catch (_) {
// Ignore backend stop errors // Ignore backend stop errors
} finally {
clearTimeout(_audioStopTid);
} }
if (resumeWaterfall && _active) { if (resumeWaterfall && _active) {
@@ -3222,10 +3226,14 @@ const Waterfall = (function () {
if (_es) { if (_es) {
_closeSseStream(); _closeSseStream();
const _wfStopCtrl = new AbortController();
const _wfStopTid = setTimeout(() => _wfStopCtrl.abort(), 3000);
try { try {
await fetch('/receiver/waterfall/stop', { method: 'POST' }); await fetch('/receiver/waterfall/stop', { method: 'POST', signal: _wfStopCtrl.signal });
} catch (_) { } catch (_) {
// Ignore fallback stop errors. // Ignore fallback stop errors.
} finally {
clearTimeout(_wfStopTid);
} }
} }
+5 -1
View File
@@ -2344,7 +2344,11 @@ const WeatherSat = (function() {
clearInterval(countdownInterval); clearInterval(countdownInterval);
countdownInterval = null; countdownInterval = null;
} }
stopStream(); if (isRunning) {
stop().catch(() => {});
} else {
stopStream();
}
} }
/** /**
+15 -6
View File
@@ -260,7 +260,10 @@ function initWebsdrGlobe(mapEl) {
if (typeof window.Globe !== 'function' || !isWebglSupported()) return false; if (typeof window.Globe !== 'function' || !isWebglSupported()) return false;
mapEl.innerHTML = ''; mapEl.innerHTML = '';
mapEl.style.background = 'radial-gradient(circle at 30% 20%, rgba(14, 42, 68, 0.9), rgba(4, 9, 16, 0.95) 58%, rgba(2, 4, 9, 0.98) 100%)'; const _wsdrTier = document.documentElement.getAttribute('data-ui-tier') || 'enhanced';
mapEl.style.background = _wsdrTier === 'enhanced'
? 'radial-gradient(circle at 30% 20%, rgba(4, 18, 22, 0.92), rgba(2, 8, 10, 0.96) 58%, rgba(0, 2, 2, 0.99) 100%)'
: 'radial-gradient(circle at 30% 20%, rgba(14, 42, 68, 0.9), rgba(4, 9, 16, 0.95) 58%, rgba(2, 4, 9, 0.98) 100%)';
mapEl.style.cursor = 'grab'; mapEl.style.cursor = 'grab';
const _wsdrAccent = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#3bb9ff'; const _wsdrAccent = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#3bb9ff';
@@ -296,6 +299,8 @@ function initWebsdrGlobe(mapEl) {
ensureWebsdrGlobePopup(mapEl); ensureWebsdrGlobePopup(mapEl);
resizeWebsdrGlobe(); resizeWebsdrGlobe();
// Grid layout may not have settled on the first rAF; re-sync after one frame.
requestAnimationFrame(() => resizeWebsdrGlobe());
return true; return true;
} }
@@ -475,8 +480,10 @@ function ensureWebsdrGlobePopup(mapEl) {
websdrGlobePopup.style.maxWidth = '260px'; websdrGlobePopup.style.maxWidth = '260px';
websdrGlobePopup.style.padding = '10px'; websdrGlobePopup.style.padding = '10px';
websdrGlobePopup.style.borderRadius = '8px'; websdrGlobePopup.style.borderRadius = '8px';
websdrGlobePopup.style.border = '1px solid rgba(0, 212, 255, 0.35)'; const _wsdrPopupRgb = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan-rgb').trim() || '0, 212, 255';
websdrGlobePopup.style.background = 'rgba(5, 13, 20, 0.92)'; const _wsdrPopupTier = document.documentElement.getAttribute('data-ui-tier') || 'enhanced';
websdrGlobePopup.style.border = `1px solid rgba(${_wsdrPopupRgb}, 0.35)`;
websdrGlobePopup.style.background = _wsdrPopupTier === 'enhanced' ? 'rgba(2, 8, 10, 0.94)' : 'rgba(5, 13, 20, 0.92)';
websdrGlobePopup.style.backdropFilter = 'blur(4px)'; websdrGlobePopup.style.backdropFilter = 'blur(4px)';
websdrGlobePopup.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.4)'; websdrGlobePopup.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.4)';
websdrGlobePopup.style.color = 'var(--text-primary)'; websdrGlobePopup.style.color = 'var(--text-primary)';
@@ -574,8 +581,9 @@ function renderReceiverList(receivers) {
container.innerHTML = receivers.slice(0, 50).map((rx, idx) => { container.innerHTML = receivers.slice(0, 50).map((rx, idx) => {
const selected = idx === websdrSelectedReceiverIndex; const selected = idx === websdrSelectedReceiverIndex;
const baseBg = selected ? 'rgba(0,212,255,0.14)' : 'transparent'; const _wsdrRxRgb = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan-rgb').trim() || '0, 212, 255';
const hoverBg = selected ? 'rgba(0,212,255,0.18)' : 'rgba(0,212,255,0.05)'; const baseBg = selected ? `rgba(${_wsdrRxRgb},0.14)` : 'transparent';
const hoverBg = selected ? `rgba(${_wsdrRxRgb},0.18)` : `rgba(${_wsdrRxRgb},0.05)`;
return ` return `
<div style="padding: 8px 8px 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor: pointer; transition: background 0.2s; border-left: 2px solid ${selected ? 'var(--accent-cyan)' : 'transparent'}; background: ${baseBg};" <div style="padding: 8px 8px 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor: pointer; transition: background 0.2s; border-left: 2px solid ${selected ? 'var(--accent-cyan)' : 'transparent'}; background: ${baseBg};"
onmouseover="this.style.background='${hoverBg}'" onmouseout="this.style.background='${baseBg}'" onmouseover="this.style.background='${hoverBg}'" onmouseout="this.style.background='${baseBg}'"
@@ -951,13 +959,14 @@ function loadSpyStationPresets() {
return; return;
} }
const _wsdrSpyRgb = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan-rgb').trim() || '0, 212, 255';
container.innerHTML = stations.slice(0, 30).map(s => { container.innerHTML = stations.slice(0, 30).map(s => {
const primaryFreq = s.frequencies?.find(f => f.primary) || s.frequencies?.[0]; const primaryFreq = s.frequencies?.find(f => f.primary) || s.frequencies?.[0];
const freqKhz = primaryFreq?.freq_khz || 0; const freqKhz = primaryFreq?.freq_khz || 0;
return ` return `
<div style="padding: 6px 4px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor: pointer; display: flex; justify-content: space-between; align-items: center;" <div style="padding: 6px 4px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor: pointer; display: flex; justify-content: space-between; align-items: center;"
onclick="tuneToSpyStation('${escapeHtmlWebsdr(s.id)}', ${freqKhz})" onclick="tuneToSpyStation('${escapeHtmlWebsdr(s.id)}', ${freqKhz})"
onmouseover="this.style.background='rgba(0,212,255,0.05)'" onmouseout="this.style.background='transparent'"> onmouseover="this.style.background='rgba(${_wsdrSpyRgb},0.05)'" onmouseout="this.style.background='transparent'">
<div> <div>
<span style="color: var(--accent-cyan); font-weight: bold;">${escapeHtmlWebsdr(s.name)}</span> <span style="color: var(--accent-cyan); font-weight: bold;">${escapeHtmlWebsdr(s.name)}</span>
<span style="color: var(--text-muted); font-size: 9px; margin-left: 4px;">${escapeHtmlWebsdr(s.nickname || '')}</span> <span style="color: var(--text-muted); font-size: 9px; margin-left: 4px;">${escapeHtmlWebsdr(s.nickname || '')}</span>
+3
View File
@@ -58,6 +58,9 @@ var WeFax = (function () {
function destroy() { function destroy() {
closeImage(); closeImage();
if (state.running) {
stop();
}
disconnectSSE(); disconnectSSE();
stopScope(); stopScope();
stopCountdownTimer(); stopCountdownTimer();
+10 -13
View File
@@ -155,25 +155,22 @@ const WiFiMode = (function() {
// ========================================================================== // ==========================================================================
function init() { function init() {
console.log('[WiFiMode] Initializing...'); // Capabilities and one-time component setup only on first call.
// Subsequent visits refresh scan state and re-render without redundant fetches.
const firstInit = capabilities === null;
// Cache DOM elements
cacheDOM(); cacheDOM();
// Check capabilities if (firstInit) {
checkCapabilities(); checkCapabilities();
initScanModeTabs();
initNetworkFilters();
initSortControls();
initHeatmap();
}
// Initialize components
initScanModeTabs();
initNetworkFilters();
initSortControls();
initHeatmap();
scheduleRender({ table: true, stats: true, radar: true }); scheduleRender({ table: true, stats: true, radar: true });
// Check if already scanning
checkScanStatus(); checkScanStatus();
console.log('[WiFiMode] Initialized');
} }
// DOM element cache // DOM element cache
+15 -3
View File
@@ -2094,13 +2094,25 @@ ACARS: ${r.statistics.acarsMessages} messages`;
const existing = document.getElementById('aircraftDbBanner'); const existing = document.getElementById('aircraftDbBanner');
if (existing) existing.remove(); if (existing) existing.remove();
const _dbTier = document.documentElement.getAttribute('data-ui-tier') || 'enhanced';
const _dbBg = _dbTier === 'enhanced'
? (type === 'not_installed' ? 'rgba(4, 22, 26, 0.97)' : 'rgba(4, 22, 26, 0.97)')
: (type === 'not_installed' ? 'rgba(59, 130, 246, 0.95)' : 'rgba(34, 197, 94, 0.95)');
const _dbBorder = _dbTier === 'enhanced'
? (type === 'not_installed' ? '1px solid rgba(46, 125, 138, 0.45)' : '1px solid rgba(46, 125, 138, 0.45)')
: 'none';
const _dbBtnColor = _dbTier === 'enhanced'
? (type === 'not_installed' ? '#2e7d8a' : '#38c180')
: (type === 'not_installed' ? '#3b82f6' : '#22c55e');
const banner = document.createElement('div'); const banner = document.createElement('div');
banner.id = 'aircraftDbBanner'; banner.id = 'aircraftDbBanner';
banner.style.cssText = ` banner.style.cssText = `
position: fixed; position: fixed;
top: 70px; top: 70px;
right: 20px; right: 20px;
background: ${type === 'not_installed' ? 'rgba(59, 130, 246, 0.95)' : 'rgba(34, 197, 94, 0.95)'}; background: ${_dbBg};
border: ${_dbBorder};
color: white; color: white;
padding: 12px 16px; padding: 12px 16px;
border-radius: 8px; border-radius: 8px;
@@ -2115,14 +2127,14 @@ ACARS: ${r.statistics.acarsMessages} messages`;
banner.innerHTML = ` banner.innerHTML = `
<div style="font-weight: bold; margin-bottom: 6px;">Aircraft Database Not Installed</div> <div style="font-weight: bold; margin-bottom: 6px;">Aircraft Database Not Installed</div>
<div style="margin-bottom: 10px; font-size: 11px; opacity: 0.9;">Download to see aircraft types, registrations, and model info.</div> <div style="margin-bottom: 10px; font-size: 11px; opacity: 0.9;">Download to see aircraft types, registrations, and model info.</div>
<button onclick="downloadAircraftDb()" style="background: white; color: #3b82f6; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 11px;">Download Database</button> <button onclick="downloadAircraftDb()" style="background: rgba(0,0,0,0.35); color: ${_dbBtnColor}; border: 1px solid ${_dbBtnColor}; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 11px;">Download Database</button>
<button onclick="this.parentElement.remove()" style="position: absolute; top: 6px; right: 8px; background: none; border: none; color: white; cursor: pointer; font-size: 14px;">×</button> <button onclick="this.parentElement.remove()" style="position: absolute; top: 6px; right: 8px; background: none; border: none; color: white; cursor: pointer; font-size: 14px;">×</button>
`; `;
} else { } else {
banner.innerHTML = ` banner.innerHTML = `
<div style="font-weight: bold; margin-bottom: 6px;">Database Update Available</div> <div style="font-weight: bold; margin-bottom: 6px;">Database Update Available</div>
<div style="margin-bottom: 10px; font-size: 11px; opacity: 0.9;">New version: ${version || 'latest'}</div> <div style="margin-bottom: 10px; font-size: 11px; opacity: 0.9;">New version: ${version || 'latest'}</div>
<button onclick="downloadAircraftDb()" style="background: white; color: #22c55e; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 11px;">Update Now</button> <button onclick="downloadAircraftDb()" style="background: rgba(0,0,0,0.35); color: ${_dbBtnColor}; border: 1px solid ${_dbBtnColor}; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 11px;">Update Now</button>
<button onclick="this.parentElement.remove()" style="position: absolute; top: 6px; right: 8px; background: none; border: none; color: white; cursor: pointer; font-size: 14px;">×</button> <button onclick="this.parentElement.remove()" style="position: absolute; top: 6px; right: 8px; background: none; border: none; color: white; cursor: pointer; font-size: 14px;">×</button>
`; `;
} }
+3 -1
View File
@@ -1,14 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}"> <html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
<head> <head>
<script>(function(){var t=localStorage.getItem('intercept-ui-tier')||'enhanced';document.documentElement.setAttribute('data-ui-tier',t);})();</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADS-B History // INTERCEPT</title> <title>ADS-B History // INTERCEPT</title>
{% if offline_settings.fonts_source == 'local' %} {% if offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %} {% else %}
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/layout.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/core/layout.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}?v={{ version }}&r=maptheme17"> <link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}?v={{ version }}&r=maptheme17">
+3
View File
@@ -5,7 +5,10 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iNTERCEPT // Remote Agents</title> <title>iNTERCEPT // Remote Agents</title>
<script>(function(){var t=localStorage.getItem('intercept-ui-tier')||'enhanced';document.documentElement.setAttribute('data-ui-tier',t);})();</script>
<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/base.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/core/base.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
+14 -9
View File
@@ -42,7 +42,7 @@
{% if offline_settings.fonts_source == 'local' %} {% if offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %} {% else %}
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
{% endif %} {% endif %}
<!-- Leaflet CSS --> <!-- Leaflet CSS -->
{% if offline_settings.assets_source == 'local' %} {% if offline_settings.assets_source == 'local' %}
@@ -1478,7 +1478,7 @@
<div id="tscmVisuals" class="tscm-dashboard" style="display: none; padding: 16px;"> <div id="tscmVisuals" class="tscm-dashboard" style="display: none; padding: 16px;">
<!-- Legal Disclaimer Banner --> <!-- Legal Disclaimer Banner -->
<div class="tscm-legal-banner" <div class="tscm-legal-banner"
style="margin-bottom: 12px; padding: 8px 12px; background: rgba(74, 158, 255, 0.1); border: 1px solid rgba(74, 158, 255, 0.3); border-radius: 4px; font-size: 10px; color: #ffffff;"> style="margin-bottom: 12px; padding: 8px 12px; background: rgba(var(--accent-cyan-rgb), 0.1); border: 1px solid rgba(var(--accent-cyan-rgb), 0.3); border-radius: 4px; font-size: 10px; color: #ffffff;">
<strong>TSCM Screening Tool:</strong> This system identifies wireless and RF anomalies. <strong>TSCM Screening Tool:</strong> This system identifies wireless and RF anomalies.
Findings are indicators, NOT confirmed surveillance devices. Findings are indicators, NOT confirmed surveillance devices.
No content is intercepted or decoded. Professional verification required. No content is intercepted or decoded. Professional verification required.
@@ -2172,7 +2172,7 @@
<div style="display: grid; grid-template-columns: 3fr 1fr; gap: 12px; flex: 1; min-height: 0; overflow: hidden;"> <div style="display: grid; grid-template-columns: 3fr 1fr; gap: 12px; flex: 1; min-height: 0; overflow: hidden;">
<!-- Map --> <!-- Map -->
<div class="radio-module-box" style="padding: 0; overflow: hidden; position: relative; min-height: 0;"> <div class="radio-module-box" style="padding: 0; overflow: hidden; position: relative; min-height: 0;">
<div id="websdrMap" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div> <div id="websdrMap" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; overflow: hidden;"></div>
</div> </div>
<!-- Receiver List --> <!-- Receiver List -->
<div style="display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; overflow: hidden;"> <div style="display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; overflow: hidden;">
@@ -4639,15 +4639,17 @@
return; return;
} }
if (!validModes.has(mode)) mode = 'pager'; if (!validModes.has(mode)) mode = 'pager';
const _modeAssetTimeout = (p) =>
Promise.race([p, new Promise((r) => setTimeout(r, 5000))]);
const styleReadyPromise = (typeof window.ensureModeStyles === 'function') const styleReadyPromise = (typeof window.ensureModeStyles === 'function')
? Promise.resolve(window.ensureModeStyles(mode)).catch((err) => { ? _modeAssetTimeout(Promise.resolve(window.ensureModeStyles(mode)).catch((err) => {
console.warn(`[ModeSwitch] style load failed for ${mode}: ${err?.message || err}`); console.warn(`[ModeSwitch] style load failed for ${mode}: ${err?.message || err}`);
}) }))
: Promise.resolve(); : Promise.resolve();
const scriptReadyPromise = (typeof window.ensureModeScript === 'function') const scriptReadyPromise = (typeof window.ensureModeScript === 'function')
? Promise.resolve(window.ensureModeScript(mode)).catch((err) => { ? _modeAssetTimeout(Promise.resolve(window.ensureModeScript(mode)).catch((err) => {
console.warn(`[ModeSwitch] script load failed for ${mode}: ${err?.message || err}`); console.warn(`[ModeSwitch] script load failed for ${mode}: ${err?.message || err}`);
}) }))
: Promise.resolve(); : Promise.resolve();
// Only stop local scans if in local mode (not agent mode) // Only stop local scans if in local mode (not agent mode)
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local'; const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
@@ -4697,6 +4699,9 @@
if (isDroneRunning) { if (isDroneRunning) {
stopTasks.push(awaitStopAction('drone', () => fetch('/drone/stop', { method: 'POST' }), LOCAL_STOP_TIMEOUT_MS)); stopTasks.push(awaitStopAction('drone', () => fetch('/drone/stop', { method: 'POST' }), LOCAL_STOP_TIMEOUT_MS));
} }
if (isRtlamrRunning) {
stopTasks.push(awaitStopAction('rtlamr', () => stopRtlamrDecoding(), LOCAL_STOP_TIMEOUT_MS));
}
if (stopTasks.length) { if (stopTasks.length) {
await Promise.allSettled(stopTasks); await Promise.allSettled(stopTasks);
@@ -16616,7 +16621,7 @@
<!-- Cheat Sheet Modal --> <!-- Cheat Sheet Modal -->
<div id="cheatSheetModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:10000; align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this)CheatSheets.hide()"> <div id="cheatSheetModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:10000; align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this)CheatSheets.hide()">
<div style="background:var(--bg-card, #1a1f2e); border:1px solid rgba(255,255,255,0.15); border-radius:12px; max-width:480px; width:100%; max-height:80vh; overflow-y:auto; padding:20px; position:relative;"> <div style="background:var(--bg-card, #1a1f2e); border:1px solid var(--border-color); border-radius:12px; max-width:480px; width:100%; max-height:80vh; overflow-y:auto; padding:20px; position:relative;">
<button onclick="CheatSheets.hide()" style="position:absolute; top:12px; right:12px; background:none; border:none; color:var(--text-dim); cursor:pointer; font-size:18px; line-height:1;"></button> <button onclick="CheatSheets.hide()" style="position:absolute; top:12px; right:12px; background:none; border:none; color:var(--text-dim); cursor:pointer; font-size:18px; line-height:1;"></button>
<div id="cheatSheetContent"></div> <div id="cheatSheetContent"></div>
</div> </div>
@@ -16624,7 +16629,7 @@
<!-- Keyboard Shortcuts Modal --> <!-- Keyboard Shortcuts Modal -->
<div id="kbShortcutsModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:10000; align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this)KeyboardShortcuts.hideHelp()"> <div id="kbShortcutsModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:10000; align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this)KeyboardShortcuts.hideHelp()">
<div style="background:var(--bg-card, #1a1f2e); border:1px solid rgba(255,255,255,0.15); border-radius:12px; max-width:520px; width:100%; max-height:80vh; overflow-y:auto; padding:20px; position:relative;"> <div style="background:var(--bg-card, #1a1f2e); border:1px solid var(--border-color); border-radius:12px; max-width:520px; width:100%; max-height:80vh; overflow-y:auto; padding:20px; position:relative;">
<button onclick="KeyboardShortcuts.hideHelp()" style="position:absolute; top:12px; right:12px; background:none; border:none; color:var(--text-dim); cursor:pointer; font-size:18px; line-height:1;"></button> <button onclick="KeyboardShortcuts.hideHelp()" style="position:absolute; top:12px; right:12px; background:none; border:none; color:var(--text-dim); cursor:pointer; font-size:18px; line-height:1;"></button>
<h2 style="margin:0 0 16px; font-size:16px; color:var(--accent-cyan, #4aa3ff); font-family:var(--font-mono);">Keyboard Shortcuts</h2> <h2 style="margin:0 0 16px; font-size:16px; color:var(--accent-cyan, #4aa3ff); font-family:var(--font-mono);">Keyboard Shortcuts</h2>
<table style="width:100%; border-collapse:collapse; font-family:var(--font-mono); font-size:12px;"> <table style="width:100%; border-collapse:collapse; font-family:var(--font-mono); font-size:12px;">
+1 -1
View File
@@ -10,7 +10,7 @@
{% if offline_settings and offline_settings.fonts_source == 'local' %} {% if offline_settings and offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %} {% else %}
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
{% endif %} {% endif %}
{# Core CSS (Design System) #} {# Core CSS (Design System) #}
+2
View File
@@ -12,6 +12,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iNTERCEPT // Restricted Access</title> <title>iNTERCEPT // Restricted Access</title>
<script src="{{ url_for('static', filename='js/core/login.js') }}"></script> <script src="{{ url_for('static', filename='js/core/login.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}" />
</head> </head>
+28 -9
View File
@@ -1,14 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}"> <html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
<head> <head>
<script>(function(){var t=localStorage.getItem('intercept-ui-tier')||'enhanced';document.documentElement.setAttribute('data-ui-tier',t);})();</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Network Monitor // INTERCEPT</title> <title>Network Monitor // INTERCEPT</title>
{% if offline_settings.fonts_source == 'local' %} {% if offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %} {% else %}
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Roboto+Condensed:wght@300;400;500;600;700&display=swap" rel="stylesheet">
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/agents.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/agents.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/layout.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/core/layout.css') }}">
@@ -18,8 +20,8 @@
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
:root { :root {
--font-sans: 'Roboto Condensed', 'Arial Narrow', Roboto, 'Helvetica Neue', Arial, sans-serif; --font-sans: 'Inter', 'Roboto Condensed', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'Roboto Condensed', 'Arial Narrow', Roboto, 'Helvetica Neue', Arial, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', Consolas, monospace;
--bg-primary: #0a0c10; --bg-primary: #0a0c10;
--bg-secondary: #0f1218; --bg-secondary: #0f1218;
--bg-tertiary: #151a23; --bg-tertiary: #151a23;
@@ -28,12 +30,29 @@
--text-dim: #4b5563; --text-dim: #4b5563;
--border-color: #1f2937; --border-color: #1f2937;
--accent-cyan: #4a9eff; --accent-cyan: #4a9eff;
--accent-cyan-rgb: 74, 158, 255;
--accent-green: #22c55e; --accent-green: #22c55e;
--accent-red: #ef4444; --accent-red: #ef4444;
--accent-orange: #f59e0b; --accent-orange: #f59e0b;
--accent-purple: #a855f7; --accent-purple: #a855f7;
} }
html[data-ui-tier="enhanced"] {
--bg-primary: #000000;
--bg-secondary: #020404;
--bg-tertiary: #040808;
--border-color: rgba(46, 125, 138, 0.18);
--accent-cyan: #2e7d8a;
--accent-cyan-rgb: 46, 125, 138;
}
html[data-ui-tier="lean"] {
--bg-primary: #111111;
--bg-secondary: #181818;
--bg-tertiary: #1f1f1f;
--border-color: #2a2a2a;
}
body { body {
font-family: var(--font-sans); font-family: var(--font-sans);
background: var(--bg-primary); background: var(--bg-primary);
@@ -161,7 +180,7 @@
.panel-count { .panel-count {
font-size: 10px; font-size: 10px;
padding: 2px 8px; padding: 2px 8px;
background: rgba(74, 158, 255, 0.2); background: rgba(var(--accent-cyan-rgb), 0.2);
color: var(--accent-cyan); color: var(--accent-cyan);
border-radius: 10px; border-radius: 10px;
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -192,7 +211,7 @@
} }
.panel-tab.active { .panel-tab.active {
background: rgba(74, 158, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
color: var(--accent-cyan); color: var(--accent-cyan);
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
} }
@@ -230,7 +249,7 @@
} }
.data-table tr:hover { .data-table tr:hover {
background: rgba(74, 158, 255, 0.05); background: rgba(var(--accent-cyan-rgb), 0.05);
} }
.mono { .mono {
@@ -249,7 +268,7 @@
gap: 4px; gap: 4px;
padding: 2px 6px; padding: 2px 6px;
font-size: 9px; font-size: 9px;
background: rgba(74, 158, 255, 0.15); background: rgba(var(--accent-cyan-rgb), 0.15);
color: var(--accent-cyan); color: var(--accent-cyan);
border-radius: 8px; border-radius: 8px;
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -439,7 +458,7 @@
gap: 6px; gap: 6px;
margin-top: 4px; margin-top: 4px;
padding: 3px 8px; padding: 3px 8px;
background: rgba(74, 158, 255, 0.1); background: rgba(var(--accent-cyan-rgb), 0.1);
border-radius: 4px; border-radius: 4px;
font-size: 10px; font-size: 10px;
} }
@@ -497,7 +516,7 @@
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
.type-badge.type-wifi { background: rgba(74, 158, 255, 0.2); color: var(--accent-cyan); } .type-badge.type-wifi { background: rgba(var(--accent-cyan-rgb), 0.2); color: var(--accent-cyan); }
.type-badge.type-bluetooth { background: rgba(168, 85, 247, 0.2); color: var(--accent-purple); } .type-badge.type-bluetooth { background: rgba(168, 85, 247, 0.2); color: var(--accent-purple); }
.type-badge.type-adsb { background: rgba(34, 197, 94, 0.2); color: var(--accent-green); } .type-badge.type-adsb { background: rgba(34, 197, 94, 0.2); color: var(--accent-green); }
.type-badge.type-ais { background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); } .type-badge.type-ais { background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); }
+10 -8
View File
@@ -563,7 +563,7 @@
} }
.location-select:focus { .location-select:focus {
outline: none; outline: none;
border-color: #00d4ff; border-color: var(--accent-cyan);
} }
.location-status-dot { .location-status-dot {
width: 8px; width: 8px;
@@ -823,14 +823,16 @@
] ]
}; };
const _satAccent = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00ffff';
let satellites = { let satellites = {
25544: { name: 'ISS (ZARYA)', color: '#00ffff' }, 25544: { name: 'ISS (ZARYA)', color: _satAccent },
40069: { name: 'METEOR-M2', color: '#9370DB' }, 40069: { name: 'METEOR-M2', color: '#9370DB' },
57166: { name: 'METEOR-M2-3', color: '#ff00ff' }, 57166: { name: 'METEOR-M2-3', color: '#ff00ff' },
59051: { name: 'METEOR-M2-4', color: '#00ff88' } 59051: { name: 'METEOR-M2-4', color: '#00ff88' }
}; };
const satColors = ['#00ffff', '#9370DB', '#ff00ff', '#00ff00', '#ff6600', '#ffff00', '#ff69b4', '#7b68ee']; const satColors = [_satAccent, '#9370DB', '#ff00ff', '#00ff00', '#ff6600', '#ffff00', '#ff69b4', '#7b68ee'];
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = SAT_DRAWER_FETCH_TIMEOUT_MS) { async function fetchJsonWithTimeout(url, options = {}, timeoutMs = SAT_DRAWER_FETCH_TIMEOUT_MS) {
const controller = new AbortController(); const controller = new AbortController();
@@ -1102,7 +1104,7 @@
drawPolarPlotWithPosition( drawPolarPlotWithPosition(
normalized.azimuth ?? normalized.az, normalized.azimuth ?? normalized.az,
normalized.elevation ?? normalized.el, normalized.elevation ?? normalized.el,
satellites[selectedSatellite]?.color || '#00d4ff' satellites[selectedSatellite]?.color || _satAccent
); );
} }
renderMapTrackOverlays({ refreshPass: false, refreshLive: true }); renderMapTrackOverlays({ refreshPass: false, refreshLive: true });
@@ -1547,7 +1549,7 @@
const track = Array.isArray(pass?.groundTrack) ? pass.groundTrack : []; const track = Array.isArray(pass?.groundTrack) ? pass.groundTrack : [];
if (!track.length) return null; if (!track.length) return null;
const color = pass.color || satellites[selectedSatellite]?.color || '#00d4ff'; const color = pass.color || satellites[selectedSatellite]?.color || _satAccent;
const layer = L.layerGroup(); const layer = L.layerGroup();
const segments = splitAtAntimeridian(track); const segments = splitAtAntimeridian(track);
const bounds = []; const bounds = [];
@@ -1687,7 +1689,7 @@
bounds.push(...liveTrack.map(p => [p.lat, p.lon])); bounds.push(...liveTrack.map(p => [p.lat, p.lon]));
} }
const satColor = satellites[selectedSatellite]?.color || '#00d4ff'; const satColor = satellites[selectedSatellite]?.color || _satAccent;
const currentPos = latestLivePosition?.lat != null && latestLivePosition?.lon != null const currentPos = latestLivePosition?.lat != null && latestLivePosition?.lon != null
? { lat: latestLivePosition.lat, lon: latestLivePosition.lon } ? { lat: latestLivePosition.lat, lon: latestLivePosition.lon }
: (pass?.currentPos?.lat != null && pass?.currentPos?.lon != null : (pass?.currentPos?.lat != null && pass?.currentPos?.lon != null
@@ -2328,7 +2330,7 @@
// Pass trajectory // Pass trajectory
if (pass && pass.trajectory) { if (pass && pass.trajectory) {
ctx.strokeStyle = pass.color || '#00d4ff'; ctx.strokeStyle = pass.color || _satAccent;
ctx.lineWidth = 3; ctx.lineWidth = 3;
ctx.setLineDash([8, 4]); ctx.setLineDash([8, 4]);
ctx.beginPath(); ctx.beginPath();
@@ -2356,7 +2358,7 @@
if (maxElPoint) { if (maxElPoint) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(maxElPoint.x, maxElPoint.y, 8, 0, Math.PI * 2); ctx.arc(maxElPoint.x, maxElPoint.y, 8, 0, Math.PI * 2);
ctx.fillStyle = pass.color || '#00d4ff'; ctx.fillStyle = pass.color || _satAccent;
ctx.fill(); ctx.fill();
ctx.strokeStyle = '#fff'; ctx.strokeStyle = '#fff';
ctx.lineWidth = 2; ctx.lineWidth = 2;