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) { if (newPulsing.size > 0) {
setPulsingAccounts(newPulsing); setPulsingAccounts(newPulsing);
// Clear pulse after animation // Clear pulse after animation completes (match CSS animation duration)
const timer = setTimeout(() => setPulsingAccounts(new Set()), 600); const timer = setTimeout(() => setPulsingAccounts(new Set()), 2000);
return () => clearTimeout(timer); return () => clearTimeout(timer);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // 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 */} {/* Pulse layer - only shows when new activity detected */}
{isPulsing && ( {isPulsing && (
<path <>
d={d} {/* Glowing path pulse */}
fill="none" <path
stroke={account.color} d={d}
strokeWidth={strokeWidth * 1.5} fill="none"
strokeLinecap="round" stroke={account.color}
filter="url(#flow-glow)" strokeWidth={strokeWidth * 2}
className="animate-request-pulse" 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> </g>
); );
@@ -731,20 +745,20 @@ export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
{/* Stats */} {/* Stats */}
<div className="bg-muted/30 dark:bg-zinc-900/50 rounded-lg p-3 border border-border"> <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"> <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> <span>SUCCESSFUL</span>
</div> </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()} {selectedAccount.successCount.toLocaleString()}
</div> </div>
</div> </div>
<div className="bg-muted/30 dark:bg-zinc-900/50 rounded-lg p-3 border border-border"> <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"> <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> <span>FAILED</span>
</div> </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()} {selectedAccount.failureCount.toLocaleString()}
</div> </div>
</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, ''); 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 = [ const ACCOUNT_COLORS = [
'#277da1', // Cerulean '#1e6091', // Deep Cerulean (was #277da1)
'#43aa8b', // Seaweed '#2d8a6e', // Deep Seaweed (was #43aa8b)
'#f9c74f', // Tuscan Sun '#d4a012', // Dark Tuscan (was #f9c74f)
'#f94144', // Strawberry '#c92a2d', // Deep Strawberry (was #f94144)
'#f3722c', // Pumpkin '#c45a1a', // Deep Pumpkin (was #f3722c)
'#90be6d', // Willow '#6b9c4d', // Dark Willow (was #90be6d)
'#577590', // Blue Slate '#3d5a73', // Deep Blue Slate (was #577590)
'#f8961e', // Carrot '#cc7614', // Dark Carrot (was #f8961e)
'#4d908e', // Dark Cyan '#3a7371', // Deep Cyan (was #4d908e)
'#a78bfa', // Purple '#7c5fc4', // Deep Purple (was #a78bfa)
]; ];
/** Enhanced live pulse indicator with multi-ring animation */ /** Enhanced live pulse indicator with multi-ring animation */
@@ -94,15 +94,15 @@ function InlineStatsBadge({ success, failure }: { success: number; failure: numb
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex items-center gap-0.5"> <div className="flex items-center gap-0.5">
<CheckCircle2 className="w-3 h-3 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-500"> <span className="text-[10px] font-mono font-medium text-emerald-700 dark:text-emerald-500">
{success.toLocaleString()} {success.toLocaleString()}
</span> </span>
</div> </div>
{failure > 0 && ( {failure > 0 && (
<div className="flex items-center gap-0.5"> <div className="flex items-center gap-0.5">
<XCircle className="w-3 h-3 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-500"> <span className="text-[10px] font-mono font-medium text-red-700 dark:text-red-500">
{failure.toLocaleString()} {failure.toLocaleString()}
</span> </span>
</div> </div>
@@ -308,7 +308,7 @@ export function AuthMonitor() {
</div> </div>
{/* Flow Visualization */} {/* Flow Visualization */}
<div className="relative min-h-[320px] overflow-hidden"> <div className="relative overflow-hidden">
{selectedProviderData ? ( {selectedProviderData ? (
// Account-level flow view // Account-level flow view
<AccountFlowViz <AccountFlowViz
+64 -28
View File
@@ -5,46 +5,46 @@
:root { :root {
/* CCS Brand Colors - Calculated from Crail, Pampas, White, Cloudy */ /* CCS Brand Colors - Calculated from Crail, Pampas, White, Cloudy */
/* Primary: Neutral (Dark Warm Grey) - Demoted Crail from Primary */ /* 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 */ --primary-foreground: oklch(0.9635 0.0067 97.35); /* Pampas */
--secondary: oklch(0.9 0.01 95); /* Light Cloudy */ --secondary: oklch(0.88 0.012 95); /* Slightly darker */
--secondary-foreground: oklch(0.2 0.02 40); --secondary-foreground: oklch(0.15 0.02 40);
/* Accent: Crail (Moved from Primary) */ /* 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 */ --accent-foreground: oklch(1 0 0); /* White */
--background: oklch(0.9635 0.0067 97.35); /* Pampas */ --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: oklch(0.9 0.012 95);
--muted-foreground: oklch(0.55 0.02 40); --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 */ --border: oklch(0.78 0.02 91.6); /* Darker: 0.85 -> 0.78 */
--input: oklch(0.85 0.015 91.6); --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; --radius: 0.5rem;
--popover: oklch(0.9635 0.0067 97.35); /* Match background */ --popover: oklch(0.98 0.005 97.35); /* Slight depth */
--popover-foreground: oklch(0.2 0.02 40); /* Match foreground */ --popover-foreground: oklch(0.15 0.02 40); /* Match foreground */
--card: oklch(0.9635 0.0067 97.35); /* Match background */ --card: oklch(0.98 0.005 97.35); /* Slight depth from background */
--card-foreground: oklch(0.2 0.02 40); /* Match foreground */ --card-foreground: oklch(0.15 0.02 40); /* Match foreground */
--destructive: oklch(0.577 0.245 27.325); /* Red 600 */ --destructive: oklch(0.5 0.22 27); /* Darker red for visibility */
--destructive-foreground: oklch(0.9635 0.0067 97.35); /* White */ --destructive-foreground: oklch(0.98 0.005 97.35); /* White */
/* Sidebar colors - Light */ /* Sidebar colors - Light */
--sidebar: oklch(0.9635 0.0067 97.35); /* Pampas */ --sidebar: oklch(0.9635 0.0067 97.35); /* Pampas */
--sidebar-foreground: oklch(0.2 0.02 40); --sidebar-foreground: oklch(0.15 0.02 40);
--sidebar-primary: oklch(0.2 0.02 40); /* Neutral */ --sidebar-primary: oklch(0.15 0.02 40); /* Neutral - darker */
--sidebar-primary-foreground: oklch(0.9635 0.0067 97.35); --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-accent-foreground: oklch(1 0 0);
--sidebar-border: oklch(0.85 0.015 91.6); --sidebar-border: oklch(0.78 0.02 91.6); /* Darker */
--sidebar-ring: oklch(0.2 0.02 40); --sidebar-ring: oklch(0.15 0.02 40);
} }
.dark { .dark {
@@ -203,25 +203,57 @@
/* Flow visualization animations */ /* Flow visualization animations */
@keyframes request-pulse { @keyframes request-pulse {
0% { 0% {
stroke-opacity: 0.9; stroke-opacity: 1;
stroke-width: inherit; stroke-width: inherit;
filter: drop-shadow(0 0 2px currentColor);
} }
50% { 30% {
stroke-opacity: 0.6; 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% { 100% {
stroke-opacity: 0; stroke-opacity: 0;
stroke-width: calc(inherit * 2); 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 { @keyframes glow-pulse {
0%, 0%,
100% { 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% { 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 { .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 { .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 { .animate-subtle-float {
+4 -4
View File
@@ -29,11 +29,11 @@ const PROVIDER_COLORS: Record<string, string> = {
qwen: '#f9c74f', // Tuscan qwen: '#f9c74f', // Tuscan
}; };
// Status colors (from Analytics Cost breakdown) // Status colors (from Analytics Cost breakdown) - darker for light theme contrast
export const STATUS_COLORS = { export const STATUS_COLORS = {
success: '#43aa8b', // Seaweed success: '#15803d', // Green-700 (was Seaweed #43aa8b)
degraded: '#e09f3e', // Ochre degraded: '#b45309', // Amber-700 (was Ochre #e09f3e)
failed: '#9e2a2b', // Merlot failed: '#b91c1c', // Red-700 (was Merlot #9e2a2b)
} as const; } as const;
export function getModelColor(model: string): string { 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 }> = { const variantStyles: Record<StatVariant, { iconBg: string; iconColor: string }> = {
default: { iconBg: 'bg-muted', iconColor: 'text-muted-foreground' }, default: { iconBg: 'bg-muted', iconColor: 'text-muted-foreground' },
success: { iconBg: 'bg-green-500/10', iconColor: 'text-green-600' }, success: { iconBg: 'bg-green-600/15', iconColor: 'text-green-700 dark:text-green-500' },
warning: { iconBg: 'bg-yellow-500/10', iconColor: 'text-yellow-500' }, warning: { iconBg: 'bg-amber-500/15', iconColor: 'text-amber-700 dark:text-amber-400' },
error: { iconBg: 'bg-red-500/10', iconColor: 'text-red-500' }, error: { iconBg: 'bg-red-600/15', iconColor: 'text-red-700 dark:text-red-500' },
accent: { iconBg: 'bg-accent/10', iconColor: 'text-accent' }, accent: { iconBg: 'bg-accent/15', iconColor: 'text-accent' },
}; };
function InlineStat({ function InlineStat({