feat(ui): enhance light theme contrast and animations

This commit is contained in:
kaitranntt
2025-12-16 07:06:06 -05:00
parent 994bd7765a
commit 197848a71b
5 changed files with 117 additions and 67 deletions
+29 -15
View File
@@ -252,8 +252,8 @@ export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
if (newPulsing.size > 0) {
setPulsingAccounts(newPulsing);
// Clear pulse after animation
const timer = setTimeout(() => setPulsingAccounts(new Set()), 600);
// Clear pulse after animation completes (match CSS animation duration)
const timer = setTimeout(() => setPulsingAccounts(new Set()), 2000);
return () => clearTimeout(timer);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -429,15 +429,29 @@ export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
/>
{/* Pulse layer - only shows when new activity detected */}
{isPulsing && (
<path
d={d}
fill="none"
stroke={account.color}
strokeWidth={strokeWidth * 1.5}
strokeLinecap="round"
filter="url(#flow-glow)"
className="animate-request-pulse"
/>
<>
{/* Glowing path pulse */}
<path
d={d}
fill="none"
stroke={account.color}
strokeWidth={strokeWidth * 2}
strokeLinecap="round"
filter="url(#flow-glow)"
className="animate-request-pulse"
/>
{/* Traveling dot along path */}
<circle
r={6}
fill={account.color}
filter="url(#flow-glow)"
style={{
offsetPath: `path('${d}')`,
offsetDistance: '0%',
animation: 'travel-dot 1.5s cubic-bezier(0.4, 0, 0.2, 1) forwards',
}}
/>
</>
)}
</g>
);
@@ -731,20 +745,20 @@ export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
{/* Stats */}
<div className="bg-muted/30 dark:bg-zinc-900/50 rounded-lg p-3 border border-border">
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground mb-1">
<CheckCircle2 className="w-3 h-3 text-emerald-500" />
<CheckCircle2 className="w-3 h-3 text-emerald-700 dark:text-emerald-500" />
<span>SUCCESSFUL</span>
</div>
<div className="text-xl font-mono text-emerald-500 tracking-tighter">
<div className="text-xl font-mono text-emerald-700 dark:text-emerald-500 tracking-tighter">
{selectedAccount.successCount.toLocaleString()}
</div>
</div>
<div className="bg-muted/30 dark:bg-zinc-900/50 rounded-lg p-3 border border-border">
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground mb-1">
<XCircle className="w-3 h-3 text-red-500" />
<XCircle className="w-3 h-3 text-red-700 dark:text-red-500" />
<span>FAILED</span>
</div>
<div className="text-xl font-mono text-red-500 tracking-tighter">
<div className="text-xl font-mono text-red-700 dark:text-red-500 tracking-tighter">
{selectedAccount.failureCount.toLocaleString()}
</div>
</div>
+16 -16
View File
@@ -48,18 +48,18 @@ function cleanEmail(email: string): string {
return email.replace(/@(gmail|yahoo|hotmail|outlook|icloud)\.com$/i, '');
}
// Vibrant colors for account segments
// Vibrant colors for account segments - darker for light theme contrast
const ACCOUNT_COLORS = [
'#277da1', // Cerulean
'#43aa8b', // Seaweed
'#f9c74f', // Tuscan Sun
'#f94144', // Strawberry
'#f3722c', // Pumpkin
'#90be6d', // Willow
'#577590', // Blue Slate
'#f8961e', // Carrot
'#4d908e', // Dark Cyan
'#a78bfa', // Purple
'#1e6091', // Deep Cerulean (was #277da1)
'#2d8a6e', // Deep Seaweed (was #43aa8b)
'#d4a012', // Dark Tuscan (was #f9c74f)
'#c92a2d', // Deep Strawberry (was #f94144)
'#c45a1a', // Deep Pumpkin (was #f3722c)
'#6b9c4d', // Dark Willow (was #90be6d)
'#3d5a73', // Deep Blue Slate (was #577590)
'#cc7614', // Dark Carrot (was #f8961e)
'#3a7371', // Deep Cyan (was #4d908e)
'#7c5fc4', // Deep Purple (was #a78bfa)
];
/** Enhanced live pulse indicator with multi-ring animation */
@@ -94,15 +94,15 @@ function InlineStatsBadge({ success, failure }: { success: number; failure: numb
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-0.5">
<CheckCircle2 className="w-3 h-3 text-emerald-500" />
<span className="text-[10px] font-mono font-medium text-emerald-500">
<CheckCircle2 className="w-3 h-3 text-emerald-700 dark:text-emerald-500" />
<span className="text-[10px] font-mono font-medium text-emerald-700 dark:text-emerald-500">
{success.toLocaleString()}
</span>
</div>
{failure > 0 && (
<div className="flex items-center gap-0.5">
<XCircle className="w-3 h-3 text-red-500" />
<span className="text-[10px] font-mono font-medium text-red-500">
<XCircle className="w-3 h-3 text-red-700 dark:text-red-500" />
<span className="text-[10px] font-mono font-medium text-red-700 dark:text-red-500">
{failure.toLocaleString()}
</span>
</div>
@@ -308,7 +308,7 @@ export function AuthMonitor() {
</div>
{/* Flow Visualization */}
<div className="relative min-h-[320px] overflow-hidden">
<div className="relative overflow-hidden">
{selectedProviderData ? (
// Account-level flow view
<AccountFlowViz
+64 -28
View File
@@ -5,46 +5,46 @@
:root {
/* CCS Brand Colors - Calculated from Crail, Pampas, White, Cloudy */
/* Primary: Neutral (Dark Warm Grey) - Demoted Crail from Primary */
--primary: oklch(0.2 0.02 40);
--primary: oklch(0.15 0.02 40); /* Darker for better contrast */
--primary-foreground: oklch(0.9635 0.0067 97.35); /* Pampas */
--secondary: oklch(0.9 0.01 95); /* Light Cloudy */
--secondary-foreground: oklch(0.2 0.02 40);
--secondary: oklch(0.88 0.012 95); /* Slightly darker */
--secondary-foreground: oklch(0.15 0.02 40);
/* Accent: Crail (Moved from Primary) */
--accent: oklch(0.5971 0.1352 39.87); /* Crail */
--accent: oklch(0.52 0.15 39.87); /* Crail - slightly darker */
--accent-foreground: oklch(1 0 0); /* White */
--background: oklch(0.9635 0.0067 97.35); /* Pampas */
--foreground: oklch(0.2 0.02 40); /* Dark warm grey */
--foreground: oklch(0.15 0.02 40); /* Darker for better readability */
--muted: oklch(0.92 0.01 95);
--muted-foreground: oklch(0.55 0.02 40);
--muted: oklch(0.9 0.012 95);
--muted-foreground: oklch(0.4 0.025 40); /* Much darker: 0.55 -> 0.40 */
--border: oklch(0.85 0.015 91.6); /* Based on Cloudy */
--input: oklch(0.85 0.015 91.6);
--border: oklch(0.78 0.02 91.6); /* Darker: 0.85 -> 0.78 */
--input: oklch(0.78 0.02 91.6);
--ring: oklch(0.2 0.02 40); /* Match primary */
--ring: oklch(0.15 0.02 40); /* Match primary */
--radius: 0.5rem;
--popover: oklch(0.9635 0.0067 97.35); /* Match background */
--popover-foreground: oklch(0.2 0.02 40); /* Match foreground */
--popover: oklch(0.98 0.005 97.35); /* Slight depth */
--popover-foreground: oklch(0.15 0.02 40); /* Match foreground */
--card: oklch(0.9635 0.0067 97.35); /* Match background */
--card-foreground: oklch(0.2 0.02 40); /* Match foreground */
--card: oklch(0.98 0.005 97.35); /* Slight depth from background */
--card-foreground: oklch(0.15 0.02 40); /* Match foreground */
--destructive: oklch(0.577 0.245 27.325); /* Red 600 */
--destructive-foreground: oklch(0.9635 0.0067 97.35); /* White */
--destructive: oklch(0.5 0.22 27); /* Darker red for visibility */
--destructive-foreground: oklch(0.98 0.005 97.35); /* White */
/* Sidebar colors - Light */
--sidebar: oklch(0.9635 0.0067 97.35); /* Pampas */
--sidebar-foreground: oklch(0.2 0.02 40);
--sidebar-primary: oklch(0.2 0.02 40); /* Neutral */
--sidebar-foreground: oklch(0.15 0.02 40);
--sidebar-primary: oklch(0.15 0.02 40); /* Neutral - darker */
--sidebar-primary-foreground: oklch(0.9635 0.0067 97.35);
--sidebar-accent: oklch(0.5971 0.1352 39.87); /* Crail */
--sidebar-accent: oklch(0.52 0.15 39.87); /* Crail - darker */
--sidebar-accent-foreground: oklch(1 0 0);
--sidebar-border: oklch(0.85 0.015 91.6);
--sidebar-ring: oklch(0.2 0.02 40);
--sidebar-border: oklch(0.78 0.02 91.6); /* Darker */
--sidebar-ring: oklch(0.15 0.02 40);
}
.dark {
@@ -203,25 +203,57 @@
/* Flow visualization animations */
@keyframes request-pulse {
0% {
stroke-opacity: 0.9;
stroke-opacity: 1;
stroke-width: inherit;
filter: drop-shadow(0 0 2px currentColor);
}
50% {
stroke-opacity: 0.6;
30% {
stroke-opacity: 0.9;
filter: drop-shadow(0 0 8px currentColor);
}
60% {
stroke-opacity: 0.5;
stroke-width: calc(inherit * 1.5);
filter: drop-shadow(0 0 4px currentColor);
}
100% {
stroke-opacity: 0;
stroke-width: calc(inherit * 2);
filter: none;
}
}
/* Traveling dot along path */
@keyframes travel-dot {
0% {
offset-distance: 0%;
opacity: 1;
}
80% {
opacity: 1;
}
100% {
offset-distance: 100%;
opacity: 0;
}
}
@keyframes glow-pulse {
0%,
100% {
box-shadow: 0 0 0 0 var(--glow-color, rgba(16, 185, 129, 0.2));
box-shadow: 0 0 0 0 var(--glow-color, rgba(16, 185, 129, 0.3));
transform: scale(1);
}
25% {
box-shadow: 0 0 15px 3px var(--glow-color, rgba(16, 185, 129, 0.5));
transform: scale(1.02);
}
50% {
box-shadow: 0 0 20px 4px var(--glow-color, rgba(16, 185, 129, 0.3));
box-shadow: 0 0 25px 6px var(--glow-color, rgba(16, 185, 129, 0.4));
transform: scale(1.01);
}
75% {
box-shadow: 0 0 12px 2px var(--glow-color, rgba(16, 185, 129, 0.35));
}
}
@@ -258,11 +290,15 @@
}
.animate-request-pulse {
animation: request-pulse 0.6s ease-out forwards;
animation: request-pulse 2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.animate-glow-pulse {
animation: glow-pulse 2s ease-in-out infinite;
animation: glow-pulse 2.5s ease-in-out infinite;
}
.animate-travel-dot {
animation: travel-dot 1.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.animate-subtle-float {
+4 -4
View File
@@ -29,11 +29,11 @@ const PROVIDER_COLORS: Record<string, string> = {
qwen: '#f9c74f', // Tuscan
};
// Status colors (from Analytics Cost breakdown)
// Status colors (from Analytics Cost breakdown) - darker for light theme contrast
export const STATUS_COLORS = {
success: '#43aa8b', // Seaweed
degraded: '#e09f3e', // Ochre
failed: '#9e2a2b', // Merlot
success: '#15803d', // Green-700 (was Seaweed #43aa8b)
degraded: '#b45309', // Amber-700 (was Ochre #e09f3e)
failed: '#b91c1c', // Red-700 (was Merlot #9e2a2b)
} as const;
export function getModelColor(model: string): string {
+4 -4
View File
@@ -19,10 +19,10 @@ type StatVariant = 'default' | 'success' | 'warning' | 'error' | 'accent';
const variantStyles: Record<StatVariant, { iconBg: string; iconColor: string }> = {
default: { iconBg: 'bg-muted', iconColor: 'text-muted-foreground' },
success: { iconBg: 'bg-green-500/10', iconColor: 'text-green-600' },
warning: { iconBg: 'bg-yellow-500/10', iconColor: 'text-yellow-500' },
error: { iconBg: 'bg-red-500/10', iconColor: 'text-red-500' },
accent: { iconBg: 'bg-accent/10', iconColor: 'text-accent' },
success: { iconBg: 'bg-green-600/15', iconColor: 'text-green-700 dark:text-green-500' },
warning: { iconBg: 'bg-amber-500/15', iconColor: 'text-amber-700 dark:text-amber-400' },
error: { iconBg: 'bg-red-600/15', iconColor: 'text-red-700 dark:text-red-500' },
accent: { iconBg: 'bg-accent/15', iconColor: 'text-accent' },
};
function InlineStat({