From 837090d15089163a88fe55e57d534baf2c6211b9 Mon Sep 17 00:00:00 2001 From: James Smith Date: Thu, 26 Mar 2026 21:54:13 +0000 Subject: [PATCH] Finalise WiFi scanner redesign spec (reviewer approved) --- ...2026-03-26-wifi-scanner-redesign-design.md | 368 ++++++++++++++---- 1 file changed, 300 insertions(+), 68 deletions(-) diff --git a/docs/superpowers/specs/2026-03-26-wifi-scanner-redesign-design.md b/docs/superpowers/specs/2026-03-26-wifi-scanner-redesign-design.md index 0fc00ec..ca1d9f2 100644 --- a/docs/superpowers/specs/2026-03-26-wifi-scanner-redesign-design.md +++ b/docs/superpowers/specs/2026-03-26-wifi-scanner-redesign-design.md @@ -17,112 +17,344 @@ Redesign the WiFi scanner's main content area for better space utilisation, visu | Channel analysis | Heatmap (channels × time) | Shows congestion history, more useful than a snapshot bar chart | | Row click behaviour | Right panel takeover (detail replaces heatmap) | Keeps spatial layout stable; no scroll disruption | +## CSS Tokens + +Tokens confirmed in `index.css :root`: `--bg-primary`, `--bg-secondary`, `--bg-tertiary`, `--bg-card`, `--border-color`, `--border-light`, `--text-primary`, `--text-secondary`, `--text-dim`, `--accent-cyan`, `--accent-cyan-dim`, `--accent-green`, `--accent-green-dim`, `--accent-red`, `--accent-red-dim`, `--accent-orange`, `--accent-amber`, `--accent-amber-dim`. + +**Not defined in `index.css :root`** — do not use: +- `--accent-yellow` (used in some existing WiFi rules but undefined — use `--accent-orange` instead) +- `--accent-cyan-rgb` (undefined — use the literal `rgba(74, 163, 255, 0.07)` for the selected row tint) + ## Component Designs ### 1. Status Bar Enhanced version of the existing `wifi-status-bar`: -- Existing fields: Networks · Clients · Hidden -- **New:** Open count in `var(--accent-red)` — instant threat count -- **New:** Scan indicator (pulsing dot + "SCANNING · ch N" text) floated right, updates to "IDLE" when stopped -- Minor spacing/typography polish +- Existing fields preserved: Networks · Clients · Hidden (IDs: `wifiNetworkCount`, `wifiClientCount`, `wifiHiddenCount`) +- **New — Open count:** Add `
Open0
` after the Hidden item. Populated by `renderNetworks()` counting networks where `security === 'Open'`. +- **Scan indicator — HTML:** Replace the existing `
` with: + ```html +
+ + IDLE +
+ ``` + CSS: `.wifi-scan-dot` is a 7×7px circle with `animation: wifi-pulse 1.2s ease-in-out infinite` (opacity 1→0.4→1, scale 1→0.7→1). The dot is hidden (`display:none`) when idle; shown when scanning. `.wifi-scan-indicator` is floated/pushed to `margin-left: auto`. +- `updateScanningState(scanning, scanMode)` — revised body: + ```js + const dot = elements.scanIndicator?.querySelector('.wifi-scan-dot'); + const text = elements.scanIndicator?.querySelector('.wifi-scan-text'); + if (dot) dot.style.display = scanning ? 'inline-block' : 'none'; + if (text) text.textContent = scanning + ? `SCANNING (${scanMode === 'quick' ? 'Quick' : 'Deep'})` + : 'IDLE'; + ``` + `elements.scanIndicator` replaces `elements.scanStatus` in the `elements` map, pointing to `#wifiScanIndicator`. ### 2. Networks Table -**Row structure (two visual lines per row):** +**Structural change:** Remove `` (including `` and ``). Replace with `
`. Update `elements.networkTable → elements.networkList` and `elements.networkTableBody → elements.networkList` in the `elements` map in `wifi.js`. +**Sort controls:** The `th[data-sort]` click listener on the old table is removed. Add three small text-style sort buttons to the `.wifi-networks-header`: +```html +
+ Sort: + + + +
``` -[left-border] SSID name / [Hidden] italic [SECURITY BADGE] [HIDDEN TAG] - [━━━━━━━━━▓░░░░░░░] signal bar ch 6 · 4 ↔ · −48 +Clicking a button sets the existing `sortBy` state variable and calls `renderNetworks()`. Active button gets `.active` class (cyan text). + +**Filter buttons:** `#wifiNetworkFilters` / `.wifi-filter-btn` bar (All / 2.4G / 5G / Open / Hidden) is kept as-is in HTML. The JS `applyFilters()` function is adapted to operate on `
` elements using their `data-band` and `data-security` attributes (same logic, different element type). + +**Row HTML structure:** +```html +
+
+ SSID or [Hidden] BSSID +
+ LABEL + HIDDEN +
+
+
+
+
+
+
+
+
+ ch N + N ↔ + −N +
+
+
``` -**Left border colour coding:** -- `var(--accent-red)` — Open security -- `var(--accent-green)` — WPA2 / WPA3 -- `var(--border-color)` (dim) — Hidden network with unknown security +**Left border colour (CSS class on `.network-row`):** +- `.threat-open` → `border-left: 3px solid var(--accent-red)` +- `.threat-safe` → `border-left: 3px solid var(--accent-green)` (WPA2, WPA3) +- `.threat-hidden` → `border-left: 3px solid var(--border-color)` -**Signal bar:** gradient `green → amber → red` based on dBm value: -- Strong (> −55): green fill -- Medium (−55 to −70): green→amber gradient -- Weak (< −70): amber→red gradient +**Signal bar fill width:** `pct = Math.max(0, Math.min(100, (rssi + 100) / 80 * 100))` where −100 dBm → 0%, −20 dBm → 100%. -**Security badge pills** (replacing current flat badges): -- Open: red pill with red border -- WPA2: green pill with green border -- WPA3: cyan pill with cyan border -- WPA/WPA2: amber pill +**Signal fill classes:** +- `.strong` (rssi > −55): `background: linear-gradient(90deg, var(--accent-green), #88d49b)` +- `.medium` (−55 ≥ rssi > −70): `background: linear-gradient(90deg, var(--accent-green), var(--accent-orange))` +- `.weak` (rssi ≤ −70): `background: linear-gradient(90deg, var(--accent-orange), var(--accent-red))` -**Hidden tag:** small dim pill "HIDDEN" next to security badge. +**Security string → badge class mapping** (mirrors existing `securityClass` logic in `wifi.js`): +```js +function securityBadgeClass(security) { + const s = (security || '').toLowerCase(); + if (s === 'open' || s === '') return 'open'; + if (s.includes('wpa3')) return 'wpa3'; + if (s.includes('wpa')) return 'wpa2'; // covers WPA2, WPA/WPA2, WPA PSK, etc. + if (s.includes('wep')) return 'wep'; + return 'wpa2'; // fallback for unknown encrypted +} +``` + +**Security badge colours:** +- `.badge.open` — `var(--accent-red)` text + border, `var(--accent-red-dim)` background +- `.badge.wpa2` — `var(--accent-green)` text + border, `var(--accent-green-dim)` background +- `.badge.wpa3` — `var(--accent-cyan)` text + border, `var(--accent-cyan-dim)` background +- `.badge.wep` — `var(--accent-orange)` text + border, `var(--accent-amber-dim)` background +- `.badge.hidden-tag` — `var(--text-dim)` text, `var(--border-color)` border, transparent background + +**Radar dot colours (same semantic mapping):** +- Open: `var(--accent-red)` / `#e25d5d` +- WPA2 / WPA3: `var(--accent-green)` / `#38c180` +- WEP: `var(--accent-orange)` / `#d6a85e` +- Unknown / Hidden: `#484f58` + +**Selected state persistence across re-renders:** `WiFiMode` stores the selected BSSID in module-level `let selectedBssid = null`. After `renderNetworks()` rebuilds the list, if `selectedBssid` is set, find the matching row by `data-bssid` and add `.selected` to it; also refresh `#wifiDetailView` with updated data for that network. + +**Empty state:** When network list is empty, insert `

No networks detected.
Start a scan to begin.

`. **Row states:** -- Default: transparent background -- Hover: `var(--bg-tertiary)` -- Selected: `rgba(0,180,216,0.07)` + cyan left border override +- Hover: `background: var(--bg-tertiary)` +- Selected: `background: rgba(74, 163, 255, 0.07)` + `border-left-color: var(--accent-cyan)` (via `.selected` class, overrides threat colour) ### 3. Proximity Radar -**Animation:** Pure CSS — a `` element wrapping the sweep line + trailing arc rotates with `animation: radar-sweep 3s linear infinite`. No canvas required. +**Existing `#wifiProximityRadar` div** contents replaced with an inline SVG. `wifi.js` adds `renderRadar(networks)`. -**Sweep elements (inside rotating ``):** -- Trailing arc: a pie-slice `` filling ~60° behind the sweep line, filled `#00b4d8` at ~8% opacity -- Sweep line: `` from centre to edge, cyan, with a green highlight layer at low opacity +**SVG:** `width="100%" viewBox="0 0 210 210"`, centre `(105, 105)`. A `` is applied to the rotating group to prevent arc overflow. -**Network dots:** -- Positioned by signal strength (stronger = closer to centre) -- Sized: near=6px, mid=4.5px, far=3px radius -- Coloured by security: red=Open, green=WPA2/3, amber=WEP/weak, grey=hidden -- Each dot has a soft radial glow halo (second circle at ~1.5× radius, 10–15% opacity) -- Dots are outside the rotating group — they stay stationary +**Rings (static, outside rotating group):** +```html + + + + +``` -**Zone summary row** below radar: Near / Mid / Far counts, coloured red / amber / green respectively. +**Sweep animation — CSS:** +```css +.wifi-radar-sweep { + transform-origin: 105px 105px; + animation: wifi-radar-rotate 3s linear infinite; +} +@keyframes wifi-radar-rotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} +``` -### 4. Right Panel — Channel Heatmap (default) +**Sweep group** ``: +- Trailing arc 60°: `` + _(endpoint derived: x = 105 + 100·sin(60°) ≈ 191.6, y = 105 − 100·cos(60°) + 100 = 155)_ +- Trailing arc 90°: `` + _(endpoint: x=205, y=105 — 90° clockwise from top)_ +- Sweep line: `` -**Layout:** Vertical stack: -1. Band label ("2.4 GHz · Last N scans") -2. Channel number labels (1–11) -3. Heatmap grid (JS-generated `
` cells) -4. Legend gradient bar (Low → High) -5. Security ring chart (always visible below heatmap) +**Network dots** (rendered as SVG `` elements outside the rotating group, replaced by `renderRadar(networks)`): +- Angle per dot: determined by `bssidToAngle(bssid)` — a simple hash of the BSSID string modulo 2π. This produces stable positions across re-renders for the same network. +- Radius from centre: `dotR = 5 + (1 - Math.max(0, Math.min(1, (rssi + 100) / 80))) * 90` — stronger signal → smaller radius +- Zone thresholds: dotR < 35 = Near, 35–70 = Mid, >70 = Far +- Visual radius: Near → 6, Mid → 4.5, Far → 3 (px) +- Each dot: a main `` + a glow halo `` at 1.5× visual radius, same fill colour, 12% opacity +- Colour: see Section 2 "Radar dot colours" -**Heatmap grid:** -- Rows = time steps (newest at top, oldest at bottom, ~10 rows) -- Columns = channels 1–11 for 2.4 GHz, with a tab toggle for 5 GHz -- Cell background colour maps congestion → colour: `#0d1117` (none) → dark blue → `#0ea5e9` (medium) → `#f97316` (high) → `#ef4444` (congested) -- Grid generated by `WiFiMode` using existing channel utilisation data +**Zone counts:** `renderRadar()` updates `#wifiZoneImmediate`, `#wifiZoneNear`, `#wifiZoneFar` (IDs unchanged). -**Security ring chart (below heatmap):** -- SVG ring using `stroke-dasharray` arcs -- Segments: WPA2 (green), Open (red), WPA3 (cyan), WEP (amber), proportional to counts -- Counts shown in a legend list to the right of the ring +### 4. Right Panel — Channel Heatmap + Security Ring -### 5. Right Panel — Network Detail (on row click) +**Removed elements:** The existing `.wifi-channel-section` (containing `.wifi-channel-tabs` and `#wifiChannelChart`), `.wifi-security-section`, and the IDs `openCount`, `wpa2Count`, `wpa3Count`, `wepCount` are all removed from the HTML. Any JS references to these IDs are also removed. -Replaces heatmap content in the same panel. Header changes from "Channel Heatmap" to "Network Detail" and a "← Back" button appears. +**New `.wifi-analysis-panel` inner HTML:** +```html +
+ Channel Heatmap + +
-**Detail panel contents:** -- SSID (large, bold) + BSSID (monospace, dim) -- Signal strength bar (same gradient as table rows, wider) -- 2×3 stat grid: Channel · Band · Security · Cipher · Clients · Vendor -- Action buttons: "⊕ Locate AP" (handoff to `WiFiLocate`) · "Close" (returns to heatmap) +
+
+
2.4 GHz · Last 0 scans
+
+ +
+
+
+ Low +
+ High +
+
+
+ + + + +
+ +
+
+
-Closing via "← Back" or "Close" restores the heatmap and clears row selection. + +``` + +**5 GHz heatmap:** Tab toggle removed. Heatmap always shows 2.4 GHz. **5 GHz networks are excluded from `channelHistory` snapshots** — only networks with `band === '2.4'` are counted when building each snapshot. + +**Heatmap data source:** Module-level `let channelHistory = []` (max 10 entries). Each `renderNetworks()` prepends `{ timestamp: Date.now(), channels: { 1:N, …, 11:N } }` built from 2.4 GHz networks only. Entries beyond 10 are dropped. `wifiHeatmapCount` span is updated with `channelHistory.length`. + +**Heatmap grid DOM structure:** `#wifiHeatmapGrid` is a CSS grid container (`display: grid; grid-template-columns: 26px repeat(11, 1fr); gap: 2px`). `renderHeatmap()` clears and rebuilds it on every call. For each of the up to 10 history entries (newest first), it creates one row of 12 elements: a time-label `
` (text "now" for index 0, empty for others) followed by 11 cell `
` elements whose `background` is set by `congestionColor()`. Total DOM nodes: up to 10 × 12 = 120 divs, fully rebuilt on each render call. + +**Cell colour — `congestionColor(value, maxValue)`:** +```js +function congestionColor(value, maxValue) { + if (value === 0 || maxValue === 0) return '#0d1117'; + const ratio = value / maxValue; + if (ratio < 0.05) return '#0d1117'; + if (ratio < 0.25) return `rgba(13,74,110,${(ratio * 4).toFixed(2)})`; + if (ratio < 0.5) return `rgba(14,165,233,${ratio.toFixed(2)})`; + if (ratio < 0.75) return `rgba(249,115,22,${ratio.toFixed(2)})`; + return `rgba(239,68,68,${ratio.toFixed(2)})`; +} +``` +`maxValue` = the maximum cell value across the entire `channelHistory` array (for consistent colour scale). + +**Empty/loading state:** When `channelHistory.length === 0`, `#wifiHeatmapGrid` shows a single placeholder div: `
Scan to populate channel history
`. + +**Security ring — `renderSecurityRing(networks)`:** + +Circumference `C = 2 * Math.PI * 15 ≈ 94.25`. Each segment is a `` element with `stroke-dasharray="${arcLen} ${C - arcLen}"` and a `stroke-dashoffset` that positions it after the previous segments. The ring starts at the top by applying `transform="rotate(-90 24 24)"` to each arc. + +Standard SVG donut-segment technique — `dashoffset` for segment N = `-(sum of all preceding arcLengths)`: + +```js +const C = 2 * Math.PI * 15; // ≈ 94.25 +const segments = [ + { label: 'WPA2', color: 'var(--accent-green)', count: wpa2 }, + { label: 'Open', color: 'var(--accent-red)', count: open }, + { label: 'WPA3', color: 'var(--accent-cyan)', count: wpa3 }, + { label: 'WEP', color: 'var(--accent-orange)',count: wep }, +]; +const total = segments.reduce((s, seg) => s + seg.count, 0) || 1; +let offset = 0; +segments.forEach(seg => { + const arcLen = (seg.count / total) * C; + // + offset += arcLen; +}); +// Worked example: total=10, WPA2=7, Open=3: +// WPA2: arcLen=66.0, dasharray="66 28.2", dashoffset="0" +// Open: arcLen=28.2, dasharray="28.2 66", dashoffset="-66" +``` + +- Centre hole: `` injected last (on top) +- Legend rows injected into `#wifiSecurityRingLegend`: coloured square + name + count + +### 5. Right Panel — Network Detail + +**`#wifiDetailDrawer` deletion:** Delete the entire `
` block (~lines 940–1000 of `index.html`). Also remove all associated CSS from `index.css`: `.wifi-detail-drawer`, `.wifi-detail-drawer.open`, `.wifi-detail-header`, `.wifi-detail-title`, `.wifi-detail-essid`, `.wifi-detail-bssid`, `.wifi-detail-close`, `.wifi-detail-content`, `.wifi-detail-grid`, `.wifi-detail-stat`. In `wifi.js`, remove `detailDrawer` from the `elements` map and remove its usage in the existing `closeDetail()` (`detailDrawer.classList.remove('open')`) since `closeDetail()` is being replaced. + +**`#wifiDetailView` inner HTML:** +```html +
+
+
+
+
+ +
+
+ Signal + +
+
+
+
+
+ +
+
Channel
+
Band
+
Security
+
Cipher
+
Clients
+
First Seen
+
Vendor
+
+ + + + +
+ + + +
+
+``` + +**Signal bar fill width:** Same formula as table rows — `pct = Math.max(0, Math.min(100, (rssi + 100) / 80 * 100))`. + +**Show/hide:** +- `WiFiMode.selectNetwork(bssid)`: hides `#wifiHeatmapView`, shows `#wifiDetailView`, sets `#wifiRightPanelTitle` text to "Network Detail", shows `#wifiDetailBackBtn`, stores `selectedBssid = bssid` +- `WiFiMode.closeDetail()`: reverses — shows heatmap, hides detail, restores title to "Channel Heatmap", hides back button, sets `selectedBssid = null` + +**"← Back" vs "Close":** Both call `WiFiMode.closeDetail()`. "← Back" is in the panel header (always reachable at top); "Close" is at the bottom of the detail body for users who scrolled down. + +**`wifiClientCountBadge`** is preserved inside `#wifiDetailClientList` — no changes to the client list sub-panel. ## File Changes | File | Change | |---|---| -| `static/css/index.css` | Update/add WiFi section CSS (rows, radar, heatmap, detail panel, status bar) | -| `templates/index.html` | Update WiFi layout HTML structure | -| `static/js/modes/wifi.js` | Update `renderNetworks()`, add heatmap renderer, radar dot positioning, detail panel logic | - -No new files needed. No backend changes. +| `static/css/index.css` | Update WiFi section CSS (~line 3515): add `.wifi-scan-indicator`, `.wifi-scan-dot` + keyframes; replace table CSS with `.wifi-network-list`, `.network-row`, `.row-top`, `.row-bottom`, `.signal-bar-*`, `.badge.*`, `.wifi-sort-*`; add `.wifi-radar-sweep` + `@keyframes wifi-radar-rotate`; replace channel/security section CSS with `.wifi-heatmap-*`, `.wifi-security-ring-*`, `.wifi-analysis-panel-header`, `.wifi-detail-back-btn`; add `.wifi-detail-inner`, `.wifi-detail-*` | +| `templates/index.html` | WiFi section (~line 820): replace `
` with `
`, add sort controls to header, add `#wifiOpenCount` to status bar, replace `#wifiScanStatus` with `#wifiScanIndicator`, replace right panel contents with `#wifiHeatmapView` / `#wifiDetailView`, add `#wifiRightPanelTitle` + `#wifiDetailBackBtn` to panel header, inline radar SVG shell (static rings + empty dot group), remove `#wifiDetailDrawer` | +| `static/js/modes/wifi.js` | Update `elements` map (remove `networkTable`, `networkTableBody`, `detailDrawer`, `wpa3Count`, `wpa2Count`, `wepCount`, `openCount`; add `networkList`, `openCount` → `wifiOpenCount`, `scanIndicator`, `heatmapGrid`, `heatmapCount`, `securityRingSvg`, `securityRingLegend`, `heatmapView`, `detailView`, `rightPanelTitle`, `detailBackBtn`, `detailSignalFill`); update `renderNetworks()` (div rows, filter + sort, persistence of `selectedBssid`); update `updateScanningState()`; add `renderRadar(networks)`; add `renderHeatmap()`; add `renderSecurityRing(networks)`; add `selectNetwork(bssid)` / `closeDetail()`; remove `th[data-sort]` listener | +| `templates/partials/modes/wifi.html` | No changes — sidebar out of scope | ## Out of Scope -- WiFi locate mode (separate mode, untouched) -- Sidebar panel (signal source, scan settings, etc.) — untouched -- Attack options, handshake capture — untouched +- WiFi locate mode (separate) +- Sidebar panel (signal source, scan settings, attack options, handshake capture) - Mobile/responsive layout changes -- 5 GHz channel heatmap data (tab exists, data hookup is a follow-on) +- 5 GHz channel heatmap data population +- Any backend / route changes