mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 12:19:35 +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) {
|
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>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user