Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions frontend/src/components/ExecutiveStatsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({
>
{/* 1. Risk Profile */}
<div className="px-6 first:pl-8">
<span className="text-xs font-bold text-white/70 uppercase tracking-[0.3em] block mb-6">Status Profile</span>
<span className="text-xs font-bold text-silver uppercase tracking-[0.3em] block mb-6">Status Profile</span>
<div className="space-y-6">
{loading ? (
<>
Expand All @@ -61,7 +61,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({
>
{riskLabel || 'Moderate'}
</span>
<p className="text-sm text-white/80 leading-relaxed font-light tracking-wide">
<p className="text-sm text-silver leading-relaxed font-light tracking-wide">
{riskNote}
</p>
</>
Expand All @@ -71,7 +71,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({

{/* 2. Critical Vulns */}
<div className="px-6">
<span className="text-xs font-bold text-white/70 uppercase tracking-[0.3em] block mb-6">Critical Vulns</span>
<span className="text-xs font-bold text-silver uppercase tracking-[0.3em] block mb-6">Critical Vulns</span>
<div className="space-y-8">
{loading ? (
<>
Expand All @@ -81,7 +81,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({
) : (
<>
<span
className={`text-8xl font-normal leading-[0.8] block ${hasCritical ? 'text-[var(--rag-red)]' : 'text-white'}`}
className={`text-8xl font-normal leading-[0.8] block ${hasCritical ? 'text-[var(--rag-red)]' : 'text-silver-bright'}`}
style={{ fontFamily: 'var(--font-display)' }}
>
{criticalCount}
Expand All @@ -96,7 +96,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({

{/* 3. Total Findings */}
<div className="px-6">
<span className="text-xs font-bold text-white/70 uppercase tracking-[0.3em] block mb-6">Total Findings</span>
<span className="text-xs font-bold text-silver uppercase tracking-[0.3em] block mb-6">Total Findings</span>
<div className="space-y-8">
{loading ? (
<>
Expand All @@ -105,7 +105,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({
</>
) : (
<>
<span className="text-8xl font-normal text-white leading-[0.8]" style={{ fontFamily: 'var(--font-display)' }}>
<span className="text-8xl font-normal text-silver-bright leading-[0.8]" style={{ fontFamily: 'var(--font-display)' }}>
{findingCount.toLocaleString()}
</span>
<span className="text-xs text-[var(--rag-green)] font-bold uppercase tracking-[0.25em] block">
Expand All @@ -118,7 +118,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({

{/* 4. Scan Activity */}
<div className="px-6 last:pr-8">
<span className="text-xs font-bold text-white/70 uppercase tracking-[0.3em] block mb-6">Scan Cycles</span>
<span className="text-xs font-bold text-silver uppercase tracking-[0.3em] block mb-6">Scan Cycles</span>
<div className="space-y-8">
{loading ? (
<>
Expand All @@ -127,7 +127,7 @@ export const ExecutiveStatsBar: React.FC<ExecutiveStatsBarProps> = ({
</>
) : (
<>
<span className="text-8xl font-normal text-white leading-[0.8]" style={{ fontFamily: 'var(--font-display)' }}>
<span className="text-8xl font-normal text-silver-bright leading-[0.8]" style={{ fontFamily: 'var(--font-display)' }}>
{scanCount.toLocaleString()}
</span>
<span className="text-xs text-[var(--rag-blue)] font-bold uppercase tracking-[0.25em] block">
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
Design Tokens
============================================ */
:root {
color-scheme: dark;

/* Core Palette - Premium SOC */
--bg-primary: #0a0b0d;
/* Deep Onyx */
Expand Down Expand Up @@ -63,12 +65,33 @@
--sidebar-expanded: 280px;
}

/* Light mode keeps the same semantic palette while swapping its surfaces and
text values. Tailwind's custom charcoal/silver utilities resolve to these
variables, so one root class updates the entire application. */
:root.theme-light {
color-scheme: light;

--bg-primary: #f4f6f8;
--bg-secondary: #ffffff;
--bg-tertiary: #e8edf2;
--bg-elevated: #dce3ea;

--accent-silver: #64748b;
--accent-silver-dim: #64748b;
--accent-silver-bright: #111827;

--text-primary: #111827;
--text-secondary: #475569;
--text-muted: #64748b;
}

/* Base Overrides */
body {
background-color: var(--bg-primary);
color: var(--accent-silver);
font-family: var(--font-sans);
text-rendering: optimizeLegibility;
transition: background-color 200ms ease, color 200ms ease;
}

@layer components {
Expand Down
17 changes: 9 additions & 8 deletions frontend/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class',
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
'charcoal-dark': '#0a0a0c',
'charcoal-dark': 'var(--bg-primary)',
charcoal: {
light: '#1d1d21',
DEFAULT: '#121214',
dark: '#0a0a0c', /* mapped for backward compatibility */
light: 'var(--bg-tertiary)',
DEFAULT: 'var(--bg-secondary)',
dark: 'var(--bg-primary)', /* mapped for backward compatibility */
},
silver: {
bright: '#f4f4f5',
DEFAULT: '#a1a1aa',
dark: '#475569',
bright: 'var(--text-primary)',
DEFAULT: 'var(--text-secondary)',
dark: 'var(--text-muted)',
},
rag: {
red: '#ef4444',
Expand All @@ -27,7 +28,7 @@ export default {
'blue-bright': '#3b82f6',
},
accent: {
silver: '#3f3f46'
silver: 'var(--accent-silver)'
}
},
fontFamily: {
Expand Down
14 changes: 14 additions & 0 deletions frontend/testing/unit/components/ThemeToggle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ describe('ThemeToggle', () => {

expect(localStorage.getItem(STORAGE_KEY)).toBe('light')
expect(button).toHaveAttribute('aria-pressed', 'false')
expect(document.documentElement).toHaveClass('theme-light')
expect(document.documentElement).not.toHaveClass('dark')
})

it('toggles from light to dark on click and persists to localStorage', async () => {
Expand All @@ -53,6 +55,18 @@ describe('ThemeToggle', () => {

expect(localStorage.getItem(STORAGE_KEY)).toBe('dark')
expect(button).toHaveAttribute('aria-pressed', 'true')
expect(document.documentElement).toHaveClass('dark')
expect(document.documentElement).not.toHaveClass('theme-light')
})

it('restores a saved light theme when the provider mounts', () => {
localStorage.setItem(STORAGE_KEY, 'light')

renderWithTheme()

expect(document.documentElement).toHaveClass('theme-light')
expect(document.documentElement).not.toHaveClass('dark')
expect(screen.getByRole('button')).toHaveAttribute('aria-pressed', 'false')
})

it('aria-label reflects the target theme, not the current one', () => {
Expand Down
Loading