mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 04:17:11 +00:00
feat(ui): enhance light theme contrast and animations
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user