Skip to content

Commit 28cf75b

Browse files
committed
fix(dashboard): single brand color - electric blue only
- Removed ALL gradient colors from stats and repo cards - Single accent: blue-500 (#2563EB) everywhere - Clean, minimal design with proper contrast - Featured card has solid blue accent line - Consistent hover states with blue glow - No more rainbow effect
1 parent fc52660 commit 28cf75b

2 files changed

Lines changed: 116 additions & 112 deletions

File tree

frontend/src/components/RepoList.tsx

Lines changed: 76 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -12,80 +12,79 @@ interface RepoListProps {
1212

1313
const StatusBadge = ({ status }: { status: string }) => {
1414
const isIndexed = status === 'indexed'
15-
const isPending = status === 'cloned' || status === 'cloning' || status === 'indexing'
15+
16+
if (isIndexed) {
17+
return (
18+
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full bg-blue-500/10 text-blue-400 border border-blue-500/20">
19+
<span className="w-1.5 h-1.5 rounded-full bg-blue-400" />
20+
Indexed
21+
</span>
22+
)
23+
}
1624

1725
return (
18-
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full border
19-
${isIndexed
20-
? 'bg-blue-500/10 text-blue-400 border-blue-500/20'
21-
: isPending
22-
? 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'
23-
: 'bg-red-500/10 text-red-400 border-red-500/20'
24-
}`}
25-
>
26-
<span className={`w-1.5 h-1.5 rounded-full ${isIndexed ? 'bg-blue-400' : isPending ? 'bg-zinc-400 animate-pulse' : 'bg-red-400'}`} />
27-
{isIndexed ? 'Indexed' : isPending ? 'Pending' : 'Error'}
26+
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full bg-zinc-800 text-zinc-400 border border-zinc-700">
27+
<span className="w-1.5 h-1.5 rounded-full bg-zinc-500 animate-pulse" />
28+
Pending
2829
</span>
2930
)
3031
}
3132

33+
// Featured card - tall, prominent
3234
const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
3335
repo: Repository
3436
totalFunctions: number
3537
onSelect: () => void
3638
}) => {
3739
const cardRef = useRef<HTMLButtonElement>(null)
38-
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
39-
const [isHovering, setIsHovering] = useState(false)
40-
const percentage = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0
41-
42-
const handleMouseMove = (e: React.MouseEvent) => {
43-
if (!cardRef.current) return
44-
const rect = cardRef.current.getBoundingClientRect()
45-
setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top })
46-
}
40+
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
41+
const [hovering, setHovering] = useState(false)
42+
const pct = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0
4743

4844
return (
4945
<motion.button
5046
ref={cardRef}
5147
initial={{ opacity: 0, y: 20 }}
5248
animate={{ opacity: 1, y: 0 }}
53-
transition={{ duration: 0.4 }}
54-
whileHover={{ y: -2 }}
49+
whileHover={{ y: -3 }}
5550
onClick={onSelect}
56-
onMouseMove={handleMouseMove}
57-
onMouseEnter={() => setIsHovering(true)}
58-
onMouseLeave={() => setIsHovering(false)}
59-
className="group relative text-left rounded-2xl overflow-hidden w-full h-full
60-
bg-[#111113] border border-white/[0.06] hover:border-blue-500/30
61-
focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-6"
51+
onMouseMove={(e) => {
52+
if (!cardRef.current) return
53+
const rect = cardRef.current.getBoundingClientRect()
54+
setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
55+
}}
56+
onMouseEnter={() => setHovering(true)}
57+
onMouseLeave={() => setHovering(false)}
58+
className="group relative text-left rounded-2xl overflow-hidden w-full h-full min-h-[300px]
59+
bg-[#111113] border border-white/[0.06] hover:border-blue-500/40
60+
focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-6 transition-colors"
6261
>
63-
{/* Mouse glow */}
64-
{isHovering && (
62+
{/* Mouse glow - BLUE only */}
63+
{hovering && (
6564
<div
66-
className="pointer-events-none absolute inset-0 transition-opacity duration-300"
65+
className="pointer-events-none absolute inset-0"
6766
style={{
68-
background: `radial-gradient(400px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(59, 130, 246, 0.08), transparent 50%)`,
67+
background: `radial-gradient(500px circle at ${mousePos.x}px ${mousePos.y}px, rgba(37, 99, 235, 0.1), transparent 50%)`,
6968
}}
7069
/>
7170
)}
7271

73-
{/* Top gradient line */}
74-
<div className="absolute top-0 left-0 right-0 h-[2px] bg-gradient-to-r from-blue-500 to-violet-500" />
72+
{/* Top accent - solid blue */}
73+
<div className="absolute top-0 left-0 right-0 h-[2px] bg-blue-500" />
7574

7675
<div className="relative flex flex-col h-full">
7776
{/* Header */}
7877
<div className="flex items-start justify-between mb-6">
79-
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-blue-500/10 to-violet-500/10 border border-white/[0.08] flex items-center justify-center">
80-
<svg className="w-7 h-7 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
78+
<div className="w-14 h-14 rounded-xl bg-blue-500/10 border border-blue-500/20 flex items-center justify-center">
79+
<svg className="w-7 h-7 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
8180
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
8281
</svg>
8382
</div>
8483
<StatusBadge status={repo.status} />
8584
</div>
8685

8786
{/* Title */}
88-
<h3 className="text-xl font-semibold text-white mb-1 group-hover:text-blue-400 transition-colors">
87+
<h3 className="text-2xl font-semibold text-white mb-1 group-hover:text-blue-400 transition-colors">
8988
{repo.name}
9089
</h3>
9190
<p className="text-sm text-zinc-500 font-mono mb-auto">{repo.branch}</p>
@@ -94,22 +93,23 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
9493
<div className="pt-6 mt-6 border-t border-white/[0.06]">
9594
<div className="flex items-end justify-between mb-3">
9695
<span className="text-sm text-zinc-500">Functions indexed</span>
97-
<span className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-violet-400">
96+
<span className="text-4xl font-bold text-blue-500">
9897
{(repo.file_count || 0).toLocaleString()}
9998
</span>
10099
</div>
101100

101+
{/* Progress bar */}
102102
{totalFunctions > 0 && repo.file_count > 0 && (
103103
<div className="space-y-2">
104-
<div className="h-1 bg-white/[0.06] rounded-full overflow-hidden">
104+
<div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
105105
<motion.div
106106
initial={{ width: 0 }}
107-
animate={{ width: `${percentage}%` }}
108-
transition={{ duration: 0.8, ease: "easeOut", delay: 0.2 }}
109-
className="h-full bg-gradient-to-r from-blue-500 to-violet-500 rounded-full"
107+
animate={{ width: `${pct}%` }}
108+
transition={{ duration: 0.8, ease: "easeOut", delay: 0.3 }}
109+
className="h-full bg-blue-500 rounded-full"
110110
/>
111111
</div>
112-
<p className="text-xs text-zinc-600">{percentage}% of total</p>
112+
<p className="text-xs text-zinc-600">{pct}% of total indexed</p>
113113
</div>
114114
)}
115115
</div>
@@ -118,51 +118,50 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
118118
)
119119
}
120120

121+
// Regular card - compact
121122
const RepoCard = ({ repo, index, onSelect }: {
122123
repo: Repository
123124
index: number
124125
onSelect: () => void
125126
}) => {
126127
const cardRef = useRef<HTMLButtonElement>(null)
127-
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
128-
const [isHovering, setIsHovering] = useState(false)
129-
130-
const handleMouseMove = (e: React.MouseEvent) => {
131-
if (!cardRef.current) return
132-
const rect = cardRef.current.getBoundingClientRect()
133-
setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top })
134-
}
128+
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
129+
const [hovering, setHovering] = useState(false)
135130

136131
return (
137132
<motion.button
138133
ref={cardRef}
139134
initial={{ opacity: 0, y: 20 }}
140135
animate={{ opacity: 1, y: 0 }}
141-
transition={{ delay: index * 0.1, duration: 0.4 }}
142-
whileHover={{ y: -2 }}
136+
transition={{ delay: index * 0.1 }}
137+
whileHover={{ y: -3 }}
143138
onClick={onSelect}
144-
onMouseMove={handleMouseMove}
145-
onMouseEnter={() => setIsHovering(true)}
146-
onMouseLeave={() => setIsHovering(false)}
147-
className="group relative text-left rounded-2xl overflow-hidden w-full
148-
bg-[#111113] border border-white/[0.06] hover:border-white/[0.12]
149-
focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-5"
139+
onMouseMove={(e) => {
140+
if (!cardRef.current) return
141+
const rect = cardRef.current.getBoundingClientRect()
142+
setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
143+
}}
144+
onMouseEnter={() => setHovering(true)}
145+
onMouseLeave={() => setHovering(false)}
146+
className="group relative text-left rounded-2xl overflow-hidden w-full h-full
147+
bg-[#111113] border border-white/[0.06] hover:border-white/[0.15]
148+
focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-5 transition-colors"
150149
>
151150
{/* Mouse glow */}
152-
{isHovering && (
151+
{hovering && (
153152
<div
154-
className="pointer-events-none absolute inset-0 transition-opacity duration-300"
153+
className="pointer-events-none absolute inset-0"
155154
style={{
156-
background: `radial-gradient(250px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(59, 130, 246, 0.06), transparent 50%)`,
155+
background: `radial-gradient(300px circle at ${mousePos.x}px ${mousePos.y}px, rgba(37, 99, 235, 0.08), transparent 50%)`,
157156
}}
158157
/>
159158
)}
160159

161-
<div className="relative">
160+
<div className="relative flex flex-col h-full">
162161
{/* Header */}
163162
<div className="flex items-start justify-between mb-4">
164-
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-500/10 to-violet-500/10 border border-white/[0.08] flex items-center justify-center">
165-
<svg className="w-5 h-5 text-blue-400/70" fill="none" viewBox="0 0 24 24" stroke="currentColor">
163+
<div className="w-10 h-10 rounded-lg bg-zinc-800 border border-zinc-700 flex items-center justify-center">
164+
<svg className="w-5 h-5 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
166165
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
167166
</svg>
168167
</div>
@@ -173,13 +172,13 @@ const RepoCard = ({ repo, index, onSelect }: {
173172
<h3 className="font-semibold text-white mb-0.5 group-hover:text-blue-400 transition-colors">
174173
{repo.name}
175174
</h3>
176-
<p className="text-xs text-zinc-500 font-mono mb-4">{repo.branch}</p>
175+
<p className="text-xs text-zinc-500 font-mono mb-auto">{repo.branch}</p>
177176

178177
{/* Stats */}
179-
<div className="pt-3 border-t border-white/[0.04]">
178+
<div className="pt-4 mt-4 border-t border-white/[0.04]">
180179
<div className="flex items-center justify-between">
181180
<span className="text-xs text-zinc-500">Functions</span>
182-
<span className="text-lg font-semibold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-violet-400">
181+
<span className="text-xl font-bold text-blue-500">
183182
{(repo.file_count || 0).toLocaleString()}
184183
</span>
185184
</div>
@@ -199,8 +198,10 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro
199198
animate={{ opacity: 1 }}
200199
className="bg-[#111113] border border-white/[0.06] rounded-2xl p-16 text-center"
201200
>
202-
<div className="w-16 h-16 mx-auto mb-4 rounded-xl bg-gradient-to-br from-blue-500/10 to-violet-500/10 border border-white/[0.06] flex items-center justify-center">
203-
<span className="text-2xl">📦</span>
201+
<div className="w-14 h-14 mx-auto mb-4 rounded-xl bg-blue-500/10 border border-blue-500/20 flex items-center justify-center">
202+
<svg className="w-6 h-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
203+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4v16m8-8H4" />
204+
</svg>
204205
</div>
205206
<h3 className="text-lg font-semibold mb-2 text-white">No repositories yet</h3>
206207
<p className="text-sm text-zinc-500 max-w-xs mx-auto">
@@ -210,7 +211,7 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro
210211
)
211212
}
212213

213-
// Sort: indexed first, then by function count
214+
// Sort: indexed first, then by function count desc
214215
const sortedRepos = useMemo(() => {
215216
return [...repos].sort((a, b) => {
216217
if (a.status === 'indexed' && b.status !== 'indexed') return -1
@@ -223,10 +224,10 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro
223224
const [featured, ...rest] = sortedRepos
224225

225226
return (
226-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
227-
{/* Featured - spans 2 rows */}
227+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 auto-rows-fr">
228+
{/* Featured - spans 2 rows on desktop */}
228229
{featured && (
229-
<div className="lg:row-span-2 min-h-[320px]">
230+
<div className="lg:row-span-2">
230231
<FeaturedRepoCard
231232
repo={featured}
232233
totalFunctions={totalFunctions}
@@ -235,7 +236,7 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro
235236
</div>
236237
)}
237238

238-
{/* Rest */}
239+
{/* Other repos */}
239240
{rest.map((repo, index) => (
240241
<RepoCard
241242
key={repo.id}

frontend/src/components/dashboard/DashboardStats.tsx

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,51 +29,54 @@ export function DashboardStats({ repos }: DashboardStatsProps) {
2929
const totalRepos = repos.length
3030
const indexedRepos = repos.filter(r => r.status === 'indexed').length
3131
const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0)
32-
const indexingRepos = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length
32+
const indexingCount = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length
3333

3434
const animatedTotal = useAnimatedCounter(totalRepos)
3535
const animatedIndexed = useAnimatedCounter(indexedRepos)
3636
const animatedFunctions = useAnimatedCounter(totalFunctions)
3737

38-
const stats = [
39-
{ label: 'Total Repositories', value: animatedTotal, suffix: '' },
40-
{ label: 'Indexed', value: animatedIndexed, suffix: `/${totalRepos}`, hasIndicator: indexingRepos > 0, indicatorText: `${indexingRepos} indexing...` },
41-
{ label: 'Functions Indexed', value: animatedFunctions, suffix: '' },
42-
]
43-
4438
return (
4539
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
46-
{stats.map((stat, index) => (
47-
<motion.div
48-
key={stat.label}
49-
initial={{ opacity: 0, y: 16 }}
50-
animate={{ opacity: 1, y: 0 }}
51-
transition={{ delay: index * 0.1, duration: 0.4 }}
52-
className="relative overflow-hidden rounded-2xl bg-[#111113] border border-white/[0.06] p-6"
53-
>
54-
{/* Subtle glow - same blue-violet for all */}
55-
<div className="absolute -top-20 -right-20 w-40 h-40 bg-gradient-to-br from-blue-500/20 to-violet-500/20 blur-3xl" />
56-
57-
<div className="relative">
58-
<p className="text-sm text-zinc-500 mb-2">{stat.label}</p>
59-
<div className="flex items-baseline gap-1">
60-
<span className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-violet-400">
61-
{stat.value.toLocaleString()}
62-
</span>
63-
{stat.suffix && (
64-
<span className="text-xl text-zinc-600">{stat.suffix}</span>
65-
)}
66-
</div>
67-
68-
{stat.hasIndicator && (
69-
<div className="mt-3 flex items-center gap-2 text-xs text-blue-400">
70-
<span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
71-
{stat.indicatorText}
72-
</div>
73-
)}
40+
{/* Total Repos */}
41+
<motion.div
42+
initial={{ opacity: 0, y: 16 }}
43+
animate={{ opacity: 1, y: 0 }}
44+
className="relative overflow-hidden rounded-2xl bg-[#111113] border border-white/[0.06] p-6"
45+
>
46+
<p className="text-sm text-zinc-500 mb-2">Total Repositories</p>
47+
<p className="text-4xl font-bold text-blue-500">{animatedTotal}</p>
48+
</motion.div>
49+
50+
{/* Indexed */}
51+
<motion.div
52+
initial={{ opacity: 0, y: 16 }}
53+
animate={{ opacity: 1, y: 0 }}
54+
transition={{ delay: 0.05 }}
55+
className="relative overflow-hidden rounded-2xl bg-[#111113] border border-white/[0.06] p-6"
56+
>
57+
<p className="text-sm text-zinc-500 mb-2">Indexed</p>
58+
<div className="flex items-baseline gap-1">
59+
<span className="text-4xl font-bold text-blue-500">{animatedIndexed}</span>
60+
<span className="text-xl text-zinc-600">/{totalRepos}</span>
61+
</div>
62+
{indexingCount > 0 && (
63+
<div className="mt-3 flex items-center gap-2 text-xs text-blue-400">
64+
<span className="w-1.5 h-1.5 rounded-full bg-blue-500 animate-pulse" />
65+
{indexingCount} indexing...
7466
</div>
75-
</motion.div>
76-
))}
67+
)}
68+
</motion.div>
69+
70+
{/* Functions */}
71+
<motion.div
72+
initial={{ opacity: 0, y: 16 }}
73+
animate={{ opacity: 1, y: 0 }}
74+
transition={{ delay: 0.1 }}
75+
className="relative overflow-hidden rounded-2xl bg-[#111113] border border-white/[0.06] p-6"
76+
>
77+
<p className="text-sm text-zinc-500 mb-2">Functions Indexed</p>
78+
<p className="text-4xl font-bold text-blue-500">{animatedFunctions.toLocaleString()}</p>
79+
</motion.div>
7780
</div>
7881
)
7982
}

0 commit comments

Comments
 (0)