Skip to content

Commit e467207

Browse files
committed
feat(frontend): Major UX/UI overhaul - stale results indicator + premium cards
UX FIX - Stale Results Problem: - Separate inputQuery (typing) vs searchedQuery (what was searched) - When user types new query, results blur/fade to 40% opacity - Yellow indicator: 'Press Enter to search for X' with pulsing dot - Shows '(showing results for Y)' to clarify what's displayed - Results have subtle 2px blur + scale down to 0.98 when stale VISUAL REDESIGN - Premium Card Design: - Removed cramped border-b between header and code - More padding (px-6 py-5 vs px-5 py-4) - Function name is now the HERO (text-lg, hover turns blue) - Match score in colored box with gradient text: - Green (70%+): emerald gradient - Blue (50-70%): blue/indigo gradient - Amber (<50%): orange gradient - Full file path shown (not truncated) - Type badge is minimal (bg-white/5, no border) CODE BLOCK IMPROVEMENTS: - Max height 200px with gradient fade - 'Show X more lines' button to expand - 'Show less' button to collapse - Darker code background (#08080a) - Better padding in code block SPACING: - Cards now have space-y-6 (was space-y-4) - More breathing room overall This transforms the 'meh' results page into a premium experience.
1 parent e00c3b2 commit e467207

1 file changed

Lines changed: 125 additions & 38 deletions

File tree

frontend/src/pages/LandingPage.tsx

Lines changed: 125 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,24 @@ const cardHoverVariants = {
259259
}
260260

261261
function ResultCard({ result, index }: { result: SearchResult; index: number }) {
262+
const [expanded, setExpanded] = useState(false)
263+
const codeLines = result.code.split('\n')
264+
const isLongCode = codeLines.length > 8
265+
const displayCode = expanded ? result.code : codeLines.slice(0, 8).join('\n')
266+
267+
// Color based on match score
268+
const scoreColor = result.score >= 0.7
269+
? 'from-emerald-400 to-green-500'
270+
: result.score >= 0.5
271+
? 'from-blue-400 to-indigo-500'
272+
: 'from-amber-400 to-orange-500'
273+
274+
const scoreBg = result.score >= 0.7
275+
? 'bg-emerald-500/10 border-emerald-500/20'
276+
: result.score >= 0.5
277+
? 'bg-blue-500/10 border-blue-500/20'
278+
: 'bg-amber-500/10 border-amber-500/20'
279+
262280
return (
263281
<motion.div
264282
variants={resultCardVariants}
@@ -274,35 +292,79 @@ function ResultCard({ result, index }: { result: SearchResult; index: number })
274292
initial="rest"
275293
whileHover="hover"
276294
>
277-
<Card
278-
className="bg-[#111113] border-white/5 overflow-hidden hover:border-blue-500/30 transition-colors duration-300 cursor-pointer"
279-
>
280-
<div className="px-5 py-4 border-b border-white/5 flex items-start justify-between">
281-
<div>
282-
<div className="flex items-center gap-3">
283-
<h3 className="font-mono font-semibold">{result.name}</h3>
284-
<Badge variant="outline" className="text-[10px] text-gray-400 border-gray-700">
285-
{result.type.replace('_', ' ')}
286-
</Badge>
295+
<Card className="bg-[#0c0c0e] border-white/[0.06] overflow-hidden hover:border-white/10 transition-all duration-300 cursor-pointer group">
296+
{/* Header - Clean and Spacious */}
297+
<div className="px-6 py-5 flex items-start justify-between gap-6">
298+
{/* Left: Function info */}
299+
<div className="flex-1 min-w-0">
300+
<div className="flex items-center gap-3 mb-2">
301+
<h3 className="font-mono text-lg font-semibold text-white truncate group-hover:text-blue-400 transition-colors">
302+
{result.name}
303+
</h3>
304+
<span className="px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-gray-500 bg-white/5 rounded">
305+
{result.type.replace('_', ' ')}
306+
</span>
307+
</div>
308+
<p className="text-sm text-gray-500 font-mono truncate">
309+
{result.file_path}
310+
</p>
311+
</div>
312+
313+
{/* Right: Match Score - THE HERO */}
314+
<div className={cn("flex flex-col items-center px-4 py-3 rounded-xl border", scoreBg)}>
315+
<div className={cn("text-3xl font-bold bg-gradient-to-r bg-clip-text text-transparent", scoreColor)}>
316+
{(result.score * 100).toFixed(0)}%
317+
</div>
318+
<div className="text-[10px] text-gray-500 uppercase tracking-widest mt-0.5">match</div>
287319
</div>
288-
<p className="text-sm text-gray-500 font-mono mt-1">
289-
{result.file_path.split('/').slice(-2).join('/')}
290-
</p>
291320
</div>
292-
<div className="text-right">
293-
<div className="text-2xl font-bold text-blue-400">{(result.score * 100).toFixed(0)}%</div>
294-
<div className="text-[10px] text-gray-500 uppercase tracking-wider">match</div>
321+
322+
{/* Code Block - Contained with gradient fade */}
323+
<div className="relative">
324+
<div className={cn(
325+
"transition-all duration-300",
326+
!expanded && isLongCode && "max-h-[200px] overflow-hidden"
327+
)}>
328+
<SyntaxHighlighter
329+
language={result.language || 'python'}
330+
style={oneDark}
331+
customStyle={{
332+
margin: 0,
333+
borderRadius: 0,
334+
fontSize: '0.8rem',
335+
background: '#08080a',
336+
padding: '1rem 1.5rem',
337+
}}
338+
showLineNumbers
339+
startingLineNumber={result.line_start || 1}
340+
>
341+
{displayCode}
342+
</SyntaxHighlighter>
343+
</div>
344+
345+
{/* Gradient fade + expand button */}
346+
{isLongCode && !expanded && (
347+
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-[#08080a] to-transparent flex items-end justify-center pb-3">
348+
<button
349+
onClick={(e) => { e.stopPropagation(); setExpanded(true); }}
350+
className="px-4 py-1.5 text-xs font-medium text-gray-400 bg-white/5 hover:bg-white/10 rounded-full border border-white/10 hover:border-white/20 transition-all"
351+
>
352+
Show {codeLines.length - 8} more lines
353+
</button>
354+
</div>
355+
)}
356+
357+
{isLongCode && expanded && (
358+
<div className="flex justify-center py-3 bg-[#08080a] border-t border-white/5">
359+
<button
360+
onClick={(e) => { e.stopPropagation(); setExpanded(false); }}
361+
className="px-4 py-1.5 text-xs font-medium text-gray-400 bg-white/5 hover:bg-white/10 rounded-full border border-white/10 hover:border-white/20 transition-all"
362+
>
363+
Show less
364+
</button>
365+
</div>
366+
)}
295367
</div>
296-
</div>
297-
<SyntaxHighlighter
298-
language={result.language || 'python'}
299-
style={oneDark}
300-
customStyle={{ margin: 0, borderRadius: 0, fontSize: '0.8rem', background: '#0d0d0f' }}
301-
showLineNumbers
302-
startingLineNumber={result.line_start || 1}
303-
>
304-
{result.code}
305-
</SyntaxHighlighter>
306368
</Card>
307369
</motion.div>
308370
</motion.div>
@@ -319,16 +381,21 @@ export function LandingPage() {
319381
const [hasSearched, setHasSearched] = useState(false)
320382
const [availableRepos, setAvailableRepos] = useState<string[]>([])
321383
const [rateLimitError, setRateLimitError] = useState<string | null>(null)
322-
const [lastQuery, setLastQuery] = useState('')
384+
const [searchedQuery, setSearchedQuery] = useState('') // What was actually searched
385+
const [inputQuery, setInputQuery] = useState('') // What user is typing
323386
const [currentRepoId, setCurrentRepoId] = useState('')
324387
const [isCustomRepo, setIsCustomRepo] = useState(false)
325388

389+
// Check if results are stale (user typed something different)
390+
const isStale = hasSearched && !loading && inputQuery.trim() !== searchedQuery.trim() && inputQuery.trim() !== ''
391+
326392
// Reset to hero state
327393
const handleNewSearch = () => {
328394
setHasSearched(false)
329395
setResults([])
330396
setSearchTime(null)
331-
setLastQuery('')
397+
setSearchedQuery('')
398+
setInputQuery('')
332399
window.scrollTo({ top: 0, behavior: 'smooth' })
333400
}
334401

@@ -359,7 +426,8 @@ export function LandingPage() {
359426

360427
setLoading(true)
361428
setHasSearched(true)
362-
setLastQuery(query)
429+
setSearchedQuery(query) // Track what was actually searched
430+
setInputQuery(query) // Sync input with searched
363431
setCurrentRepoId(repoId)
364432
setIsCustomRepo(isCustom)
365433
setRateLimitError(null)
@@ -396,8 +464,8 @@ export function LandingPage() {
396464

397465
// Re-search with updated query (from compact search bar)
398466
const handleReSearch = () => {
399-
if (lastQuery.trim() && currentRepoId) {
400-
handleSearch(lastQuery, currentRepoId, isCustomRepo)
467+
if (inputQuery.trim() && currentRepoId) {
468+
handleSearch(inputQuery, currentRepoId, isCustomRepo)
401469
}
402470
}
403471

@@ -435,8 +503,8 @@ export function LandingPage() {
435503
<div className="min-h-screen pt-16">
436504
{/* Compact Search Bar - sticky below nav */}
437505
<CompactSearchBar
438-
query={lastQuery}
439-
onQueryChange={setLastQuery}
506+
query={inputQuery}
507+
onQueryChange={setInputQuery}
440508
onSearch={handleReSearch}
441509
onNewSearch={handleNewSearch}
442510
loading={loading}
@@ -450,12 +518,26 @@ export function LandingPage() {
450518
<div className="flex items-center justify-between mb-6 animate-in fade-in duration-300">
451519
{loading ? (
452520
<span className="text-gray-400 text-sm">
453-
Searching for "<span className="text-blue-400">{lastQuery}</span>"...
521+
Searching for "<span className="text-blue-400">{inputQuery}</span>"...
454522
</span>
523+
) : isStale ? (
524+
<motion.div
525+
className="flex items-center gap-3"
526+
initial={{ opacity: 0, y: -10 }}
527+
animate={{ opacity: 1, y: 0 }}
528+
>
529+
<span className="text-amber-400 text-sm flex items-center gap-2">
530+
<span className="w-2 h-2 bg-amber-400 rounded-full animate-pulse" />
531+
Press Enter to search for "<span className="text-white">{inputQuery}</span>"
532+
</span>
533+
<span className="text-gray-600 text-xs">
534+
(showing results for "{searchedQuery}")
535+
</span>
536+
</motion.div>
455537
) : (
456538
<div className="flex items-center gap-4">
457539
<span className="text-gray-400 text-sm">
458-
<span className="text-white font-semibold">{results.length}</span> results for "<span className="text-blue-400">{lastQuery}</span>"
540+
<span className="text-white font-semibold">{results.length}</span> results for "<span className="text-blue-400">{searchedQuery}</span>"
459541
</span>
460542
{searchTime && (
461543
<span className="font-mono text-sm text-green-400">
@@ -495,11 +577,16 @@ export function LandingPage() {
495577

496578
<AnimatePresence mode="wait">
497579
<motion.div
498-
key={lastQuery}
499-
className="space-y-4"
580+
key={searchedQuery}
581+
className="space-y-6"
500582
initial={{ opacity: 0 }}
501-
animate={{ opacity: 1 }}
583+
animate={{
584+
opacity: isStale ? 0.4 : 1,
585+
filter: isStale ? 'blur(2px)' : 'blur(0px)',
586+
scale: isStale ? 0.98 : 1,
587+
}}
502588
exit={{ opacity: 0 }}
589+
transition={{ duration: 0.3 }}
503590
>
504591
{results.map((result, idx) => (
505592
<ResultCard key={`${result.file_path}-${result.name}-${idx}`} result={result} index={idx} />

0 commit comments

Comments
 (0)