Files
intercept/docs/UI_GUIDE.md
Smittix ab033b35d3 feat: WiFi Locate mode, mobile nav groups, v2.24.0
Add WiFi Locate mode for locating access points by BSSID with real-time
signal meter, distance estimation, RSSI history chart, and audio
proximity tones. Includes hand-off from WiFi detail drawer, environment
presets (Free Space/Outdoor/Indoor), and signal-lost detection.

Also includes:
- Mobile navigation reorganized into labeled groups (SIG/TRK/SPC/WIFI/INTEL/SYS)
- flask-limiter made optional with graceful degradation
- Fix radiosonde setup missing semver Python dependency
- Documentation updates (FEATURES, USAGE, UI_GUIDE, GitHub Pages site)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:49:03 +00:00

15 KiB

iNTERCEPT UI Guide

This guide documents the UI design system, components, and patterns used in iNTERCEPT.

Table of Contents

  1. Design Tokens
  2. Base Templates
  3. Navigation
  4. Components
  5. Adding a New Module Page
  6. 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

/* 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

--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 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

--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 data-theme="dark">  <!-- or "light" -->

Toggle with 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.

{% extends 'layout/base.html' %}

{% block title %}My Page Title{% endblock %}

{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/my-page.css') }}">
{% endblock %}

{% block navigation %}
{% set active_mode = 'mymode' %}
{% include 'partials/nav.html' %}
{% endblock %}

{% block sidebar %}
<div class="app-sidebar">
    <!-- Sidebar content -->
</div>
{% endblock %}

{% block content %}
<div class="page-container">
    <h1>Page Title</h1>
    <!-- Page content -->
</div>
{% endblock %}

{% block scripts %}
<script>
    // Page-specific JavaScript
</script>
{% endblock %}

templates/layout/base_dashboard.html

Extended base for full-screen dashboards (maps, visualizations).

{% extends 'layout/base_dashboard.html' %}

{% set active_mode = 'mydashboard' %}

{% block dashboard_title %}MY DASHBOARD{% endblock %}

{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/my_dashboard.css') }}">
{% endblock %}

{% block stats_strip %}
<div class="stats-strip">
    <!-- Stats bar content -->
</div>
{% endblock %}

{% block dashboard_content %}
<div class="dashboard-map-container">
    <!-- Main visualization -->
</div>
<div class="dashboard-sidebar">
    <!-- Sidebar panels -->
</div>
{% endblock %}

Navigation

Including Navigation

{% 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
weathersat Weather satellites
sstv_general HF SSTV
gps GPS tracking
websdr WebSDR
subghz Sub-GHz analyzer
bt_locate BT Locate
wifi_locate WiFi Locate
analytics Analytics dashboard
spaceweather Space weather

Navigation Groups

The navigation is organized into groups:

  • Signals: Pager, 433MHz, Meters, Listening Post, SubGHz
  • Tracking: Aircraft, Vessels, APRS, GPS
  • Space: Satellite, ISS SSTV, Weather Sat, HF SSTV, Space Weather
  • Wireless: WiFi, Bluetooth, BT Locate, WiFi Locate, Meshtastic
  • Intel: TSCM, Analytics, Spy Stations, WebSDR

Components

Card / Panel

{% call card(title='PANEL TITLE', indicator=true, indicator_active=false) %}
<p>Panel content here</p>
{% endcall %}

Or manually:

<div class="panel">
    <div class="panel-header">
        <span>PANEL TITLE</span>
        <div class="panel-indicator active"></div>
    </div>
    <div class="panel-content">
        <p>Content here</p>
    </div>
</div>

Empty State

{% 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

{# 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

{% with status='online', text='Connected', id='connectionStatus' %}
{% include 'components/status_badge.html' %}
{% endwith %}

Status values: online, offline, warning, error, inactive

Buttons

<!-- Primary action -->
<button class="btn btn-primary">Start Tracking</button>

<!-- Secondary action -->
<button class="btn btn-secondary">Cancel</button>

<!-- Danger action -->
<button class="btn btn-danger">Stop</button>

<!-- Ghost/subtle -->
<button class="btn btn-ghost">Settings</button>

<!-- Sizes -->
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-lg">Large</button>

<!-- Icon button -->
<button class="btn btn-icon btn-secondary">
    <span class="icon">...</span>
</button>

Badges

<span class="badge">Default</span>
<span class="badge badge-primary">Primary</span>
<span class="badge badge-success">Online</span>
<span class="badge badge-warning">Warning</span>
<span class="badge badge-danger">Error</span>

Form Groups

<div class="form-group">
    <label for="frequency">Frequency (MHz)</label>
    <input type="text" id="frequency" value="153.350">
    <span class="form-help">Enter frequency in MHz</span>
</div>

<div class="form-group">
    <label for="gain">Gain</label>
    <select id="gain">
        <option value="auto">Auto</option>
        <option value="30">30 dB</option>
    </select>
</div>

<label class="form-check">
    <input type="checkbox" id="alerts">
    <span>Enable alerts</span>
</label>

Stats Strip

Used in dashboards for horizontal statistics display:

<div class="stats-strip">
    <div class="stats-strip-inner">
        <div class="strip-stat">
            <span class="strip-value" id="count">0</span>
            <span class="strip-label">COUNT</span>
        </div>
        <div class="strip-divider"></div>
        <div class="strip-status">
            <div class="status-dot active" id="statusDot"></div>
            <span id="statusText">TRACKING</span>
        </div>
        <div class="strip-time" id="utcTime">--:--:-- UTC</div>
    </div>
</div>

Adding a New Module Page

1. Create the Route

In routes/mymodule.py:

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:

from routes.mymodule import mymodule_bp
app.register_blueprint(mymodule_bp)

3. Create the Template

Option A: Simple page extending base.html

{% extends 'layout/base.html' %}
{% set active_mode = 'mymodule' %}

{% block title %}My Module{% endblock %}

{% block navigation %}
{% include 'partials/nav.html' %}
{% endblock %}

{% block content %}
<!-- Your content -->
{% endblock %}

Option B: Full-screen dashboard

{% extends 'layout/base_dashboard.html' %}
{% set active_mode = 'mymodule' %}

{% block dashboard_title %}MY MODULE{% endblock %}

{% block dashboard_content %}
<!-- Your dashboard content -->
{% endblock %}

4. Add to Navigation

In templates/partials/nav.html, add your module to the appropriate group:

<button class="mode-nav-btn {% if active_mode == 'mymodule' %}active{% endif %}"
        onclick="switchMode('mymodule')">
    <span class="nav-icon icon"><!-- SVG icon --></span>
    <span class="nav-label">My Module</span>
</button>

Or if it's a dashboard link:

<a href="/mymodule/dashboard"
   class="mode-nav-btn {% if active_mode == 'mymodule' %}active{% endif %}"
   style="text-decoration: none;">
    <span class="nav-icon icon"><!-- SVG icon --></span>
    <span class="nav-label">My Module</span>
</a>

5. Create Stylesheet

In static/css/mymodule.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

<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MY DASHBOARD // iNTERCEPT</title>
    <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">

    <!-- Design tokens (required) -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">

    <!-- Fonts -->
    {% if offline_settings.fonts_source == 'local' %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
    {% else %}
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    {% endif %}

    <!-- External libraries if needed -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

    <!-- Dashboard styles -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/mydashboard.css') }}">
</head>
<body>
    <!-- Background effects -->
    <div class="radar-bg"></div>
    <div class="scanline"></div>

    <!-- Header -->
    <header class="header">
        <div class="logo">
            <a href="/" style="color: inherit; text-decoration: none;">
                MY DASHBOARD
                <span>// iNTERCEPT</span>
            </a>
        </div>
        <div class="status-bar">
            <a href="#" onclick="history.back(); return false;" class="back-link">Back</a>
            <a href="/" class="back-link">Main Dashboard</a>
        </div>
    </header>

    <!-- Unified Navigation -->
    {% set active_mode = 'mydashboard' %}
    {% include 'partials/nav.html' %}

    <!-- Stats Strip -->
    <div class="stats-strip">
        <!-- Stats content -->
    </div>

    <!-- Main Dashboard Content -->
    <main class="dashboard">
        <!-- Your dashboard layout -->
    </main>

    <script>
        // Dashboard JavaScript
    </script>
</body>
</html>

2. Create the Stylesheet

/**
 * 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