diff --git a/docs/UI_GUIDE.md b/docs/UI_GUIDE.md
new file mode 100644
index 0000000..cbd2ff2
--- /dev/null
+++ b/docs/UI_GUIDE.md
@@ -0,0 +1,608 @@
+# iNTERCEPT UI Guide
+
+This guide documents the UI design system, components, and patterns used in iNTERCEPT.
+
+## Table of Contents
+
+1. [Design Tokens](#design-tokens)
+2. [Base Templates](#base-templates)
+3. [Navigation](#navigation)
+4. [Components](#components)
+5. [Adding a New Module Page](#adding-a-new-module-page)
+6. [Adding a New Dashboard](#adding-a-new-dashboard)
+
+---
+
+## Design Tokens
+
+All design tokens are defined in `static/css/core/variables.css`. Import this file first in any stylesheet.
+
+### Colors
+
+```css
+/* Backgrounds (layered depth) */
+--bg-primary: #0a0c10; /* Darkest - page background */
+--bg-secondary: #0f1218; /* Panels, sidebars */
+--bg-tertiary: #151a23; /* Cards, elevated elements */
+--bg-card: #121620; /* Card backgrounds */
+--bg-elevated: #1a202c; /* Hover states, modals */
+
+/* Accent Colors */
+--accent-cyan: #4a9eff; /* Primary action color */
+--accent-green: #22c55e; /* Success, online status */
+--accent-red: #ef4444; /* Error, danger, stop */
+--accent-orange: #f59e0b; /* Warning */
+--accent-amber: #d4a853; /* Secondary highlight */
+
+/* Text Hierarchy */
+--text-primary: #e8eaed; /* Main content */
+--text-secondary: #9ca3af; /* Secondary content */
+--text-dim: #4b5563; /* Disabled, placeholder */
+--text-muted: #374151; /* Barely visible */
+
+/* Status Colors */
+--status-online: #22c55e;
+--status-warning: #f59e0b;
+--status-error: #ef4444;
+--status-offline: #6b7280;
+```
+
+### Spacing Scale
+
+```css
+--space-1: 4px;
+--space-2: 8px;
+--space-3: 12px;
+--space-4: 16px;
+--space-5: 20px;
+--space-6: 24px;
+--space-8: 32px;
+--space-10: 40px;
+--space-12: 48px;
+--space-16: 64px;
+```
+
+### Typography
+
+```css
+/* Font Families */
+--font-sans: 'Inter', -apple-system, sans-serif;
+--font-mono: 'JetBrains Mono', monospace;
+
+/* Font Sizes */
+--text-xs: 10px;
+--text-sm: 12px;
+--text-base: 14px;
+--text-lg: 16px;
+--text-xl: 18px;
+--text-2xl: 20px;
+--text-3xl: 24px;
+--text-4xl: 30px;
+```
+
+### Border Radius
+
+```css
+--radius-sm: 4px;
+--radius-md: 6px;
+--radius-lg: 8px;
+--radius-xl: 12px;
+--radius-full: 9999px;
+```
+
+### Light Theme
+
+The design system supports light/dark themes via `data-theme` attribute:
+
+```html
+
+```
+
+Toggle with JavaScript:
+```javascript
+document.documentElement.setAttribute('data-theme', 'light');
+```
+
+---
+
+## Base Templates
+
+### `templates/layout/base.html`
+
+The main base template for standard pages. Use for pages with sidebar + content layout.
+
+```html
+{% extends 'layout/base.html' %}
+
+{% block title %}My Page Title{% endblock %}
+
+{% block styles %}
+
+{% endblock %}
+
+{% block navigation %}
+{% set active_mode = 'mymode' %}
+{% include 'partials/nav.html' %}
+{% endblock %}
+
+{% block sidebar %}
+
+{% endblock %}
+
+{% block content %}
+
+
Page Title
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
+```
+
+### `templates/layout/base_dashboard.html`
+
+Extended base for full-screen dashboards (maps, visualizations).
+
+```html
+{% extends 'layout/base_dashboard.html' %}
+
+{% set active_mode = 'mydashboard' %}
+
+{% block dashboard_title %}MY DASHBOARD{% endblock %}
+
+{% block styles %}
+{{ super() }}
+
+{% endblock %}
+
+{% block stats_strip %}
+
+
+
+{% endblock %}
+
+{% block dashboard_content %}
+
+
+
+
+{% endblock %}
+```
+
+---
+
+## Navigation
+
+### Including Navigation
+
+```html
+{% set active_mode = 'pager' %}
+{% include 'partials/nav.html' %}
+```
+
+### Valid `active_mode` Values
+
+| Mode | Description |
+|------|-------------|
+| `pager` | Pager decoding |
+| `sensor` | 433MHz sensors |
+| `rtlamr` | Utility meters |
+| `adsb` | Aircraft tracking |
+| `ais` | Vessel tracking |
+| `aprs` | Amateur radio |
+| `wifi` | WiFi scanning |
+| `bluetooth` | Bluetooth scanning |
+| `tscm` | Counter-surveillance |
+| `satellite` | Satellite tracking |
+| `sstv` | ISS SSTV |
+| `listening` | Listening post |
+| `spystations` | Spy stations |
+| `meshtastic` | Mesh networking |
+
+### Navigation Groups
+
+The navigation is organized into groups:
+- **SDR / RF**: Pager, 433MHz, Meters, Aircraft, Vessels, APRS, Listening Post, Spy Stations, Meshtastic
+- **Wireless**: WiFi, Bluetooth
+- **Security**: TSCM
+- **Space**: Satellite, ISS SSTV
+
+---
+
+## Components
+
+### Card / Panel
+
+```html
+{% call card(title='PANEL TITLE', indicator=true, indicator_active=false) %}
+Panel content here
+{% endcall %}
+```
+
+Or manually:
+```html
+
+```
+
+### Empty State
+
+```html
+{% include 'components/empty_state.html' with context %}
+{# Or with variables: #}
+{% with title='No data yet', description='Start scanning to see results', action_text='Start Scan', action_onclick='startScan()' %}
+{% include 'components/empty_state.html' %}
+{% endwith %}
+```
+
+### Loading State
+
+```html
+{# Inline spinner #}
+{% include 'components/loading.html' %}
+
+{# With text #}
+{% with text='Loading data...', size='lg' %}
+{% include 'components/loading.html' %}
+{% endwith %}
+
+{# Full overlay #}
+{% with overlay=true, text='Please wait...' %}
+{% include 'components/loading.html' %}
+{% endwith %}
+```
+
+### Status Badge
+
+```html
+{% with status='online', text='Connected', id='connectionStatus' %}
+{% include 'components/status_badge.html' %}
+{% endwith %}
+```
+
+Status values: `online`, `offline`, `warning`, `error`, `inactive`
+
+### Buttons
+
+```html
+
+Start Tracking
+
+
+Cancel
+
+
+Stop
+
+
+Settings
+
+
+Small
+Large
+
+
+
+ ...
+
+```
+
+### Badges
+
+```html
+Default
+Primary
+Online
+Warning
+Error
+```
+
+### Form Groups
+
+```html
+
+ Frequency (MHz)
+
+ Enter frequency in MHz
+
+
+
+ Gain
+
+ Auto
+ 30 dB
+
+
+
+
+
+ Enable alerts
+
+```
+
+### Stats Strip
+
+Used in dashboards for horizontal statistics display:
+
+```html
+
+
+
+ 0
+ COUNT
+
+
+
+
--:--:-- UTC
+
+
+```
+
+---
+
+## Adding a New Module Page
+
+### 1. Create the Route
+
+In `routes/mymodule.py`:
+
+```python
+from flask import Blueprint, render_template
+
+mymodule_bp = Blueprint('mymodule', __name__, url_prefix='/mymodule')
+
+@mymodule_bp.route('/dashboard')
+def dashboard():
+ return render_template('mymodule_dashboard.html',
+ offline_settings=get_offline_settings())
+```
+
+### 2. Register the Blueprint
+
+In `routes/__init__.py`:
+
+```python
+from routes.mymodule import mymodule_bp
+app.register_blueprint(mymodule_bp)
+```
+
+### 3. Create the Template
+
+Option A: Simple page extending base.html
+```html
+{% extends 'layout/base.html' %}
+{% set active_mode = 'mymodule' %}
+
+{% block title %}My Module{% endblock %}
+
+{% block navigation %}
+{% include 'partials/nav.html' %}
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+```
+
+Option B: Full-screen dashboard
+```html
+{% extends 'layout/base_dashboard.html' %}
+{% set active_mode = 'mymodule' %}
+
+{% block dashboard_title %}MY MODULE{% endblock %}
+
+{% block dashboard_content %}
+
+{% endblock %}
+```
+
+### 4. Add to Navigation
+
+In `templates/partials/nav.html`, add your module to the appropriate group:
+
+```html
+
+
+ My Module
+
+```
+
+Or if it's a dashboard link:
+```html
+
+
+ My Module
+
+```
+
+### 5. Create Stylesheet
+
+In `static/css/mymodule.css`:
+
+```css
+/**
+ * My Module Styles
+ */
+@import url('./core/variables.css');
+
+/* Your styles using design tokens */
+.mymodule-container {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ padding: var(--space-4);
+}
+```
+
+---
+
+## Adding a New Dashboard
+
+For full-screen dashboards like ADSB, AIS, or Satellite:
+
+### 1. Create the Template
+
+```html
+
+
+
+
+
+ MY DASHBOARD // iNTERCEPT
+
+
+
+
+
+
+ {% if offline_settings.fonts_source == 'local' %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% set active_mode = 'mydashboard' %}
+ {% include 'partials/nav.html' %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 2. Create the Stylesheet
+
+```css
+/**
+ * My Dashboard Styles
+ */
+@import url('./core/variables.css');
+
+:root {
+ /* Dashboard-specific aliases */
+ --bg-dark: var(--bg-primary);
+ --bg-panel: var(--bg-secondary);
+ --bg-card: var(--bg-tertiary);
+ --grid-line: rgba(74, 158, 255, 0.08);
+}
+
+/* Your dashboard styles */
+```
+
+---
+
+## Best Practices
+
+### DO
+
+- Use design tokens for all colors, spacing, and typography
+- Include the nav partial on all pages for consistent navigation
+- Set `active_mode` before including the nav partial
+- Use semantic component classes (`btn`, `panel`, `badge`, etc.)
+- Support both light and dark themes
+- Test on mobile viewports
+
+### DON'T
+
+- Hardcode color values - use CSS variables
+- Create new color variations without adding to tokens
+- Duplicate navigation markup - use the partial
+- Skip the favicon and design tokens imports
+- Use inline styles for layout (use utility classes)
+
+---
+
+## File Structure
+
+```
+templates/
+├── layout/
+│ ├── base.html # Standard page base
+│ └── base_dashboard.html # Dashboard page base
+├── partials/
+│ ├── nav.html # Unified navigation
+│ ├── page_header.html # Page title component
+│ └── settings-modal.html # Settings modal
+├── components/
+│ ├── card.html # Panel/card component
+│ ├── empty_state.html # Empty state placeholder
+│ ├── loading.html # Loading spinner
+│ ├── stats_strip.html # Stats bar component
+│ └── status_badge.html # Status indicator
+├── index.html # Main dashboard
+├── adsb_dashboard.html # Aircraft tracking
+├── ais_dashboard.html # Vessel tracking
+└── satellite_dashboard.html # Satellite tracking
+
+static/css/
+├── core/
+│ ├── variables.css # Design tokens
+│ ├── base.css # Reset & typography
+│ ├── components.css # Component styles
+│ └── layout.css # Layout styles
+├── index.css # Main dashboard styles
+├── adsb_dashboard.css # Aircraft dashboard
+├── ais_dashboard.css # Vessel dashboard
+├── satellite_dashboard.css # Satellite dashboard
+└── responsive.css # Responsive breakpoints
+```
diff --git a/static/css/components/function-strip.css b/static/css/components/function-strip.css
new file mode 100644
index 0000000..492be77
--- /dev/null
+++ b/static/css/components/function-strip.css
@@ -0,0 +1,371 @@
+/* Function Strip (Action Bar) - Shared across modes
+ * Based on APRS strip pattern, reusable for Pager, Sensor, Bluetooth, WiFi, TSCM, etc.
+ */
+
+.function-strip {
+ background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ padding: 8px 12px;
+ margin-bottom: 10px;
+ overflow: visible;
+ min-height: 44px;
+}
+
+.function-strip-inner {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: max-content;
+}
+
+/* Stats */
+.function-strip .strip-stat {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 6px 10px;
+ background: rgba(74, 158, 255, 0.05);
+ border: 1px solid rgba(74, 158, 255, 0.15);
+ border-radius: 4px;
+ min-width: 55px;
+}
+
+.function-strip .strip-stat:hover {
+ background: rgba(74, 158, 255, 0.1);
+ border-color: rgba(74, 158, 255, 0.3);
+}
+
+.function-strip .strip-value {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--accent-cyan);
+ line-height: 1.2;
+}
+
+.function-strip .strip-label {
+ font-size: 8px;
+ font-weight: 600;
+ color: var(--text-dim);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-top: 1px;
+}
+
+.function-strip .strip-divider {
+ width: 1px;
+ height: 28px;
+ background: var(--border-color);
+ margin: 0 4px;
+}
+
+/* Signal stat coloring */
+.function-strip .signal-stat.good .strip-value { color: var(--accent-green); }
+.function-strip .signal-stat.warning .strip-value { color: var(--accent-yellow); }
+.function-strip .signal-stat.poor .strip-value { color: var(--accent-red); }
+
+/* Controls */
+.function-strip .strip-control {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.function-strip .strip-select {
+ background: rgba(0,0,0,0.3);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 10px;
+ cursor: pointer;
+}
+
+.function-strip .strip-select:hover {
+ border-color: var(--accent-cyan);
+}
+
+.function-strip .strip-select:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.function-strip .strip-input-label {
+ font-size: 9px;
+ color: var(--text-muted);
+ font-weight: 600;
+}
+
+.function-strip .strip-input {
+ background: rgba(0,0,0,0.3);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 4px 6px;
+ border-radius: 4px;
+ font-size: 10px;
+ width: 50px;
+ text-align: center;
+}
+
+.function-strip .strip-input:hover,
+.function-strip .strip-input:focus {
+ border-color: var(--accent-cyan);
+ outline: none;
+}
+
+.function-strip .strip-input:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Wider input for frequency values */
+.function-strip .strip-input.wide {
+ width: 70px;
+}
+
+/* Tool Status Indicators */
+.function-strip .strip-tools {
+ display: flex;
+ gap: 4px;
+}
+
+.function-strip .strip-tool {
+ font-size: 9px;
+ font-weight: 600;
+ padding: 3px 6px;
+ border-radius: 3px;
+ background: rgba(255, 59, 48, 0.2);
+ color: var(--accent-red);
+ border: 1px solid rgba(255, 59, 48, 0.3);
+}
+
+.function-strip .strip-tool.ok {
+ background: rgba(0, 255, 136, 0.1);
+ color: var(--accent-green);
+ border-color: rgba(0, 255, 136, 0.3);
+}
+
+.function-strip .strip-tool.warn {
+ background: rgba(255, 193, 7, 0.2);
+ color: var(--accent-yellow);
+ border-color: rgba(255, 193, 7, 0.3);
+}
+
+/* Buttons */
+.function-strip .strip-btn {
+ background: rgba(74, 158, 255, 0.1);
+ border: 1px solid rgba(74, 158, 255, 0.2);
+ color: var(--text-primary);
+ padding: 6px 12px;
+ border-radius: 4px;
+ font-size: 10px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ white-space: nowrap;
+}
+
+.function-strip .strip-btn:hover:not(:disabled) {
+ background: rgba(74, 158, 255, 0.2);
+ border-color: rgba(74, 158, 255, 0.4);
+}
+
+.function-strip .strip-btn.primary {
+ background: linear-gradient(135deg, var(--accent-green) 0%, #10b981 100%);
+ border: none;
+ color: #000;
+}
+
+.function-strip .strip-btn.primary:hover:not(:disabled) {
+ filter: brightness(1.1);
+}
+
+.function-strip .strip-btn.stop {
+ background: linear-gradient(135deg, var(--accent-red) 0%, #dc2626 100%);
+ border: none;
+ color: #fff;
+}
+
+.function-strip .strip-btn.stop:hover:not(:disabled) {
+ filter: brightness(1.1);
+}
+
+.function-strip .strip-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Status indicator */
+.function-strip .strip-status {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 8px;
+ background: rgba(0,0,0,0.2);
+ border-radius: 4px;
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--text-secondary);
+}
+
+.function-strip .status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--text-muted);
+}
+
+.function-strip .status-dot.inactive {
+ background: var(--text-muted);
+}
+
+.function-strip .status-dot.active,
+.function-strip .status-dot.scanning,
+.function-strip .status-dot.decoding {
+ background: var(--accent-cyan);
+ animation: strip-pulse 1.5s ease-in-out infinite;
+}
+
+.function-strip .status-dot.listening,
+.function-strip .status-dot.tracking,
+.function-strip .status-dot.receiving {
+ background: var(--accent-green);
+ animation: strip-pulse 1.5s ease-in-out infinite;
+}
+
+.function-strip .status-dot.sweeping {
+ background: var(--accent-orange);
+ animation: strip-pulse 1s ease-in-out infinite;
+}
+
+.function-strip .status-dot.error {
+ background: var(--accent-red);
+}
+
+@keyframes strip-pulse {
+ 0%, 100% { opacity: 1; box-shadow: 0 0 4px 2px currentColor; }
+ 50% { opacity: 0.6; box-shadow: none; }
+}
+
+/* Time display */
+.function-strip .strip-time {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ color: var(--text-muted);
+ padding: 4px 8px;
+ background: rgba(0,0,0,0.2);
+ border-radius: 4px;
+ white-space: nowrap;
+}
+
+/* Mode-specific accent colors */
+.function-strip.pager-strip .strip-stat {
+ background: rgba(255, 193, 7, 0.05);
+ border-color: rgba(255, 193, 7, 0.15);
+}
+.function-strip.pager-strip .strip-stat:hover {
+ background: rgba(255, 193, 7, 0.1);
+ border-color: rgba(255, 193, 7, 0.3);
+}
+.function-strip.pager-strip .strip-value {
+ color: var(--accent-yellow);
+}
+
+.function-strip.sensor-strip .strip-stat {
+ background: rgba(0, 255, 136, 0.05);
+ border-color: rgba(0, 255, 136, 0.15);
+}
+.function-strip.sensor-strip .strip-stat:hover {
+ background: rgba(0, 255, 136, 0.1);
+ border-color: rgba(0, 255, 136, 0.3);
+}
+.function-strip.sensor-strip .strip-value {
+ color: var(--accent-green);
+}
+
+.function-strip.bt-strip .strip-stat {
+ background: rgba(0, 122, 255, 0.05);
+ border-color: rgba(0, 122, 255, 0.15);
+}
+.function-strip.bt-strip .strip-stat:hover {
+ background: rgba(0, 122, 255, 0.1);
+ border-color: rgba(0, 122, 255, 0.3);
+}
+.function-strip.bt-strip .strip-value {
+ color: #0a84ff;
+}
+
+.function-strip.wifi-strip .strip-stat {
+ background: rgba(255, 149, 0, 0.05);
+ border-color: rgba(255, 149, 0, 0.15);
+}
+.function-strip.wifi-strip .strip-stat:hover {
+ background: rgba(255, 149, 0, 0.1);
+ border-color: rgba(255, 149, 0, 0.3);
+}
+.function-strip.wifi-strip .strip-value {
+ color: var(--accent-orange);
+}
+
+.function-strip.tscm-strip {
+ margin-top: 4px; /* Extra clearance to prevent top clipping */
+}
+
+.function-strip.tscm-strip .strip-stat {
+ background: rgba(255, 59, 48, 0.15);
+ border: 1px solid rgba(255, 59, 48, 0.4);
+}
+.function-strip.tscm-strip .strip-stat:hover {
+ background: rgba(255, 59, 48, 0.25);
+ border-color: rgba(255, 59, 48, 0.6);
+}
+.function-strip.tscm-strip .strip-value {
+ color: #ef4444; /* Explicit red color */
+}
+.function-strip.tscm-strip .strip-label {
+ color: #9ca3af; /* Explicit light gray */
+}
+.function-strip.tscm-strip .strip-select {
+ color: #e8eaed; /* Explicit white for selects */
+ background: rgba(0, 0, 0, 0.4);
+}
+.function-strip.tscm-strip .strip-btn {
+ color: #e8eaed; /* Explicit white for buttons */
+}
+.function-strip.tscm-strip .strip-tool {
+ color: #e8eaed; /* Explicit white for tool indicators */
+}
+.function-strip.tscm-strip .strip-time,
+.function-strip.tscm-strip .strip-status span {
+ color: #9ca3af; /* Explicit gray for status/time */
+}
+
+.function-strip.rtlamr-strip .strip-stat {
+ background: rgba(175, 82, 222, 0.05);
+ border-color: rgba(175, 82, 222, 0.15);
+}
+.function-strip.rtlamr-strip .strip-stat:hover {
+ background: rgba(175, 82, 222, 0.1);
+ border-color: rgba(175, 82, 222, 0.3);
+}
+.function-strip.rtlamr-strip .strip-value {
+ color: #af52de;
+}
+
+.function-strip.listening-strip .strip-stat {
+ background: rgba(74, 158, 255, 0.05);
+ border-color: rgba(74, 158, 255, 0.15);
+}
+.function-strip.listening-strip .strip-stat:hover {
+ background: rgba(74, 158, 255, 0.1);
+ border-color: rgba(74, 158, 255, 0.3);
+}
+.function-strip.listening-strip .strip-value {
+ color: var(--accent-cyan);
+}
+
+/* Threat-colored stats for TSCM */
+.function-strip .strip-stat.threat-high .strip-value { color: var(--accent-red); }
+.function-strip .strip-stat.threat-review .strip-value { color: var(--accent-orange); }
+.function-strip .strip-stat.threat-info .strip-value { color: var(--accent-cyan); }
diff --git a/static/css/core/base.css b/static/css/core/base.css
new file mode 100644
index 0000000..b6f9a81
--- /dev/null
+++ b/static/css/core/base.css
@@ -0,0 +1,420 @@
+/**
+ * INTERCEPT Base Styles
+ * Reset, typography, and foundational element styles
+ * Requires: variables.css to be imported first
+ */
+
+/* ============================================
+ CSS RESET
+ ============================================ */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ -webkit-text-size-adjust: 100%;
+ -moz-tab-size: 4;
+ tab-size: 4;
+}
+
+body {
+ font-family: var(--font-sans);
+ font-size: var(--text-base);
+ line-height: var(--leading-normal);
+ color: var(--text-primary);
+ background: var(--bg-primary);
+ min-height: 100vh;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* ============================================
+ TYPOGRAPHY
+ ============================================ */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: var(--font-semibold);
+ line-height: var(--leading-tight);
+ color: var(--text-primary);
+}
+
+h1 { font-size: var(--text-4xl); }
+h2 { font-size: var(--text-3xl); }
+h3 { font-size: var(--text-2xl); }
+h4 { font-size: var(--text-xl); }
+h5 { font-size: var(--text-lg); }
+h6 { font-size: var(--text-base); }
+
+p {
+ margin-bottom: var(--space-4);
+}
+
+a {
+ color: var(--accent-cyan);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+a:hover {
+ color: var(--accent-cyan-hover);
+}
+
+a:focus-visible {
+ outline: 2px solid var(--border-focus);
+ outline-offset: 2px;
+}
+
+strong, b {
+ font-weight: var(--font-semibold);
+}
+
+small {
+ font-size: var(--text-sm);
+}
+
+code, kbd, pre, samp {
+ font-family: var(--font-mono);
+ font-size: 0.9em;
+}
+
+code {
+ background: var(--bg-tertiary);
+ padding: 2px 6px;
+ border-radius: var(--radius-sm);
+}
+
+pre {
+ background: var(--bg-tertiary);
+ padding: var(--space-4);
+ border-radius: var(--radius-md);
+ overflow-x: auto;
+}
+
+pre code {
+ background: none;
+ padding: 0;
+}
+
+/* ============================================
+ FORM ELEMENTS
+ ============================================ */
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+}
+
+button {
+ cursor: pointer;
+ border: none;
+ background: none;
+}
+
+button:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+input,
+select,
+textarea {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ padding: var(--space-2) var(--space-3);
+ color: var(--text-primary);
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
+}
+
+input:focus,
+select:focus,
+textarea:focus {
+ outline: none;
+ border-color: var(--accent-cyan);
+ box-shadow: 0 0 0 3px var(--accent-cyan-dim);
+}
+
+input::placeholder,
+textarea::placeholder {
+ color: var(--text-dim);
+}
+
+select {
+ cursor: pointer;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ padding-right: 28px;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ width: 16px;
+ height: 16px;
+ padding: 0;
+ cursor: pointer;
+ accent-color: var(--accent-cyan);
+}
+
+label {
+ display: block;
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ margin-bottom: var(--space-1);
+}
+
+/* ============================================
+ TABLES
+ ============================================ */
+table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: var(--text-sm);
+}
+
+th,
+td {
+ padding: var(--space-2) var(--space-3);
+ text-align: left;
+ border-bottom: 1px solid var(--border-color);
+}
+
+th {
+ font-weight: var(--font-semibold);
+ color: var(--text-secondary);
+ background: var(--bg-secondary);
+ text-transform: uppercase;
+ font-size: var(--text-xs);
+ letter-spacing: 0.05em;
+}
+
+tr:hover td {
+ background: var(--bg-tertiary);
+}
+
+/* ============================================
+ LISTS
+ ============================================ */
+ul, ol {
+ padding-left: var(--space-6);
+ margin-bottom: var(--space-4);
+}
+
+li {
+ margin-bottom: var(--space-1);
+}
+
+/* ============================================
+ UTILITY CLASSES
+ ============================================ */
+
+/* Text colors */
+.text-primary { color: var(--text-primary); }
+.text-secondary { color: var(--text-secondary); }
+.text-muted { color: var(--text-muted); }
+.text-cyan { color: var(--accent-cyan); }
+.text-green { color: var(--accent-green); }
+.text-red { color: var(--accent-red); }
+.text-orange { color: var(--accent-orange); }
+.text-amber { color: var(--accent-amber); }
+
+/* Font utilities */
+.font-mono { font-family: var(--font-mono); }
+.font-medium { font-weight: var(--font-medium); }
+.font-semibold { font-weight: var(--font-semibold); }
+.font-bold { font-weight: var(--font-bold); }
+
+/* Text sizes */
+.text-xs { font-size: var(--text-xs); }
+.text-sm { font-size: var(--text-sm); }
+.text-base { font-size: var(--text-base); }
+.text-lg { font-size: var(--text-lg); }
+.text-xl { font-size: var(--text-xl); }
+
+/* Display */
+.hidden { display: none !important; }
+.block { display: block; }
+.inline-block { display: inline-block; }
+.flex { display: flex; }
+.inline-flex { display: inline-flex; }
+.grid { display: grid; }
+
+/* Flexbox */
+.items-center { align-items: center; }
+.justify-center { justify-content: center; }
+.justify-between { justify-content: space-between; }
+.flex-1 { flex: 1; }
+.gap-1 { gap: var(--space-1); }
+.gap-2 { gap: var(--space-2); }
+.gap-3 { gap: var(--space-3); }
+.gap-4 { gap: var(--space-4); }
+
+/* Spacing */
+.m-0 { margin: 0; }
+.mt-2 { margin-top: var(--space-2); }
+.mt-4 { margin-top: var(--space-4); }
+.mb-2 { margin-bottom: var(--space-2); }
+.mb-4 { margin-bottom: var(--space-4); }
+.p-2 { padding: var(--space-2); }
+.p-3 { padding: var(--space-3); }
+.p-4 { padding: var(--space-4); }
+
+/* Borders */
+.rounded { border-radius: var(--radius-md); }
+.rounded-lg { border-radius: var(--radius-lg); }
+.border { border: 1px solid var(--border-color); }
+
+/* Truncate text */
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Screen reader only */
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* ============================================
+ SCROLLBAR STYLING
+ ============================================ */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg-secondary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border-light);
+ border-radius: var(--radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--text-dim);
+}
+
+/* Firefox scrollbar */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--border-light) var(--bg-secondary);
+}
+
+/* ============================================
+ SELECTION
+ ============================================ */
+::selection {
+ background: var(--accent-cyan-dim);
+ color: var(--text-primary);
+}
+
+/* ============================================
+ UX POLISH - TRANSITIONS & INTERACTIONS
+ ============================================ */
+
+/* Smooth page transitions */
+html {
+ scroll-behavior: smooth;
+}
+
+/* Better focus ring for all interactive elements */
+:focus-visible {
+ outline: 2px solid var(--accent-cyan);
+ outline-offset: 2px;
+}
+
+/* Remove focus ring for mouse users */
+:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* Active state feedback */
+button:active:not(:disabled),
+a:active,
+[role="button"]:active {
+ transform: scale(0.98);
+}
+
+/* Smooth transitions for all interactive elements */
+button,
+a,
+input,
+select,
+textarea,
+[role="button"] {
+ transition:
+ color var(--transition-fast),
+ background-color var(--transition-fast),
+ border-color var(--transition-fast),
+ box-shadow var(--transition-fast),
+ transform var(--transition-fast),
+ opacity var(--transition-fast);
+}
+
+/* Subtle hover lift effect for cards and panels */
+.card:hover,
+.panel:hover {
+ box-shadow: var(--shadow-md);
+}
+
+/* Link underline on hover */
+a:hover {
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+/* Skip link for accessibility */
+.skip-link {
+ position: absolute;
+ top: -40px;
+ left: 0;
+ background: var(--accent-cyan);
+ color: var(--bg-primary);
+ padding: var(--space-2) var(--space-4);
+ z-index: 9999;
+ transition: top var(--transition-fast);
+}
+
+.skip-link:focus {
+ top: 0;
+}
+
+/* Reduced motion preference */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+
+/* High contrast mode support */
+@media (prefers-contrast: high) {
+ :root {
+ --border-color: #4b5563;
+ --text-secondary: #d1d5db;
+ }
+}
diff --git a/static/css/core/components.css b/static/css/core/components.css
new file mode 100644
index 0000000..1d4f576
--- /dev/null
+++ b/static/css/core/components.css
@@ -0,0 +1,723 @@
+/**
+ * INTERCEPT UI Components
+ * Reusable component styles for buttons, cards, badges, etc.
+ * Requires: variables.css and base.css
+ */
+
+/* ============================================
+ BUTTONS
+ ============================================ */
+
+/* Base button */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-4);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ border-radius: var(--radius-md);
+ border: 1px solid transparent;
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ white-space: nowrap;
+ text-decoration: none;
+}
+
+.btn:focus-visible {
+ outline: 2px solid var(--border-focus);
+ outline-offset: 2px;
+}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Button variants */
+.btn-primary {
+ background: var(--accent-cyan);
+ color: var(--text-inverse);
+ border-color: var(--accent-cyan);
+}
+
+.btn-primary:hover:not(:disabled) {
+ background: var(--accent-cyan-hover);
+ border-color: var(--accent-cyan-hover);
+}
+
+.btn-secondary {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.btn-secondary:hover:not(:disabled) {
+ background: var(--bg-elevated);
+ border-color: var(--border-light);
+}
+
+.btn-ghost {
+ background: transparent;
+ color: var(--text-secondary);
+ border-color: transparent;
+}
+
+.btn-ghost:hover:not(:disabled) {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+}
+
+.btn-danger {
+ background: var(--accent-red);
+ color: white;
+ border-color: var(--accent-red);
+}
+
+.btn-danger:hover:not(:disabled) {
+ background: #dc2626;
+ border-color: #dc2626;
+}
+
+.btn-success {
+ background: var(--accent-green);
+ color: white;
+ border-color: var(--accent-green);
+}
+
+.btn-success:hover:not(:disabled) {
+ background: #16a34a;
+ border-color: #16a34a;
+}
+
+/* Button sizes */
+.btn-sm {
+ padding: var(--space-1) var(--space-2);
+ font-size: var(--text-xs);
+}
+
+.btn-lg {
+ padding: var(--space-3) var(--space-6);
+ font-size: var(--text-base);
+}
+
+/* Icon button */
+.btn-icon {
+ padding: var(--space-2);
+ width: 36px;
+ height: 36px;
+}
+
+.btn-icon.btn-sm {
+ width: 28px;
+ height: 28px;
+ padding: var(--space-1);
+}
+
+/* ============================================
+ CARDS / PANELS
+ ============================================ */
+.card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-3) var(--space-4);
+ border-bottom: 1px solid var(--border-color);
+ background: var(--bg-secondary);
+}
+
+.card-header-title {
+ font-size: var(--text-xs);
+ font-weight: var(--font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-secondary);
+}
+
+.card-body {
+ padding: var(--space-4);
+}
+
+.card-footer {
+ padding: var(--space-3) var(--space-4);
+ border-top: 1px solid var(--border-color);
+ background: var(--bg-secondary);
+}
+
+/* Panel variant (used in dashboards) */
+.panel {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+}
+
+.panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-2) var(--space-3);
+ border-bottom: 1px solid var(--border-color);
+ background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--bg-secondary) 100%);
+ font-size: var(--text-xs);
+ font-weight: var(--font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--text-secondary);
+}
+
+.panel-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: var(--radius-full);
+ background: var(--status-offline);
+}
+
+.panel-indicator.active {
+ background: var(--status-online);
+ box-shadow: 0 0 8px var(--status-online);
+}
+
+.panel-content {
+ padding: var(--space-3);
+}
+
+/* ============================================
+ BADGES
+ ============================================ */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-1);
+ padding: 2px var(--space-2);
+ font-size: var(--text-xs);
+ font-weight: var(--font-medium);
+ border-radius: var(--radius-full);
+ background: var(--bg-tertiary);
+ color: var(--text-secondary);
+}
+
+.badge-primary {
+ background: var(--accent-cyan-dim);
+ color: var(--accent-cyan);
+}
+
+.badge-success {
+ background: var(--accent-green-dim);
+ color: var(--accent-green);
+}
+
+.badge-warning {
+ background: var(--accent-orange-dim);
+ color: var(--accent-orange);
+}
+
+.badge-danger {
+ background: var(--accent-red-dim);
+ color: var(--accent-red);
+}
+
+/* ============================================
+ STATUS INDICATORS
+ ============================================ */
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: var(--radius-full);
+ background: var(--status-offline);
+ flex-shrink: 0;
+}
+
+.status-dot.online,
+.status-dot.active {
+ background: var(--status-online);
+ box-shadow: 0 0 6px var(--status-online);
+}
+
+.status-dot.warning {
+ background: var(--status-warning);
+ box-shadow: 0 0 6px var(--status-warning);
+}
+
+.status-dot.error,
+.status-dot.offline {
+ background: var(--status-error);
+}
+
+.status-dot.inactive {
+ background: var(--status-offline);
+}
+
+/* Pulse animation for active status */
+.status-dot.pulse {
+ animation: statusPulse 2s ease-in-out infinite;
+}
+
+@keyframes statusPulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+/* ============================================
+ EMPTY STATE
+ ============================================ */
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-8) var(--space-4);
+ text-align: center;
+ color: var(--text-muted);
+}
+
+.empty-state-icon {
+ width: 48px;
+ height: 48px;
+ margin-bottom: var(--space-4);
+ opacity: 0.5;
+}
+
+.empty-state-title {
+ font-size: var(--text-base);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ margin-bottom: var(--space-2);
+}
+
+.empty-state-description {
+ font-size: var(--text-sm);
+ color: var(--text-dim);
+ max-width: 300px;
+}
+
+.empty-state-action {
+ margin-top: var(--space-4);
+}
+
+/* ============================================
+ LOADING STATES
+ ============================================ */
+.spinner {
+ width: 20px;
+ height: 20px;
+ border: 2px solid var(--border-color);
+ border-top-color: var(--accent-cyan);
+ border-radius: var(--radius-full);
+ animation: spin 0.8s linear infinite;
+}
+
+.spinner-sm {
+ width: 14px;
+ height: 14px;
+ border-width: 2px;
+}
+
+.spinner-lg {
+ width: 32px;
+ height: 32px;
+ border-width: 3px;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* Loading overlay */
+.loading-overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-overlay);
+ z-index: var(--z-modal);
+}
+
+/* Skeleton loader */
+.skeleton {
+ background: linear-gradient(
+ 90deg,
+ var(--bg-tertiary) 25%,
+ var(--bg-elevated) 50%,
+ var(--bg-tertiary) 75%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+ border-radius: var(--radius-sm);
+}
+
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+/* ============================================
+ STATS STRIP
+ ============================================ */
+.stats-strip {
+ display: flex;
+ align-items: center;
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ padding: 0 var(--space-4);
+ height: var(--stats-strip-height);
+ overflow-x: auto;
+ gap: var(--space-1);
+}
+
+.strip-stat {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 0 var(--space-3);
+ min-width: fit-content;
+}
+
+.strip-value {
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ color: var(--accent-cyan);
+ line-height: 1;
+}
+
+.strip-label {
+ font-size: 9px;
+ color: var(--text-dim);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ line-height: 1;
+ margin-top: 2px;
+}
+
+.strip-divider {
+ width: 1px;
+ height: 20px;
+ background: var(--border-color);
+ margin: 0 var(--space-2);
+}
+
+/* ============================================
+ FORM GROUPS
+ ============================================ */
+.form-group {
+ margin-bottom: var(--space-4);
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: var(--space-1);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+}
+
+.form-group input,
+.form-group select,
+.form-group textarea {
+ width: 100%;
+}
+
+.form-help {
+ margin-top: var(--space-1);
+ font-size: var(--text-xs);
+ color: var(--text-dim);
+}
+
+.form-error {
+ margin-top: var(--space-1);
+ font-size: var(--text-xs);
+ color: var(--accent-red);
+}
+
+/* Inline checkbox/radio */
+.form-check {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ cursor: pointer;
+}
+
+.form-check input {
+ width: auto;
+}
+
+.form-check label {
+ margin-bottom: 0;
+ cursor: pointer;
+}
+
+/* ============================================
+ ALERTS / TOASTS
+ ============================================ */
+.alert {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--space-3);
+ padding: var(--space-3) var(--space-4);
+ border-radius: var(--radius-md);
+ border: 1px solid;
+ font-size: var(--text-sm);
+}
+
+.alert-info {
+ background: var(--accent-cyan-dim);
+ border-color: var(--accent-cyan);
+ color: var(--accent-cyan);
+}
+
+.alert-success {
+ background: var(--accent-green-dim);
+ border-color: var(--accent-green);
+ color: var(--accent-green);
+}
+
+.alert-warning {
+ background: var(--accent-orange-dim);
+ border-color: var(--accent-orange);
+ color: var(--accent-orange);
+}
+
+.alert-danger {
+ background: var(--accent-red-dim);
+ border-color: var(--accent-red);
+ color: var(--accent-red);
+}
+
+/* ============================================
+ TOOLTIPS
+ ============================================ */
+[data-tooltip] {
+ position: relative;
+}
+
+[data-tooltip]::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: var(--space-1) var(--space-2);
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+ font-size: var(--text-xs);
+ border-radius: var(--radius-sm);
+ white-space: nowrap;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity var(--transition-fast), visibility var(--transition-fast);
+ z-index: var(--z-tooltip);
+ pointer-events: none;
+ margin-bottom: var(--space-1);
+ box-shadow: var(--shadow-md);
+}
+
+[data-tooltip]:hover::after {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* ============================================
+ ICONS
+ ============================================ */
+.icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ flex-shrink: 0;
+}
+
+.icon svg {
+ width: 100%;
+ height: 100%;
+}
+
+.icon--sm {
+ width: 16px;
+ height: 16px;
+}
+
+.icon--lg {
+ width: 24px;
+ height: 24px;
+}
+
+/* ============================================
+ SECTION HEADERS
+ ============================================ */
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: var(--space-4);
+ padding-bottom: var(--space-2);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.section-title {
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-secondary);
+}
+
+/* ============================================
+ DIVIDERS
+ ============================================ */
+.divider {
+ height: 1px;
+ background: var(--border-color);
+ margin: var(--space-4) 0;
+}
+
+.divider-vertical {
+ width: 1px;
+ height: 100%;
+ background: var(--border-color);
+ margin: 0 var(--space-3);
+}
+
+/* ============================================
+ UX POLISH - ENHANCED INTERACTIONS
+ ============================================ */
+
+/* Button hover lift */
+.btn:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+.btn:active:not(:disabled) {
+ transform: translateY(0);
+ box-shadow: var(--shadow-sm);
+}
+
+/* Card/Panel hover effects */
+.card,
+.panel {
+ transition:
+ box-shadow var(--transition-base),
+ border-color var(--transition-base),
+ transform var(--transition-base);
+}
+
+.card:hover,
+.panel:hover {
+ border-color: var(--border-light);
+}
+
+/* Stats strip value highlight on hover */
+.strip-stat {
+ transition: background-color var(--transition-fast);
+ border-radius: var(--radius-sm);
+ cursor: default;
+}
+
+.strip-stat:hover {
+ background: var(--bg-tertiary);
+}
+
+/* Status dot pulse animation */
+.status-dot.online,
+.status-dot.active {
+ animation: statusGlow 2s ease-in-out infinite;
+}
+
+@keyframes statusGlow {
+ 0%, 100% {
+ box-shadow: 0 0 6px var(--status-online);
+ }
+ 50% {
+ box-shadow: 0 0 12px var(--status-online), 0 0 20px var(--status-online);
+ }
+}
+
+/* Badge hover effect */
+.badge {
+ transition: transform var(--transition-fast), box-shadow var(--transition-fast);
+}
+
+.badge:hover {
+ transform: scale(1.05);
+}
+
+/* Alert entrance animation */
+.alert {
+ animation: alertSlideIn 0.3s ease-out;
+}
+
+@keyframes alertSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Loading spinner smooth appearance */
+.spinner {
+ animation: spin 0.8s linear infinite, fadeIn 0.3s ease-out;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* Input focus glow */
+input:focus,
+select:focus,
+textarea:focus {
+ border-color: var(--accent-cyan);
+ box-shadow: 0 0 0 3px var(--accent-cyan-dim), 0 0 20px rgba(74, 158, 255, 0.1);
+}
+
+/* Nav item active indicator */
+.mode-nav-btn.active::after,
+.mobile-nav-btn.active::after {
+ content: '';
+ position: absolute;
+ bottom: -2px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 60%;
+ height: 2px;
+ background: currentColor;
+ border-radius: var(--radius-full);
+}
+
+/* Smooth tooltip appearance */
+[data-tooltip]::after {
+ transition:
+ opacity var(--transition-fast),
+ visibility var(--transition-fast),
+ transform var(--transition-fast);
+ transform: translateX(-50%) translateY(-4px);
+}
+
+[data-tooltip]:hover::after {
+ transform: translateX(-50%) translateY(0);
+}
+
+/* Disabled state with better visual feedback */
+:disabled,
+.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ filter: grayscale(30%);
+}
diff --git a/static/css/core/layout.css b/static/css/core/layout.css
new file mode 100644
index 0000000..e969223
--- /dev/null
+++ b/static/css/core/layout.css
@@ -0,0 +1,950 @@
+/**
+ * INTERCEPT Layout Styles
+ * Global layout structure: header, navigation, sidebar, main content
+ * Requires: variables.css, base.css, components.css
+ */
+
+/* ============================================
+ APP SHELL
+ ============================================ */
+.app-shell {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+.app-main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+/* ============================================
+ GLOBAL HEADER
+ ============================================ */
+.app-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: var(--header-height);
+ padding: 0 var(--space-4);
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ position: sticky;
+ top: 0;
+ z-index: var(--z-sticky);
+}
+
+.app-header-left {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4);
+}
+
+.app-header-right {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+}
+
+/* Logo */
+.app-logo {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ text-decoration: none;
+ color: var(--text-primary);
+}
+
+.app-logo-icon {
+ width: 40px;
+ height: 40px;
+ flex-shrink: 0;
+}
+
+.app-logo-text {
+ display: flex;
+ flex-direction: column;
+}
+
+.app-logo-title {
+ font-size: var(--text-lg);
+ font-weight: var(--font-bold);
+ line-height: 1.2;
+ color: var(--text-primary);
+}
+
+.app-logo-tagline {
+ font-size: var(--text-xs);
+ color: var(--text-dim);
+}
+
+/* Page title in header */
+.app-header-title {
+ font-size: var(--text-lg);
+ font-weight: var(--font-semibold);
+ color: var(--text-primary);
+}
+
+.app-header-subtitle {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ margin-left: var(--space-2);
+}
+
+/* Header utilities */
+.header-utilities {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+}
+
+.header-clock {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.header-clock-label {
+ font-size: var(--text-xs);
+ color: var(--text-dim);
+}
+
+/* ============================================
+ GLOBAL NAVIGATION
+ ============================================ */
+.app-nav {
+ display: flex;
+ align-items: center;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+ padding: 0 var(--space-4);
+ height: var(--nav-height);
+ gap: var(--space-1);
+ overflow-x: auto;
+}
+
+.app-nav::-webkit-scrollbar {
+ height: 0;
+}
+
+/* Nav groups */
+.nav-group {
+ display: flex;
+ align-items: center;
+ position: relative;
+}
+
+/* Dropdown trigger */
+.nav-dropdown-trigger {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-3);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ white-space: nowrap;
+}
+
+.nav-dropdown-trigger:hover {
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+}
+
+.nav-dropdown-trigger.active {
+ background: var(--accent-cyan-dim);
+ color: var(--accent-cyan);
+}
+
+.nav-dropdown-arrow {
+ width: 12px;
+ height: 12px;
+ transition: transform var(--transition-fast);
+}
+
+.nav-group.open .nav-dropdown-arrow {
+ transform: rotate(180deg);
+}
+
+/* Dropdown menu */
+.nav-dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 180px;
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-lg);
+ padding: var(--space-1);
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-4px);
+ transition: all var(--transition-fast);
+ z-index: var(--z-dropdown);
+}
+
+.nav-group.open .nav-dropdown-menu {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(4px);
+}
+
+/* Nav items */
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-3);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ text-decoration: none;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+}
+
+.nav-item:hover {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+}
+
+.nav-item.active {
+ background: var(--accent-cyan-dim);
+ color: var(--accent-cyan);
+}
+
+.nav-item-icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+/* Nav divider */
+.nav-divider {
+ width: 1px;
+ height: 24px;
+ background: var(--border-color);
+ margin: 0 var(--space-2);
+}
+
+/* Nav utilities (right side) */
+.nav-utilities {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ margin-left: auto;
+ padding-left: var(--space-4);
+}
+
+.nav-tool-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ text-decoration: none;
+}
+
+.nav-tool-btn:hover {
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+}
+
+/* ============================================
+ MOBILE NAVIGATION
+ ============================================ */
+.mobile-nav {
+ display: none;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+ padding: var(--space-2) var(--space-3);
+ overflow-x: auto;
+ gap: var(--space-2);
+}
+
+.mobile-nav::-webkit-scrollbar {
+ height: 0;
+}
+
+.mobile-nav-btn {
+ display: flex;
+ align-items: center;
+ gap: var(--space-1);
+ padding: var(--space-2) var(--space-3);
+ font-size: var(--text-xs);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ white-space: nowrap;
+ text-decoration: none;
+ transition: all var(--transition-fast);
+}
+
+.mobile-nav-btn:hover,
+.mobile-nav-btn.active {
+ background: var(--accent-cyan-dim);
+ border-color: var(--accent-cyan);
+ color: var(--accent-cyan);
+}
+
+/* Hamburger button */
+.hamburger-btn {
+ display: none;
+ flex-direction: column;
+ justify-content: center;
+ gap: 4px;
+ width: 32px;
+ height: 32px;
+ padding: 6px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+}
+
+.hamburger-btn span {
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: var(--text-secondary);
+ border-radius: 1px;
+ transition: all var(--transition-fast);
+}
+
+.hamburger-btn.open span:nth-child(1) {
+ transform: rotate(45deg) translate(4px, 4px);
+}
+
+.hamburger-btn.open span:nth-child(2) {
+ opacity: 0;
+}
+
+.hamburger-btn.open span:nth-child(3) {
+ transform: rotate(-45deg) translate(4px, -4px);
+}
+
+/* ============================================
+ CONTENT LAYOUTS
+ ============================================ */
+
+/* Main content with optional sidebar */
+.content-wrapper {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+/* Sidebar */
+.app-sidebar {
+ width: var(--sidebar-width);
+ background: var(--bg-secondary);
+ border-right: 1px solid var(--border-color);
+ overflow-y: auto;
+ flex-shrink: 0;
+}
+
+.sidebar-section {
+ padding: var(--space-4);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.sidebar-section:last-child {
+ border-bottom: none;
+}
+
+.sidebar-section-title {
+ font-size: var(--text-xs);
+ font-weight: var(--font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-secondary);
+ margin-bottom: var(--space-3);
+}
+
+/* Main content area */
+.app-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-4);
+}
+
+.app-content-full {
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+/* ============================================
+ DASHBOARD LAYOUTS
+ ============================================ */
+
+/* Full-screen dashboard (maps, etc.) */
+.dashboard-layout {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.dashboard-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-2) var(--space-4);
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+.dashboard-header-logo {
+ font-size: var(--text-lg);
+ font-weight: var(--font-bold);
+ color: var(--text-primary);
+}
+
+.dashboard-header-logo span {
+ font-size: var(--text-sm);
+ font-weight: var(--font-normal);
+ color: var(--text-dim);
+ margin-left: var(--space-2);
+}
+
+.dashboard-main {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+ position: relative;
+}
+
+.dashboard-map {
+ flex: 1;
+ position: relative;
+}
+
+.dashboard-sidebar {
+ width: 320px;
+ background: var(--bg-secondary);
+ border-left: 1px solid var(--border-color);
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ padding: var(--space-3);
+}
+
+/* ============================================
+ PAGE LAYOUTS
+ ============================================ */
+.page-container {
+ max-width: var(--content-max-width);
+ margin: 0 auto;
+ padding: var(--space-6);
+}
+
+.page-header {
+ margin-bottom: var(--space-6);
+}
+
+.page-title {
+ font-size: var(--text-3xl);
+ font-weight: var(--font-bold);
+ color: var(--text-primary);
+ margin-bottom: var(--space-2);
+}
+
+.page-description {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+}
+
+/* ============================================
+ RESPONSIVE BREAKPOINTS
+ ============================================ */
+@media (max-width: 1024px) {
+ .app-sidebar {
+ position: fixed;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ z-index: var(--z-fixed);
+ transform: translateX(-100%);
+ transition: transform var(--transition-base);
+ }
+
+ .app-sidebar.open {
+ transform: translateX(0);
+ }
+
+ .dashboard-sidebar {
+ width: 280px;
+ }
+}
+
+@media (max-width: 768px) {
+ .app-nav {
+ display: none;
+ }
+
+ .mobile-nav {
+ display: flex;
+ }
+
+ .hamburger-btn {
+ display: flex;
+ }
+
+ .app-header {
+ padding: 0 var(--space-3);
+ }
+
+ .app-logo-text {
+ display: none;
+ }
+
+ .header-utilities {
+ gap: var(--space-1);
+ }
+
+ .page-container {
+ padding: var(--space-4);
+ }
+
+ .dashboard-sidebar {
+ position: fixed;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: var(--z-fixed);
+ transform: translateX(100%);
+ transition: transform var(--transition-base);
+ }
+
+ .dashboard-sidebar.open {
+ transform: translateX(0);
+ }
+}
+
+/* ============================================
+ OVERLAY (for mobile drawers)
+ ============================================ */
+.drawer-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: calc(var(--z-fixed) - 1);
+ opacity: 0;
+ visibility: hidden;
+ transition: all var(--transition-base);
+}
+
+.drawer-overlay.visible {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* ============================================
+ BACK LINK
+ ============================================ */
+.back-link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+.back-link:hover {
+ color: var(--accent-cyan);
+}
+
+/* ============================================
+ MODE NAVIGATION (from index.css)
+ Used by nav.html partial across all pages
+ ============================================ */
+
+/* Mode Navigation Bar */
+.mode-nav {
+ display: none;
+ background: #151a23 !important; /* Explicit color - forced to ensure consistency */
+ border-bottom: 1px solid var(--border-color);
+ padding: 0 20px;
+ position: relative;
+ z-index: 100;
+}
+
+@media (min-width: 1024px) {
+ .mode-nav {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 44px;
+ }
+}
+
+.mode-nav-group {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.mode-nav-label {
+ font-size: 9px;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-right: 8px;
+ font-weight: 500;
+}
+
+.mode-nav-divider {
+ width: 1px;
+ height: 24px;
+ background: var(--border-color);
+ margin: 0 12px;
+}
+
+.mode-nav-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ background: transparent;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ color: var(--text-secondary);
+ font-family: var(--font-sans);
+ font-size: 11px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s ease;
+}
+
+.mode-nav-btn .nav-icon {
+ font-size: 14px;
+}
+
+.mode-nav-btn .nav-icon svg {
+ width: 14px;
+ height: 14px;
+}
+
+.mode-nav-btn .nav-label {
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.mode-nav-btn:hover {
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.mode-nav-btn.active {
+ background: var(--accent-cyan);
+ color: var(--bg-primary);
+ border-color: var(--accent-cyan);
+}
+
+.mode-nav-btn.active .nav-icon {
+ filter: brightness(0);
+}
+
+.mode-nav-actions {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.nav-action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ background: var(--bg-elevated);
+ border: 1px solid var(--accent-cyan);
+ border-radius: 4px;
+ color: var(--accent-cyan);
+ font-family: var(--font-sans);
+ font-size: 11px;
+ font-weight: 500;
+ text-decoration: none;
+ cursor: pointer;
+ transition: all 0.15s ease;
+}
+
+.nav-action-btn .nav-icon {
+ font-size: 12px;
+}
+
+.nav-action-btn .nav-label {
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.nav-action-btn:hover {
+ background: var(--accent-cyan);
+ color: var(--bg-primary);
+}
+
+/* Dropdown Navigation */
+.mode-nav-dropdown {
+ position: relative;
+}
+
+.mode-nav-dropdown-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 14px;
+ background: transparent;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ color: var(--text-secondary);
+ font-family: var(--font-sans);
+ font-size: 11px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s ease;
+}
+
+.mode-nav-dropdown-btn:hover {
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.mode-nav-dropdown-btn .nav-icon {
+ font-size: 14px;
+}
+
+.mode-nav-dropdown-btn .nav-icon svg {
+ width: 14px;
+ height: 14px;
+}
+
+.mode-nav-dropdown-btn .nav-label {
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.mode-nav-dropdown-btn .dropdown-arrow {
+ font-size: 8px;
+ margin-left: 4px;
+ transition: transform 0.2s ease;
+}
+
+.mode-nav-dropdown-btn .dropdown-arrow svg {
+ width: 10px;
+ height: 10px;
+}
+
+.mode-nav-dropdown.open .mode-nav-dropdown-btn {
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.mode-nav-dropdown.open .dropdown-arrow {
+ transform: rotate(180deg);
+}
+
+.mode-nav-dropdown.has-active .mode-nav-dropdown-btn {
+ background: var(--accent-cyan);
+ color: var(--bg-primary);
+ border-color: var(--accent-cyan);
+}
+
+.mode-nav-dropdown.has-active .mode-nav-dropdown-btn .nav-icon {
+ filter: brightness(0);
+}
+
+.mode-nav-dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 4px;
+ min-width: 180px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-8px);
+ transition: all 0.15s ease;
+ z-index: 1000;
+ padding: 6px;
+}
+
+.mode-nav-dropdown.open .mode-nav-dropdown-menu {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+}
+
+.mode-nav-dropdown-menu .mode-nav-btn {
+ width: 100%;
+ justify-content: flex-start;
+ padding: 10px 12px;
+ border-radius: 4px;
+ margin: 0;
+}
+
+.mode-nav-dropdown-menu .mode-nav-btn:hover {
+ background: var(--bg-elevated);
+}
+
+.mode-nav-dropdown-menu .mode-nav-btn.active {
+ background: var(--accent-cyan);
+ color: var(--bg-primary);
+}
+
+/* Nav Bar Utilities (clock, theme, tools) */
+.nav-utilities {
+ display: none;
+ align-items: center;
+ gap: 12px;
+ margin-left: auto;
+ flex-shrink: 0;
+}
+
+@media (min-width: 1024px) {
+ .nav-utilities {
+ display: flex;
+ }
+}
+
+.nav-clock {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-family: var(--font-mono);
+ font-size: 11px;
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.nav-clock .utc-label {
+ font-size: 9px;
+ color: var(--text-dim);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.nav-clock .utc-time {
+ color: var(--accent-cyan);
+ font-weight: 600;
+}
+
+.nav-divider {
+ width: 1px;
+ height: 20px;
+ background: var(--border-color);
+}
+
+.nav-tools {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex-shrink: 0;
+}
+
+.nav-tool-btn {
+ width: 28px;
+ height: 28px;
+ min-width: 28px;
+ border-radius: 4px;
+ background: transparent;
+ border: 1px solid transparent;
+ color: var(--text-secondary);
+ font-size: 14px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.nav-tool-btn:hover {
+ background: var(--bg-elevated);
+ border-color: var(--border-color);
+ color: var(--accent-cyan);
+}
+
+.nav-tool-btn svg {
+ width: 14px;
+ height: 14px;
+}
+
+.nav-tool-btn .icon svg {
+ width: 14px;
+ height: 14px;
+}
+
+/* Theme toggle icon states in nav bar */
+.nav-tool-btn .icon-sun,
+.nav-tool-btn .icon-moon {
+ position: absolute;
+ transition: opacity 0.2s, transform 0.2s;
+ font-size: 14px;
+}
+
+.nav-tool-btn .icon-sun {
+ opacity: 0;
+ transform: rotate(-90deg);
+}
+
+.nav-tool-btn .icon-moon {
+ opacity: 1;
+ transform: rotate(0deg);
+}
+
+[data-theme="light"] .nav-tool-btn .icon-sun {
+ opacity: 1;
+ transform: rotate(0deg);
+}
+
+[data-theme="light"] .nav-tool-btn .icon-moon {
+ opacity: 0;
+ transform: rotate(90deg);
+}
+
+/* Effects toggle icon states */
+.nav-tool-btn .icon-effects-off {
+ display: none;
+}
+
+[data-animations="off"] .nav-tool-btn .icon-effects-on {
+ display: none;
+}
+
+[data-animations="off"] .nav-tool-btn .icon-effects-off {
+ display: flex;
+}
diff --git a/static/css/core/variables.css b/static/css/core/variables.css
new file mode 100644
index 0000000..32b79a0
--- /dev/null
+++ b/static/css/core/variables.css
@@ -0,0 +1,198 @@
+/**
+ * INTERCEPT Design Tokens
+ * Single source of truth for colors, spacing, typography, and effects
+ * Import this file FIRST in any stylesheet that needs design tokens
+ */
+
+:root {
+ /* ============================================
+ COLOR PALETTE - Dark Theme (Default)
+ ============================================ */
+
+ /* Backgrounds - layered depth system */
+ --bg-primary: #0a0c10;
+ --bg-secondary: #0f1218;
+ --bg-tertiary: #151a23;
+ --bg-card: #121620;
+ --bg-elevated: #1a202c;
+ --bg-overlay: rgba(0, 0, 0, 0.7);
+
+ /* Background aliases for components */
+ --bg-dark: var(--bg-primary);
+ --bg-panel: var(--bg-secondary);
+
+ /* Accent colors */
+ --accent-cyan: #4a9eff;
+ --accent-cyan-dim: rgba(74, 158, 255, 0.15);
+ --accent-cyan-hover: #6bb3ff;
+ --accent-green: #22c55e;
+ --accent-green-dim: rgba(34, 197, 94, 0.15);
+ --accent-red: #ef4444;
+ --accent-red-dim: rgba(239, 68, 68, 0.15);
+ --accent-orange: #f59e0b;
+ --accent-orange-dim: rgba(245, 158, 11, 0.15);
+ --accent-amber: #d4a853;
+ --accent-amber-dim: rgba(212, 168, 83, 0.15);
+ --accent-yellow: #eab308;
+ --accent-purple: #a855f7;
+
+ /* Text hierarchy */
+ --text-primary: #e8eaed;
+ --text-secondary: #9ca3af;
+ --text-dim: #4b5563;
+ --text-muted: #374151;
+ --text-inverse: #0a0c10;
+
+ /* Borders */
+ --border-color: #1f2937;
+ --border-light: #374151;
+ --border-glow: rgba(74, 158, 255, 0.2);
+ --border-focus: var(--accent-cyan);
+
+ /* Status colors */
+ --status-online: #22c55e;
+ --status-warning: #f59e0b;
+ --status-error: #ef4444;
+ --status-offline: #6b7280;
+ --status-info: #3b82f6;
+
+ /* ============================================
+ SPACING SCALE
+ ============================================ */
+ --space-1: 4px;
+ --space-2: 8px;
+ --space-3: 12px;
+ --space-4: 16px;
+ --space-5: 20px;
+ --space-6: 24px;
+ --space-8: 32px;
+ --space-10: 40px;
+ --space-12: 48px;
+ --space-16: 64px;
+
+ /* ============================================
+ TYPOGRAPHY
+ ============================================ */
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
+
+ /* Font sizes */
+ --text-xs: 10px;
+ --text-sm: 12px;
+ --text-base: 14px;
+ --text-lg: 16px;
+ --text-xl: 18px;
+ --text-2xl: 20px;
+ --text-3xl: 24px;
+ --text-4xl: 30px;
+
+ /* Font weights */
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ /* Line heights */
+ --leading-tight: 1.25;
+ --leading-normal: 1.5;
+ --leading-relaxed: 1.75;
+
+ /* ============================================
+ BORDERS & RADIUS
+ ============================================ */
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-full: 9999px;
+
+ /* ============================================
+ SHADOWS
+ ============================================ */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
+ --shadow-glow: 0 0 20px rgba(74, 158, 255, 0.15);
+
+ /* ============================================
+ TRANSITIONS
+ ============================================ */
+ --transition-fast: 150ms ease;
+ --transition-base: 200ms ease;
+ --transition-slow: 300ms ease;
+
+ /* ============================================
+ Z-INDEX SCALE
+ ============================================ */
+ --z-base: 0;
+ --z-dropdown: 100;
+ --z-sticky: 200;
+ --z-fixed: 300;
+ --z-modal-backdrop: 400;
+ --z-modal: 500;
+ --z-toast: 600;
+ --z-tooltip: 700;
+
+ /* ============================================
+ LAYOUT
+ ============================================ */
+ --header-height: 60px;
+ --nav-height: 44px;
+ --sidebar-width: 280px;
+ --stats-strip-height: 36px;
+ --content-max-width: 1400px;
+}
+
+/* ============================================
+ LIGHT THEME OVERRIDES
+ ============================================ */
+[data-theme="light"] {
+ --bg-primary: #f8fafc;
+ --bg-secondary: #f1f5f9;
+ --bg-tertiary: #e2e8f0;
+ --bg-card: #ffffff;
+ --bg-elevated: #f8fafc;
+ --bg-overlay: rgba(255, 255, 255, 0.9);
+
+ /* Background aliases for components */
+ --bg-dark: var(--bg-primary);
+ --bg-panel: var(--bg-secondary);
+
+ --accent-cyan: #2563eb;
+ --accent-cyan-dim: rgba(37, 99, 235, 0.1);
+ --accent-cyan-hover: #1d4ed8;
+ --accent-green: #16a34a;
+ --accent-green-dim: rgba(22, 163, 74, 0.1);
+ --accent-red: #dc2626;
+ --accent-red-dim: rgba(220, 38, 38, 0.1);
+ --accent-orange: #d97706;
+ --accent-orange-dim: rgba(217, 119, 6, 0.1);
+ --accent-amber: #b45309;
+ --accent-amber-dim: rgba(180, 83, 9, 0.1);
+
+ --text-primary: #0f172a;
+ --text-secondary: #475569;
+ --text-dim: #94a3b8;
+ --text-muted: #cbd5e1;
+ --text-inverse: #f8fafc;
+
+ --border-color: #e2e8f0;
+ --border-light: #cbd5e1;
+ --border-glow: rgba(37, 99, 235, 0.15);
+
+ --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 20px rgba(37, 99, 235, 0.1);
+}
+
+/* ============================================
+ REDUCED MOTION
+ ============================================ */
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --transition-fast: 0ms;
+ --transition-base: 0ms;
+ --transition-slow: 0ms;
+ }
+}
diff --git a/templates/components/card.html b/templates/components/card.html
new file mode 100644
index 0000000..c83088c
--- /dev/null
+++ b/templates/components/card.html
@@ -0,0 +1,24 @@
+{#
+ Card/Panel Component
+ Reusable container with optional header and footer
+
+ Variables:
+ - title: Optional card header title
+ - indicator: If true, shows status indicator dot in header
+ - indicator_active: If true, indicator is active/green
+ - no_padding: If true, removes body padding
+#}
+
+
+ {% if title %}
+
+ {% endif %}
+
+ {{ caller() }}
+
+
diff --git a/templates/components/empty_state.html b/templates/components/empty_state.html
new file mode 100644
index 0000000..8673707
--- /dev/null
+++ b/templates/components/empty_state.html
@@ -0,0 +1,38 @@
+{#
+ Empty State Component
+ Display when no data is available
+
+ Variables:
+ - icon: Optional SVG icon (default: generic empty icon)
+ - title: Main message (default: "No data")
+ - description: Optional helper text
+ - action_text: Optional button text
+ - action_onclick: Optional button onclick handler
+ - action_href: Optional button link
+#}
+
+
+
+ {% if icon %}
+ {{ icon|safe }}
+ {% else %}
+
+
+
+
+ {% endif %}
+
+
{{ title|default('No data') }}
+ {% if description %}
+
{{ description }}
+ {% endif %}
+ {% if action_text %}
+
+ {% if action_href %}
+
{{ action_text }}
+ {% elif action_onclick %}
+
{{ action_text }}
+ {% endif %}
+
+ {% endif %}
+
diff --git a/templates/components/loading.html b/templates/components/loading.html
new file mode 100644
index 0000000..c616b93
--- /dev/null
+++ b/templates/components/loading.html
@@ -0,0 +1,27 @@
+{#
+ Loading State Component
+ Display while data is being fetched
+
+ Variables:
+ - text: Optional loading text (default: "Loading...")
+ - size: 'sm', 'md', or 'lg' (default: 'md')
+ - overlay: If true, renders as full overlay
+#}
+
+{% if overlay %}
+
+
+
+ {% if text %}
+
{{ text }}
+ {% endif %}
+
+
+{% else %}
+
+
+ {% if text %}
+
{{ text }}
+ {% endif %}
+
+{% endif %}
diff --git a/templates/components/stats_strip.html b/templates/components/stats_strip.html
new file mode 100644
index 0000000..b70e2db
--- /dev/null
+++ b/templates/components/stats_strip.html
@@ -0,0 +1,47 @@
+{#
+ Stats Strip Component
+ Horizontal bar displaying key metrics
+
+ Variables:
+ - stats: List of stat objects with 'id', 'value', 'label', and optional 'title'
+ - show_divider: Show divider after stats (default: true)
+ - status_dot_id: Optional ID for status indicator dot
+ - status_text_id: Optional ID for status text
+ - time_id: Optional ID for time display
+#}
+
+
+
+ {% for stat in stats %}
+
+ {{ stat.value|default('0') }}
+ {{ stat.label }}
+
+ {% endfor %}
+
+ {% if show_divider|default(true) %}
+
+ {% endif %}
+
+ {# Additional content from caller #}
+ {% if caller is defined %}
+ {{ caller() }}
+ {% endif %}
+
+ {% if status_dot_id or status_text_id %}
+
+
+ {% if status_dot_id %}
+
+ {% endif %}
+ {% if status_text_id %}
+
STANDBY
+ {% endif %}
+
+ {% endif %}
+
+ {% if time_id %}
+
--:--:-- UTC
+ {% endif %}
+
+
diff --git a/templates/components/status_badge.html b/templates/components/status_badge.html
new file mode 100644
index 0000000..783aeeb
--- /dev/null
+++ b/templates/components/status_badge.html
@@ -0,0 +1,27 @@
+{#
+ Status Badge Component
+ Compact status indicator with dot and text
+
+ Variables:
+ - status: 'online', 'offline', 'warning', 'error' (default: 'offline')
+ - text: Status text to display
+ - id: Optional ID for the text element (for JS updates)
+ - dot_id: Optional ID for the dot element (for JS updates)
+ - pulse: If true, adds pulse animation to dot
+#}
+
+{% set status_class = {
+ 'online': 'online',
+ 'active': 'online',
+ 'offline': 'offline',
+ 'warning': 'warning',
+ 'error': 'error',
+ 'inactive': 'inactive'
+}.get(status|default('offline'), 'inactive') %}
+
+
+
+
{{ text|default('Unknown') }}
+
diff --git a/templates/layout/base.html b/templates/layout/base.html
new file mode 100644
index 0000000..09e97ee
--- /dev/null
+++ b/templates/layout/base.html
@@ -0,0 +1,169 @@
+
+
+
+
+
+ {% block title %}iNTERCEPT{% endblock %} // iNTERCEPT
+
+
+ {# Fonts - Conditional CDN/Local loading #}
+ {% if offline_settings and offline_settings.fonts_source == 'local' %}
+
+ {% else %}
+
+ {% endif %}
+
+ {# Core CSS (Design System) #}
+
+
+
+
+
+ {# Responsive styles #}
+
+
+ {# Page-specific CSS #}
+ {% block styles %}{% endblock %}
+
+ {# Page-specific head content #}
+ {% block head %}{% endblock %}
+
+
+
+ {# Global Header #}
+ {% block header %}
+
+ {% endblock %}
+
+ {# Global Navigation - opt-in for pages that need it #}
+ {# Override this block and include 'partials/nav.html' in child templates #}
+ {% block navigation %}{% endblock %}
+
+ {# Main Content Area #}
+
+ {% block main %}
+
+ {# Optional Sidebar #}
+ {% block sidebar %}{% endblock %}
+
+ {# Page Content #}
+
+ {% block content %}{% endblock %}
+
+
+ {% endblock %}
+
+
+ {# Toast/Notification Container #}
+
+
+
+ {# Core JavaScript #}
+
+
+ {# Page-specific JavaScript #}
+ {% block scripts %}{% endblock %}
+
+
diff --git a/templates/layout/base_dashboard.html b/templates/layout/base_dashboard.html
new file mode 100644
index 0000000..4853bd4
--- /dev/null
+++ b/templates/layout/base_dashboard.html
@@ -0,0 +1,226 @@
+{% extends 'layout/base.html' %}
+
+{#
+ Dashboard Base Template
+ Extended layout for full-screen dashboard pages (ADSB, AIS, Satellite, etc.)
+ Features: Full-height layout, stats strip, sidebar overlay on mobile
+
+ Variables:
+ - active_mode: The current mode for nav highlighting (e.g., 'adsb', 'ais', 'satellite')
+#}
+
+{% block styles %}
+{{ super() }}
+
+{% endblock %}
+
+{% block header %}
+
+{% endblock %}
+
+{% block navigation %}
+{# Include the unified nav partial with active_mode set #}
+{% include 'partials/nav.html' with context %}
+{% endblock %}
+
+{% block main %}
+{# Background effects #}
+
+ {% block dashboard_bg %}
+
+ {% endblock %}
+
+
+
+{# Stats strip #}
+{% block stats_strip %}{% endblock %}
+
+{# Dashboard content #}
+
+ {% block dashboard_content %}{% endblock %}
+
+{% endblock %}
+
+{% block scripts %}
+{{ super() }}
+
+{% endblock %}
diff --git a/templates/partials/nav.html b/templates/partials/nav.html
new file mode 100644
index 0000000..f2485cf
--- /dev/null
+++ b/templates/partials/nav.html
@@ -0,0 +1,317 @@
+{#
+ Global Navigation Partial
+ Single source of truth for app navigation
+
+ Compatible with:
+ - index.html (uses switchMode() for mode panels)
+ - Dashboard pages (uses navigation links)
+
+ Variables:
+ - active_mode: Current active mode (e.g., 'pager', 'adsb', 'wifi')
+ - is_index_page: If true, Satellite/SSTV use switchMode (panel mode)
+ If false (default), Satellite links to dashboard
+#}
+
+{% set is_index_page = is_index_page|default(false) %}
+
+{# Desktop Navigation - uses existing CSS class names for compatibility #}
+
+ {# SDR / RF Group #}
+
+
+ {# Wireless Group #}
+
+
+ {# Security Group #}
+
+
+ {# Space Group #}
+
+
+ {# Dynamic dashboard button (shown when in satellite mode) #}
+
+
+ {# Nav Utilities (clock, theme, tools) #}
+
+
+
+{# Mobile Navigation Bar #}
+
+
+
+ 433MHz
+
+
+ Meters
+
+
+ Aircraft
+
+
+ Vessels
+
+
+ APRS
+
+
+ WiFi
+
+
+ BT
+
+
+ TSCM
+
+ {% if is_index_page %}
+
+ Sat
+
+ {% else %}
+
+ Sat
+
+ {% endif %}
+
+ SSTV
+
+
+ Scanner
+
+
+ Spy
+
+
+ Mesh
+
+
+
+{# JavaScript stub for pages that don't have switchMode defined #}
+
diff --git a/templates/partials/page_header.html b/templates/partials/page_header.html
new file mode 100644
index 0000000..5d13ecd
--- /dev/null
+++ b/templates/partials/page_header.html
@@ -0,0 +1,37 @@
+{#
+ Page Header Partial
+ Consistent page title with optional description and actions
+
+ Variables:
+ - title: Page title (required)
+ - description: Optional description text
+ - back_url: Optional back link URL
+ - back_text: Optional back link text (default: "Back")
+#}
+
+