Numan Saeed commited on
Commit
bedc7e7
·
1 Parent(s): 0f97254

feat: Add visual improvements and aesthetic enhancements

Browse files

- Enhanced index.css with glassmorphism, gradient mesh, shimmer loading
- Added new Tailwind animations (scale-in, bounce-subtle, glow-pulse)
- Updated Header with animated connection indicator
- Enhanced ResultsCard and GAResultsCard with premium loading effects
- Added App.tsx gradient mesh background

frontend/src/App.tsx CHANGED
@@ -54,7 +54,7 @@ function App() {
54
 
55
  return (
56
  <ImageProvider>
57
- <div className="h-screen flex flex-col bg-dark-bg overflow-hidden">
58
  {/* Header - fixed height */}
59
  <Header isConnected={isConnected} feedbackStats={feedbackStats} />
60
 
 
54
 
55
  return (
56
  <ImageProvider>
57
+ <div className="h-screen flex flex-col bg-dark-bg gradient-mesh overflow-hidden">
58
  {/* Header - fixed height */}
59
  <Header isConnected={isConnected} feedbackStats={feedbackStats} />
60
 
frontend/src/components/GAResultsCard.tsx CHANGED
@@ -15,16 +15,16 @@ const BIOMETRY_LABELS: Record<string, string> = {
15
  export function GAResultsCard({ results, isLoading }: GAResultsCardProps) {
16
  if (isLoading) {
17
  return (
18
- <div className="space-y-3 animate-pulse">
19
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
20
- <div className="h-3 w-24 bg-dark-input rounded mb-2" />
21
- <div className="h-8 w-40 bg-dark-input rounded" />
22
  </div>
23
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
24
- <div className="h-3 w-40 bg-dark-input rounded mb-3" />
25
  <div className="grid grid-cols-3 gap-2">
26
  {[...Array(3)].map((_, i) => (
27
- <div key={i} className="h-16 bg-dark-input rounded-lg" />
28
  ))}
29
  </div>
30
  </div>
@@ -56,7 +56,7 @@ export function GAResultsCard({ results, isLoading }: GAResultsCardProps) {
56
  else if (results.femur_length) biometryLabel = BIOMETRY_LABELS.femur_length;
57
 
58
  return (
59
- <div className="space-y-3 animate-fade-in">
60
  {/* Gestational Age */}
61
  <div className="bg-gradient-to-r from-nvidia-green/10 to-nvidia-green/5 border border-nvidia-green/20 rounded-xl p-4 shadow-card">
62
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-1">
@@ -72,7 +72,7 @@ export function GAResultsCard({ results, isLoading }: GAResultsCardProps) {
72
 
73
  {/* Biometry Percentiles - Dynamic based on view */}
74
  {biometryData && (
75
- <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
76
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-3">
77
  {biometryLabel} Percentiles
78
  </p>
 
15
  export function GAResultsCard({ results, isLoading }: GAResultsCardProps) {
16
  if (isLoading) {
17
  return (
18
+ <div className="space-y-3">
19
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
20
+ <div className="h-3 w-24 shimmer-premium rounded mb-2" />
21
+ <div className="h-8 w-40 shimmer-premium rounded" />
22
  </div>
23
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
24
+ <div className="h-3 w-40 shimmer-premium rounded mb-3" />
25
  <div className="grid grid-cols-3 gap-2">
26
  {[...Array(3)].map((_, i) => (
27
+ <div key={i} className="h-16 shimmer-premium rounded-lg" style={{ animationDelay: `${i * 100}ms` }} />
28
  ))}
29
  </div>
30
  </div>
 
56
  else if (results.femur_length) biometryLabel = BIOMETRY_LABELS.femur_length;
57
 
58
  return (
59
+ <div className="space-y-3 animate-scale-in">
60
  {/* Gestational Age */}
61
  <div className="bg-gradient-to-r from-nvidia-green/10 to-nvidia-green/5 border border-nvidia-green/20 rounded-xl p-4 shadow-card">
62
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-1">
 
72
 
73
  {/* Biometry Percentiles - Dynamic based on view */}
74
  {biometryData && (
75
+ <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card card-interactive">
76
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-3">
77
  {biometryLabel} Percentiles
78
  </p>
frontend/src/components/Header.tsx CHANGED
@@ -43,15 +43,19 @@ export function Header({ isConnected, feedbackStats }: HeaderProps) {
43
  )}
44
  </div>
45
  )}
46
-
47
  <div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-dark-input border border-dark-border">
48
- <div
49
- className={`w-2 h-2 rounded-full transition-colors ${
50
- isConnected
51
- ? 'bg-nvidia-green shadow-[0_0_8px_rgba(118,185,0,0.5)]'
52
- : 'bg-red-500 animate-pulse'
53
- }`}
54
- />
 
 
 
 
55
  <span className="text-xs text-text-secondary">
56
  {isConnected ? 'Model Ready' : 'Connecting...'}
57
  </span>
 
43
  )}
44
  </div>
45
  )}
46
+
47
  <div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-dark-input border border-dark-border">
48
+ <div className="relative">
49
+ <div
50
+ className={`w-2 h-2 rounded-full transition-colors ${isConnected
51
+ ? 'bg-nvidia-green animate-glow-pulse'
52
+ : 'bg-red-500 animate-pulse'
53
+ }`}
54
+ />
55
+ {isConnected && (
56
+ <div className="absolute inset-0 rounded-full bg-nvidia-green/30 animate-ping" style={{ animationDuration: '2s' }} />
57
+ )}
58
+ </div>
59
  <span className="text-xs text-text-secondary">
60
  {isConnected ? 'Model Ready' : 'Connecting...'}
61
  </span>
frontend/src/components/ResultsCard.tsx CHANGED
@@ -11,19 +11,19 @@ interface ResultsCardProps {
11
  export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: ResultsCardProps) {
12
  if (isLoading) {
13
  return (
14
- <div className="space-y-3 animate-pulse">
15
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
16
- <div className="h-3 w-20 bg-dark-input rounded mb-2" />
17
- <div className="h-6 w-40 bg-dark-input rounded" />
18
  </div>
19
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card space-y-2">
20
  {[...Array(5)].map((_, i) => (
21
- <div key={i} className="space-y-1">
22
  <div className="flex justify-between">
23
- <div className="h-3 w-20 bg-dark-input rounded" />
24
- <div className="h-3 w-10 bg-dark-input rounded" />
25
  </div>
26
- <div className="h-2 bg-dark-input rounded" />
27
  </div>
28
  ))}
29
  </div>
@@ -43,24 +43,23 @@ export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: Re
43
  const isLowConfidence = topResult.confidence < confidenceThreshold;
44
 
45
  return (
46
- <div className="space-y-3 animate-fade-in">
47
  {/* Low Confidence Warning */}
48
  {isLowConfidence && (
49
  <div className="flex items-start gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
50
  <AlertTriangle className="w-4 h-4 text-yellow-600 flex-shrink-0 mt-0.5" />
51
  <div className="text-xs text-yellow-800">
52
- <span className="font-semibold">Low Confidence:</span> The model is uncertain about this prediction.
53
  Please verify manually and provide feedback.
54
  </div>
55
  </div>
56
  )}
57
 
58
  {/* Top Prediction */}
59
- <div className={`bg-gradient-to-r ${
60
- isLowConfidence
61
- ? 'from-yellow-50 to-yellow-50/50 border-yellow-200'
62
  : 'from-nvidia-green/10 to-nvidia-green/5 border-nvidia-green/20'
63
- } border rounded-xl p-4 shadow-card`}>
64
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-1">
65
  Top Prediction
66
  </p>
@@ -70,9 +69,8 @@ export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: Re
70
  </span>
71
  <div className="flex items-center gap-1.5">
72
  {isLowConfidence && <AlertTriangle className="w-4 h-4 text-yellow-600" />}
73
- <span className={`text-lg font-semibold ${
74
- isLowConfidence ? 'text-yellow-700' : 'text-text-primary'
75
- }`}>
76
  {topResult.confidence.toFixed(1)}%
77
  </span>
78
  </div>
@@ -80,7 +78,7 @@ export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: Re
80
  </div>
81
 
82
  {/* All Predictions */}
83
- <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
84
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-3">
85
  All Predictions
86
  </p>
@@ -95,8 +93,8 @@ export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: Re
95
  </div>
96
  <div className="h-2 bg-dark-input rounded-full overflow-hidden">
97
  <div
98
- className="h-full bg-gradient-to-r from-nvidia-green to-nvidia-green-hover rounded-full transition-all duration-500"
99
- style={{ width: `${result.confidence}%` }}
100
  />
101
  </div>
102
  </div>
 
11
  export function ResultsCard({ results, isLoading, confidenceThreshold = 70 }: ResultsCardProps) {
12
  if (isLoading) {
13
  return (
14
+ <div className="space-y-3">
15
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
16
+ <div className="h-3 w-20 shimmer-premium rounded mb-2" />
17
+ <div className="h-6 w-40 shimmer-premium rounded" />
18
  </div>
19
  <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card space-y-2">
20
  {[...Array(5)].map((_, i) => (
21
+ <div key={i} className="space-y-1" style={{ animationDelay: `${i * 100}ms` }}>
22
  <div className="flex justify-between">
23
+ <div className="h-3 w-20 shimmer-premium rounded" />
24
+ <div className="h-3 w-10 shimmer-premium rounded" />
25
  </div>
26
+ <div className="h-2 shimmer-premium rounded" />
27
  </div>
28
  ))}
29
  </div>
 
43
  const isLowConfidence = topResult.confidence < confidenceThreshold;
44
 
45
  return (
46
+ <div className="space-y-3 animate-scale-in">
47
  {/* Low Confidence Warning */}
48
  {isLowConfidence && (
49
  <div className="flex items-start gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
50
  <AlertTriangle className="w-4 h-4 text-yellow-600 flex-shrink-0 mt-0.5" />
51
  <div className="text-xs text-yellow-800">
52
+ <span className="font-semibold">Low Confidence:</span> The model is uncertain about this prediction.
53
  Please verify manually and provide feedback.
54
  </div>
55
  </div>
56
  )}
57
 
58
  {/* Top Prediction */}
59
+ <div className={`bg-gradient-to-r ${isLowConfidence
60
+ ? 'from-yellow-50 to-yellow-50/50 border-yellow-200'
 
61
  : 'from-nvidia-green/10 to-nvidia-green/5 border-nvidia-green/20'
62
+ } border rounded-xl p-4 shadow-card`}>
63
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-1">
64
  Top Prediction
65
  </p>
 
69
  </span>
70
  <div className="flex items-center gap-1.5">
71
  {isLowConfidence && <AlertTriangle className="w-4 h-4 text-yellow-600" />}
72
+ <span className={`text-lg font-semibold ${isLowConfidence ? 'text-yellow-700' : 'text-text-primary'
73
+ }`}>
 
74
  {topResult.confidence.toFixed(1)}%
75
  </span>
76
  </div>
 
78
  </div>
79
 
80
  {/* All Predictions */}
81
+ <div className="bg-white border border-dark-border rounded-xl p-4 shadow-card card-interactive">
82
  <p className="text-[10px] uppercase tracking-wider text-text-muted mb-3">
83
  All Predictions
84
  </p>
 
93
  </div>
94
  <div className="h-2 bg-dark-input rounded-full overflow-hidden">
95
  <div
96
+ className="h-full bg-gradient-to-r from-nvidia-green to-nvidia-green-hover rounded-full result-bar-animated"
97
+ style={{ width: `${result.confidence}%`, animationDelay: `${index * 80}ms` }}
98
  />
99
  </div>
100
  </div>
frontend/src/index.css CHANGED
@@ -52,6 +52,7 @@ body {
52
  0% {
53
  background-position: -200% 0;
54
  }
 
55
  100% {
56
  background-position: 200% 0;
57
  }
@@ -59,16 +60,103 @@ body {
59
 
60
  .animate-shimmer {
61
  animation: shimmer 2s infinite;
62
- background: linear-gradient(
63
- 90deg,
64
- #f1f5f9 0%,
65
- #e2e8f0 50%,
66
- #f1f5f9 100%
67
- );
68
  background-size: 200% 100%;
69
  }
70
 
71
  /* Smooth transitions globally */
72
- button, a, input, [role="button"] {
 
 
 
73
  transition: all 0.15s ease;
74
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  0% {
53
  background-position: -200% 0;
54
  }
55
+
56
  100% {
57
  background-position: 200% 0;
58
  }
 
60
 
61
  .animate-shimmer {
62
  animation: shimmer 2s infinite;
63
+ background: linear-gradient(90deg,
64
+ #f1f5f9 0%,
65
+ #e2e8f0 50%,
66
+ #f1f5f9 100%);
 
 
67
  background-size: 200% 100%;
68
  }
69
 
70
  /* Smooth transitions globally */
71
+ button,
72
+ a,
73
+ input,
74
+ [role="button"] {
75
  transition: all 0.15s ease;
76
  }
77
+
78
+ /* Enhanced glassmorphism panels */
79
+ .glass-panel {
80
+ background: rgba(255, 255, 255, 0.8);
81
+ backdrop-filter: blur(12px);
82
+ -webkit-backdrop-filter: blur(12px);
83
+ border: 1px solid rgba(255, 255, 255, 0.2);
84
+ }
85
+
86
+ /* Subtle gradient mesh background */
87
+ .gradient-mesh {
88
+ background:
89
+ radial-gradient(at 40% 20%, rgba(118, 185, 0, 0.03) 0px, transparent 50%),
90
+ radial-gradient(at 80% 0%, rgba(14, 165, 233, 0.03) 0px, transparent 50%),
91
+ radial-gradient(at 0% 50%, rgba(118, 185, 0, 0.02) 0px, transparent 50%),
92
+ #f8fafc;
93
+ }
94
+
95
+ /* Premium shimmer loading effect */
96
+ .shimmer-premium {
97
+ background: linear-gradient(90deg,
98
+ #f1f5f9 0%,
99
+ #f8fafc 25%,
100
+ #e2e8f0 50%,
101
+ #f8fafc 75%,
102
+ #f1f5f9 100%);
103
+ background-size: 400% 100%;
104
+ animation: shimmer 2s ease-in-out infinite;
105
+ }
106
+
107
+ /* Card interactive hover effect */
108
+ .card-interactive {
109
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
110
+ }
111
+
112
+ .card-interactive:hover {
113
+ transform: translateY(-2px);
114
+ box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.05);
115
+ }
116
+
117
+ /* Subtle glow effect */
118
+ .glow-nvidia {
119
+ box-shadow: 0 0 20px rgba(118, 185, 0, 0.15);
120
+ }
121
+
122
+ /* Smooth image transition */
123
+ .image-preview {
124
+ transition: opacity 0.3s ease, transform 0.3s ease;
125
+ }
126
+
127
+ .image-preview:hover {
128
+ transform: scale(1.02);
129
+ }
130
+
131
+ /* Result bar fill animation */
132
+ @keyframes fillBar {
133
+ from {
134
+ width: 0%;
135
+ }
136
+ }
137
+
138
+ .result-bar-animated {
139
+ animation: fillBar 0.6s ease-out forwards;
140
+ }
141
+
142
+ /* Pulse ring for connection indicator */
143
+ @keyframes pulseRing {
144
+ 0% {
145
+ transform: scale(1);
146
+ opacity: 1;
147
+ }
148
+
149
+ 100% {
150
+ transform: scale(2);
151
+ opacity: 0;
152
+ }
153
+ }
154
+
155
+ .pulse-ring::before {
156
+ content: '';
157
+ position: absolute;
158
+ inset: -2px;
159
+ border-radius: 50%;
160
+ border: 2px solid currentColor;
161
+ animation: pulseRing 1.5s ease-out infinite;
162
+ }
frontend/tailwind.config.js CHANGED
@@ -45,6 +45,9 @@ export default {
45
  'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
46
  'fade-in': 'fadeIn 0.3s ease-out',
47
  'slide-up': 'slideUp 0.3s ease-out',
 
 
 
48
  },
49
  keyframes: {
50
  fadeIn: {
@@ -55,6 +58,19 @@ export default {
55
  '0%': { opacity: '0', transform: 'translateY(10px)' },
56
  '100%': { opacity: '1', transform: 'translateY(0)' },
57
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  },
59
  },
60
  },
 
45
  'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
46
  'fade-in': 'fadeIn 0.3s ease-out',
47
  'slide-up': 'slideUp 0.3s ease-out',
48
+ 'scale-in': 'scaleIn 0.3s ease-out',
49
+ 'bounce-subtle': 'bounceSubtle 0.4s ease-out',
50
+ 'glow-pulse': 'glowPulse 2s ease-in-out infinite',
51
  },
52
  keyframes: {
53
  fadeIn: {
 
58
  '0%': { opacity: '0', transform: 'translateY(10px)' },
59
  '100%': { opacity: '1', transform: 'translateY(0)' },
60
  },
61
+ scaleIn: {
62
+ '0%': { opacity: '0', transform: 'scale(0.95)' },
63
+ '100%': { opacity: '1', transform: 'scale(1)' },
64
+ },
65
+ bounceSubtle: {
66
+ '0%': { transform: 'translateY(0)' },
67
+ '50%': { transform: 'translateY(-3px)' },
68
+ '100%': { transform: 'translateY(0)' },
69
+ },
70
+ glowPulse: {
71
+ '0%, 100%': { boxShadow: '0 0 8px rgba(118, 185, 0, 0.4)' },
72
+ '50%': { boxShadow: '0 0 16px rgba(118, 185, 0, 0.6)' },
73
+ },
74
  },
75
  },
76
  },