BitFire surfaces traffic that matters — blocked attacks, suspicious bots, geo anomalies — without drowning operators in chartjunk. The interface is dense, monospaced, and quiet. Color is reserved for status. Motion is reserved for life.
Five rules that everything else descends from.
Two families. Inter for prose and labels, JetBrains Mono for everything that's data: URLs, IPs, codes, fingerprints, timestamps.
Reserved for marketing, docs, and section heads in admin views. The product proper rarely goes above 14px.
13px is the workhorse. Most data rows live here. Below 11px is reserved for labels and meta.
JetBrains Mono with font-feature-settings: "ss01", "zero", "tnum". The slashed zero and tabular numbers are non-negotiable — operators scan IP addresses by column.
Every color is defined in oklch() for perceptual uniformity. Each status color has a triplet — text, background, border — so any pill works on any surface without contrast hacks.
A four-rung stack. Page is the lowest, raised is the highest. Selected rows use the accent-tinted variant.
Four levels of ink, never pure black or pure white. Use --fg-dim for secondary content, --fg-dimmer for meta, --fg-muted for placeholders.
Five families. Each has --{name}, --{name}-bg, and --{name}-border. The brand accent (ember) is also a "status" — it marks the live, the now, the user's anchor.
| Color | Meaning | Used on |
|---|---|---|
| red | Blocked, denied, malicious, dangerous | WAF rules, IP rep, blocked rows, DELETE method, exclude chips |
| amber | Rate limited, throttled, datacenter origin, tools (curl, requests) | Rate-limit pill, mobile origin, hosting origin |
| green | Allowed, ok, healthy, residential | OK pill, env "live" indicator |
| blue | Informational, neutral signal, good bots | 3xx redirect, POST method, CDN origin, include chips, googlebot |
| violet | Datacenter, infrastructure, PUT/PATCH | Datacenter origin pill, PUT method chip |
| ember | Brand, accent, user anchor | Brand mark, live count dot, focus rings, "you are here" |
A 4px grid for component padding, 8px for layout gaps. Radii are subtle: 3px for chips, 5–6px for cards, 999px for pills.
All separation is hairline. We never use shadows for boundaries — only elevation. --border is the default, --border-strong for hover states, --border-subtle for nested or alternating rows.
The vocabulary. Each piece has a single purpose and a single semantic color rule.
The status of a single request. Always rounded, always paired with a colored dot. The dot is decorative — the label carries meaning.
HTTP method, inline before the URL. Default (GET, HEAD, OPTIONS) is neutral; verbs that mutate state get color.
Where the IP came from. Five canonical buckets. Color tells you risk profile at a glance — datacenter (violet) and proxy (red) are inherently more suspicious than residential.
User-agent classification. browser is the only neutral; everything else is signal.
A live filter. Include chips are blue, exclude chips are red. The leading button toggles AND/OR; the second toggles include/exclude; the trailing × removes.
The "everything is fine" indicator. The dot pulses on a 2.5s breath. Only one of these per page — it's the user's anchor that the system is responsive.
The default action. No fill until hover. Used for everything in the chrome — toolbar, filter bar, drawer header.
Sun/moon icon in a pill. The icon swaps based on the active theme — moon when in day (click to go dark), sun when in dark (click to go light).
How components compose into functional pieces of UI.
The atomic unit of the dashboard. Four cells: request, where, who, result. Each cell has a primary line (mono) and a secondary line (smaller, dimmer). A 3px left rail color-codes the row's status — invisible for OK rows, red for blocked, blue for redirect, amber for missing.
A 3px colored stripe down the left edge of each row. Lets you scan vertical patterns — a column of red is an attack burst, a column of blue is a redirect chain. Invisible for OK to keep the rail meaningful: only signal, never noise.
For blocked / rate-limited rows the result cell stacks three lines, in descending priority: the pill (status), the reason (human, sans, tinted), and the sub (technical, mono, dim). Operators read top-down; juniors get plain English first, seniors can drop to the rule code.
One layout, two skins. The day theme is the source of truth; bitfire-lite-dark.css is a thin overlay that flips token values under body.theme-dark and adds a few dark-native flourishes (glow on the live dot, alternating row stripe, inset filter input).
Both stylesheets load. Day owns layout; dark only flips tokens via body.theme-dark. Switching is instant — no FOUC, no layout shift. Preference is persisted to localStorage[bitfire.theme].
Every blocked request carries a numeric rule code. The first digit groups by category. The UI translates these to one-line human-readable strings via BLOCK_REASONS in bitfire-lite.js.
| Code | Group | Reason | Result |
|---|---|---|---|
| 101 | 1xx · Rate | Rate limit exceeded | rate limited |
| 102 | 1xx · Rate | Burst limit exceeded | rate limited |
| 220 | 2xx · Integrity | Invalid request signature | blocked |
| 221 | 2xx · Integrity | Tampered headers | blocked |
| 310 | 3xx · Geo | Country blocked by policy | blocked |
| 311 | 3xx · Geo | Region blocked by policy | blocked |
| 420 | 4xx · Challenge | Failed JS challenge | blocked |
| 421 | 4xx · Challenge | Failed CAPTCHA | blocked |
| 510 | 5xx · Behavior | Bot score above threshold | blocked |
| 511 | 5xx · Behavior | Known bot, no allow-list | blocked |
| 601 | 6xx · WAF | SQL injection attempt | blocked |
| 602 | 6xx · WAF | Cross-site scripting attempt | blocked |
| 603 | 6xx · WAF | Path traversal attempt | blocked |
| 604 | 6xx · WAF | Command injection attempt | blocked |
| 701 | 7xx · IP rep | IP reputation: malicious | blocked |
| 702 | 7xx · IP rep | IP on threat feed | blocked |
| 810 | 8xx · Manual | Manual block by operator | blocked |