# STEAM Security Design System A design system for the **STEAM Security Dashboard** — a self-hosted vulnerability management workbench used by the NTS-AEO-STEAM and NTS-AEO-ACCESS-ENG business units. This repo captures the visual language, content patterns, tokens, and UI kit needed to extend or rebuild the product without drifting from its established look. ## What the product is The STEAM Security Dashboard centralises: - **CVE tracking** — searchable, filterable, vendor-aware CVE list with NVD auto-fill, document attachment, and group-based ownership - **Ivanti / RiskSense host findings** — live remediation queue with FP / Archer / CARD workflows, inline editing, per-finding notes, and a personal "Ivanti Queue" staging list - **AEO compliance posture** — weekly xlsx upload with drift detection, diff preview, per-team metric health cards, device-level violation tracking, and timestamped notes - **Archer EXC tickets** — risk-acceptance ticket tracking linked to CVE / vendor pairs - **Knowledge base** — internal document library (PDF, Markdown, Office, etc.) for runbooks, advisories, and policies - **Admin panel** — user / group management, audit log, system info — all gated behind an Admin group Four user groups (`Admin`, `Standard_User`, `Leadership`, `Read_Only`) define every permission boundary, and every state-changing action is audit-logged. ## The 6 pages 1. **Home / Dashboard** — CVE list, filters, calendar widget for due dates 2. **Reporting** — Ivanti host findings, charts, queue, export 3. **Compliance** — AEO posture, metric health cards, device drill-in 4. **Knowledge Base** — document library 5. **Exports** — bulk export tools (group-gated) 6. **Admin Panel** — user management, audit log, system info (Admin only) ## Sources - **Codebase:** `https://vulcan.apophisnetworking.net/jramos/cve-dashboard` (Gitea, master branch). Auth required; raw file fetch is gated. The repo's own `README.md` (fetched via the source viewer) is the most accurate functional spec we had access to and is the basis for this system. - **Existing design ref:** `DESIGN_SYSTEM.md` (290 lines, in-repo) — referenced in the audit but not directly accessible from the host. - **Component audit** provided in the project brief: 29 components, 5 primitives, 14 composites, 5 pages, 1 context provider. - **Stack:** React 19, lucide-react, recharts, react-markdown + rehype-sanitize, mermaid, xlsx. Backend Express 5 / SQLite3. ## Index — what's in this folder | Path | What it is | |---|---| | `README.md` | This file — context, content + visual foundations, iconography | | `SKILL.md` | Agent Skill manifest for Claude Code compatibility | | `colors_and_type.css` | Source-of-truth tokens — color, type, spacing, radii, elevation | | `fonts/` | Font references (Outfit + JetBrains Mono via Google Fonts CDN) | | `assets/` | Logo mark, brand SVGs, severity icons | | `preview/` | Design System tab cards — registered as assets | | `ui_kits/cve-dashboard/` | High-fidelity recreation of the dashboard, focused on Knowledge Base | --- ## CONTENT FUNDAMENTALS The product is a tactical operations console for security engineers. Copy is dense, terse, and assumes a reader who already knows what a CVE, EXC ticket, FP workflow, and BU filter are. There is no marketing voice, no onboarding nudges, and no exclamation marks. ### Voice & tone - **Operational, not editorial.** Buttons say "Sync", "Confirm Upload", "Reconcile Config", "Add to Queue". Never "Let's get started" or "You're all set". - **Imperative for actions, declarative for state.** "Save", "Delete", "Hide Selected" — never "Saving your changes…" with three dots and a heart. - **No emoji.** Status is communicated through colour-coded badges and short text labels. - **Title Case for navigation and headers**, Sentence case for body and inline labels. Tabs and buttons: `User Management`, `Audit Log`, `System Info`. Helper text: `Filter tickets by CVE ID, vendor, or status.` ### Person & address - **Second-person sparingly** — only when the system is talking *about* the user's data: "your login", "your filtered view", "your queue". Never "Welcome back, {name}". - **First-person plural never.** No "We've updated" or "Let us know". - **Errors are direct, no apology.** "SESSION_SECRET environment variable must be set." "Login rate limited — wait 15 minutes." Never "Oops! Something went wrong." ### Casing - **CVE IDs:** uppercase with hyphen — `CVE-2024-12345`. Validated against `/^CVE-\d{4}-\d{4,}$/`. - **EXC numbers:** uppercase — `EXC-12345`. Validated `/^EXC-\d+$/`. - **Severity labels:** Title Case — `Critical`, `High`, `Medium`, `Low`. Status labels: `Open`, `Addressed`, `In Progress`, `Resolved`. - **Workflow state badges:** SHOUT CASE for SLA states only — `OVERDUE`, `AT_RISK`, `WITHIN_SLA`. Everything else is Title Case. - **Group names:** snake_case in code (`Standard_User`), Title Case in UI (`Standard User`). ### Density and units - Numerical metrics are bare integers ("12 findings", "47 devices"). Percentages always carry the % sign with no space. - Dates are explicit, no relative time except "Last sync: 4h ago" patterns. - Column headers are short — `Host`, `IP Address`, `DNS`, `BU`, `SLA` — never `Host Name (Editable)`. ### Specific copy conventions seen in product - "— empty —" as a filter option for empty cells - "Hidden (N)" pattern for counted UI states - "+N" badge for overflow (e.g., 2 CVEs shown, "+5" badge) - "↻" revert glyph next to overridden cells, with a small amber dot ● for the overridden state - Tooltips appear after a 300ms delay and are session-cached - "View in Reporting →" inline link pattern with a literal arrow ### What NOT to write - No motivational copy ("Great work!", "You're crushing it") - No question-mark headlines ("Need help?") - No marketing CTAs ("Upgrade now", "Try premium") - No mascot or persona — the system is the system --- ## VISUAL FOUNDATIONS The dashboard reads as a **dark tactical intelligence console** — slate / graphite backgrounds, sky-blue as the primary accent and ambient glow, severity colours used like signal flags, animated pulse-glow status dots, and information density prioritised over breathing room. The aesthetic is closer to a SOC / NOC mission display than to a flat enterprise SaaS. ### Colour vibe - **Dark slate base.** `#0F172A` (deep slate) for the page, `#1E293B` for surfaces, `#334155` for elevated surfaces and borders. Almost black, never pure black. The cool tone is consistent — no warm shadows. - **Sky blue is the brand accent** — `#38BDF8` is the primary action / link / focused state colour. It appears in buttons, active nav items, link text, and the "create" badge in the audit log. - **Severity is a fixed semantic system** — the colours below MUST mean what they mean and nothing else. - Critical → Red `#EF4444` - High → Amber `#F59E0B` - Medium → Sky `#38BDF8` - Low → Emerald `#10B981` - **Neutral text scale** — `#F1F5F9` (primary fg), `#CBD5E1` (secondary), `#94A3B8` (muted), `#64748B` (placeholder / disabled). Never pure white. - **Group badges** — Admin red, Standard_User accent blue, Leadership amber, Read_Only muted grey. The same severity language reappears here for status urgency. ### Typography - **Outfit** for all UI (headers, body, buttons, navigation). Geometric sans, friendly but precise; weights 400 / 500 / 600 / 700. - **JetBrains Mono** for *data* — CVE IDs, IP addresses, hostnames, EXC numbers, finding IDs, code blocks. Anything you'd grep for. - **Scale** is compact. Page titles 24–28px / 600 weight; section headers 16–18px / 600; body 14px / 400; data table cells 13px / 400 mono. Line-height stays tight (1.4) to preserve density. ### Spacing - **4 / 8 / 12 / 16 / 24 / 32 / 48** — a roughly 4px grid. Cards have 16–20px internal padding; rows in dense tables have 8–10px vertical padding; modals have 24px internal padding. - **Section gaps** are 24–32px. Between siblings, 12–16px is the dominant rhythm. ### Backgrounds - **No imagery.** No hero photographs, illustrations, or marketing visuals. The page is solid `#0F172A`. - **Subtle sky-blue grid is allowed.** A 20×20px grid at `rgba(14,165,233,0.025)` (`.grid-bg` utility) sits behind hero / empty regions. It is barely visible and never dominates. - **Surfaces use diagonal gradients**, not flat fills — `linear-gradient(135deg, rgba(30,41,59,0.95), rgba(51,65,85,0.9))` is the canonical card surface. ### Cards and surfaces (`intel-card`) - **Background:** diagonal gradient `135deg, rgba(30,41,59,0.95) 0%, rgba(51,65,85,0.9) 50%, rgba(30,41,59,0.95) 100%` - **Border:** 1.5px solid `rgba(14,165,233,0.30)` — sky-blue at low alpha, not slate grey - **Radius:** 8px (default) / 12px (modals) / 4px (chips) - **Internal padding:** 16–20px - **Resting shadow:** `0 4px 12px rgba(0,0,0,0.4)` + `0 2px 6px rgba(0,0,0,0.3)` + inset `0 1px 0 rgba(14,165,233,0.10)` (sky highlight on top edge) - **Hover:** border opacity climbs to `0.50`, the card lifts `translateY(-2px)`, and gains a `0 0 30px rgba(14,165,233,0.10)` ambient glow. A `::after` shimmer sweeps left→right on entry. - **Stat cards** add a 2px `linear-gradient(90deg, transparent, #0EA5E9, transparent)` rail on the top edge. ### Borders - **Sky-blue at low alpha** is the dominant border treatment — `rgba(14,165,233,0.15)` for subtle dividers, `0.25` for default, `0.40` for strong / hover. Pure slate `#334155` borders appear only on tables and inputs at rest. - Focus state: 2px sky-blue ring `0 0 0 2px rgba(14,165,233,0.15)` plus the border swaps to solid `#0EA5E9`. - Severity-tinted left borders are NOT a pattern — colour is carried by badges, dots, and glow. ### Animation - **Pulse-glow on status dots is canonical.** Every severity / SLA badge has an 8px circle that pulses `box-shadow: 0 0 5px → 15px currentColor` on a 2s ease-in-out loop (`@keyframes pulse-glow`). - **Card hover lift** is 300ms cubic-bezier(0.4,0,0.2,1) with a `::after` shimmer sweep — `linear-gradient(90deg, transparent, rgba(14,165,233,0.08), transparent)` translating from `left:-100%` to `100%` over 500ms. - **Buttons** have a circular ripple `::before` that scales from 0×0 to 300×300 on hover (500ms). - Modal entry: 200ms fade + slight translate. Slide-out panels: 240ms ease-out from the right. - Tooltips have a deliberate **300ms hover delay** before appearing. - A `.scan-line` utility (3s loop) is available for hero / loading affordances — used sparingly. ### Hover states - **Cards** lift `-2px`, border opacity climbs from `0.30` → `0.50`, and a sky-blue ambient glow `0 0 30px rgba(14,165,233,0.10)` appears. - **Buttons** brighten their gradient fill from `0.15/0.10` to `0.25/0.20` alpha, gain a `0 0 20px` brand-color glow, and lift `-1px`. - **Text links** lighten from `#38BDF8` to `#7DD3FC` and the bottom border brightens to match. - **Table rows** get a `rgba(0,217,255,0.06)` wash plus `0 2px 8px rgba(0,217,255,0.10)` sub-shadow. - The audit notes a current anti-pattern: hover states implemented via `onMouseEnter` / `onMouseLeave` JS handlers. The design system standard is **CSS `:hover` pseudo-classes** — JS hover is a defect to migrate away from. ### Press / active states - Button: shifts to `#0EA5E9` (slightly darker than hover), no shrink, no shadow change. Press is a colour signal, not a physics signal. - Rows / interactive cards: `#475569` background on `:active`. ### Transparency & blur - Modal backdrops: `rgba(10, 14, 39, 0.97)` with `backdrop-filter: blur(12px)`. The blur is heavy and the backdrop is near-opaque — modals fully obscure the background. - Tooltips: gradient `linear-gradient(135deg, #334155, #475569)` with a sky-blue border and `0 4px 12px` + `0 0 16px rgba(14,165,233,0.15)` glow. - Inputs: translucent `rgba(30,41,59,0.6)` background with `inset 0 2px 4px rgba(0,0,0,0.2)` for a subtle recessed feel. ### Inner / outer shadows - **Both are used.** Cards combine outer drop + inner sky-blue highlight: `0 4px 12px rgba(0,0,0,0.4), inset 0 1px 0 rgba(14,165,233,0.10)`. - **Inputs are recessed** — `inset 0 2px 4px rgba(0,0,0,0.2)` plus a `0 1px 0 rgba(255,255,255,0.03)` top sheen. - **Document items** (within KB / vendor lists) use a stronger inset `inset 0 2px 4px rgba(0,0,0,0.3)` to read as nested / pressed-in. - Modals lift on `0 20px 60px rgba(0,0,0,0.6) + 0 10px 30px rgba(14,165,233,0.10)` — heavier than most enterprise products, but the brand glow is the signature. ### Layout rules - **Full-width fluid** above 1024px — the dashboard fills the viewport, with content max-width capping at ~1600px on very wide displays. - **Top app bar is fixed** — height 56px, contains brand mark, page nav, and `UserMenu`. Sits above all content with `z-index: 50`. - **Side nav drawer (NavDrawer)** slides from the left on icon click; it does *not* push content (overlay model). - **Slide-out panels** (Atlas, Compliance Detail) come from the right, ~480px wide on desktop, full-width on narrow viewports. - **Modals** are centered, max-width 640px (small) or 960px (wizard / upload), with the standard backdrop. ### Severity language This is the most important visual rule in the product. Severity badges use the **`status-badge` pattern**: - 2px solid border at `0.6` alpha - Diagonal gradient fill at `0.20 / 0.15` alpha - **Lighter text** for legibility — `#FCA5A5` (critical), `#FCD34D` (high), `#7DD3FC` (medium), `#6EE7B7` (low) — not the raw severity colour - Text-shadow `0 0 8px` brand-color at `0.4` alpha - 8px filled circle dot with a pulsing `box-shadow: 0 0 12px / 0 0 6px` glow on a 2s loop - `0 4px 8px rgba(0,0,0,0.4)` outer shadow - **Always JetBrains Mono, uppercase, 0.5px letter-spacing** Secondary references can use simpler tinted pills (`rgba(brand,0.12)` background + brand text, no border, no glow). Single coloured dots `●` next to numeric scores are also valid. The colour-to-severity mapping is fixed across every component. ### Headings — the brand glow Page titles and section headers are **JetBrains Mono, uppercase, sky-blue `#38BDF8`**, with `text-shadow: 0 0 16px rgba(14,165,233,0.30), 0 0 32px rgba(14,165,233,0.15)`. This is the most identifiable single signal in the product — every page header reads as a glowing terminal title. Outfit is reserved for body, helper, and table cell text. The Knowledge Base markdown viewer continues this language: `h1` sky-blue, `h2` emerald, `h3` amber — a deliberate severity-coloured hierarchy. --- ## ICONOGRAPHY The product uses **lucide-react** as its sole icon system. Lucide is a 1.5px-stroke, geometric, open-source icon set — clean, restrained, and perfectly aligned with the dark tactical aesthetic. ### Rules - **All icons are line / stroke style** — never filled glyphs (with one exception: the calendar's red due-date dot is a filled circle, but it's a status indicator, not an icon). - **Stroke width:** 1.5–2px (lucide default). 1.5px on small icons (≤16px), 2px on larger icons. - **Sizes:** 14px (inline with text), 16px (default UI), 20px (nav items, prominent buttons), 24px (page-header icons). - **Colour:** inherits `currentColor` — text-foreground for default, `#38BDF8` for active / accent, severity colours when used as a status indicator. - **No emoji anywhere.** Status, severity, and category use icons + colour; never `🔴` or `⚠️`. - **No unicode-as-icon shortcuts** beyond `●` (status dot), `↻` (revert / cycle), `↱` (redirect), `⊙` (filter handle), `→` (inline link), `+N` (count badge). These are part of the typography, not stand-ins for missing icons. ### Brand mark The product has no published logo file in the repo (the audit references `AtlasIcon` as a custom SVG brand icon — Atlas appears to be the action-plan integration, not the dashboard's own brand). For this design system the brand mark is a **typographic stack**: `STEAM` in Outfit 700 with a sky-blue underline accent and a small shield glyph (lucide `Shield`) to the left. See `assets/logo.svg` and `assets/atlas-shield.svg`. ### Substitutions flagged - **Atlas action-plan brand icon** is recreated as a generic shield (lucide `Shield`) tinted sky-blue. **If you have the real `AtlasIcon` SVG, please attach it** — the in-product version is custom and not available from the repo URL. - Fonts (Outfit, JetBrains Mono) load from Google Fonts CDN. **If you need offline font files, attach the woff2s** and we'll bundle them into `fonts/`. ### Icons used per page (from README) - **Home:** Calendar (CalendarWidget), Search, Filter, Plus, Upload, Edit, Trash, X (close) - **Reporting:** RefreshCw (Sync), Eye / EyeOff (row visibility), Check, Filter (⊙ in column header), Columns, Download (Export), MoreHorizontal - **Compliance:** Upload, AlertTriangle (drift breaking), AlertCircle (drift silent-miss), Info, ChevronRight, FileText - **Knowledge Base:** FileText, FilePlus, Folder, Download, Eye - **Admin:** Users, ScrollText (audit log), Activity (system info), Shield (admin badge) - **Universal:** ChevronDown, ChevronUp, Check, X, Loader, ExternalLink When picking an icon, prefer the lucide-react name from this list before introducing a new one. --- ## UI Kits | Kit | Path | What it covers | |---|---|---| | `cve-dashboard` | `ui_kits/cve-dashboard/` | App shell (top bar, nav drawer, user menu), Knowledge Base page + viewer, primitives (Button, Badge, Pill, Input, Select, Modal shell, SlideOutPanel, DataTable, GroupBadge, SeverityBadge, EmptyState, LoadingState) | The Knowledge Base page is the focused recreation. Other surfaces (Reporting, Compliance, Admin) are intentionally not built out — the primitives + shell are sufficient to compose them. --- ## How to use this system 1. **Tokens first.** Import `colors_and_type.css` into the root of any HTML file. All colour, type, radius, shadow, and spacing decisions should pull from these CSS custom properties. 2. **Pick a primitive before inventing.** Severity badges, group badges, status pills, table row, modal shell, slide-out panel — they all live in `ui_kits/cve-dashboard/`. 3. **Match the density.** When in doubt, tighter is more on-brand than airier. 4. **Lucide for icons.** Use the lucide-react CDN or copy individual SVGs from the lucide site. Do not draw your own. 5. **No emoji, no gradients, no illustration, no marketing copy.** The product is a console.