@@ -67,6 +67,7 @@ function AnimatedSection({ children, className = '' }: { children: React.ReactNo
6767
6868export function LandingPage ( ) {
6969 const navigate = useNavigate ( )
70+ const resultsRef = useRef < HTMLElement > ( null )
7071 const [ results , setResults ] = useState < SearchResult [ ] > ( [ ] )
7172 const [ loading , setLoading ] = useState ( false )
7273 const [ searchTime , setSearchTime ] = useState < number | null > ( null )
@@ -77,6 +78,22 @@ export function LandingPage() {
7778 const [ rateLimitError , setRateLimitError ] = useState < string | null > ( null )
7879 const [ lastQuery , setLastQuery ] = useState ( '' )
7980
81+ // Scroll to results when they appear
82+ const scrollToResults = ( ) => {
83+ setTimeout ( ( ) => {
84+ resultsRef . current ?. scrollIntoView ( { behavior : 'smooth' , block : 'start' } )
85+ } , 100 )
86+ }
87+
88+ // Reset to search state
89+ const handleNewSearch = ( ) => {
90+ setHasSearched ( false )
91+ setResults ( [ ] )
92+ setSearchTime ( null )
93+ setLastQuery ( '' )
94+ window . scrollTo ( { top : 0 , behavior : 'smooth' } )
95+ }
96+
8097 // Fetch rate limit status on mount
8198 useEffect ( ( ) => {
8299 fetch ( `${ API_URL } /playground/limits` , { credentials : 'include' } )
@@ -126,6 +143,8 @@ export function LandingPage() {
126143 if ( typeof data . remaining_searches === 'number' ) {
127144 setRemaining ( data . remaining_searches )
128145 }
146+ // Scroll to results after they load
147+ scrollToResults ( )
129148 } else if ( data . status === 429 ) {
130149 setRateLimitError ( 'Daily limit reached. Sign up for unlimited searches!' )
131150 setRemaining ( 0 )
@@ -199,57 +218,90 @@ export function LandingPage() {
199218
200219 { /* ============ RESULTS SECTION (if searched) ============ */ }
201220 { hasSearched && (
202- < section className = "pb-20 px-6" >
221+ < section ref = { resultsRef } className = "pb-20 px-6 pt-8 animate-in fade-in slide-in-from-bottom-4 duration-500 " >
203222 < div className = "max-w-4xl mx-auto" >
223+ { /* Results Header */ }
204224 < div className = "flex items-center justify-between mb-6" >
205- < div className = "flex items-center gap-4 text-sm" >
206- < span className = "text-gray-400" > < span className = "text-white font-semibold" > { results . length } </ span > results</ span >
207- { searchTime && < > < span className = "text-gray-700" > •</ span > < span className = "font-mono text-green-400" > { searchTime } ms</ span > </ > }
225+ < div className = "flex items-center gap-4" >
226+ < button
227+ onClick = { handleNewSearch }
228+ className = "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-800/50 hover:bg-zinc-700/50 border border-zinc-700 text-sm text-zinc-300 hover:text-white transition-all"
229+ >
230+ < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
231+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M10 19l-7-7m0 0l7-7m-7 7h18" />
232+ </ svg >
233+ New Search
234+ </ button >
235+ < span className = "text-gray-500" > |</ span >
236+ < span className = "text-gray-400 text-sm" >
237+ < span className = "text-white font-semibold" > { results . length } </ span > results for "< span className = "text-blue-400" > { lastQuery } </ span > "
238+ </ span >
239+ { searchTime && (
240+ < span className = "font-mono text-sm text-green-400" >
241+ { searchTime > 1000 ? `${ ( searchTime / 1000 ) . toFixed ( 1 ) } s` : `${ searchTime } ms` }
242+ </ span >
243+ ) }
208244 </ div >
209245 { remaining > 0 && remaining < limit && (
210246 < div className = "text-sm text-gray-500" > { remaining } remaining</ div >
211247 ) }
212248 </ div >
213249
214- { ( remaining <= 0 || rateLimitError ) && (
215- < Card className = "bg-gradient-to-r from-blue-600/20 to-purple-600/20 border-blue-500/30 p-6 mb-6" >
216- < h3 className = "text-lg font-semibold mb-2" > You've reached today's limit</ h3 >
217- < p className = "text-gray-300 mb-4" >
218- { rateLimitError || 'Sign up to get unlimited searches and index your own repos.' }
219- </ p >
220- < Button onClick = { ( ) => navigate ( '/signup' ) } className = "bg-white text-black hover:bg-gray-100" > Get started — it's free</ Button >
221- </ Card >
250+ { /* Loading State */ }
251+ { loading && (
252+ < div className = "flex flex-col items-center justify-center py-20" >
253+ < div className = "relative" >
254+ < div className = "w-12 h-12 border-4 border-zinc-700 border-t-blue-500 rounded-full animate-spin" />
255+ </ div >
256+ < p className = "mt-4 text-zinc-400 text-sm" > Searching codebase...</ p >
257+ < p className = "text-zinc-600 text-xs mt-1" > This may take a few seconds for first search</ p >
258+ </ div >
222259 ) }
223260
224- < div className = "space-y-4" >
225- { results . map ( ( result , idx ) => (
226- < Card key = { idx } className = "bg-[#111113] border-white/5 overflow-hidden hover:border-white/10 transition-all" >
227- < div className = "px-5 py-4 border-b border-white/5 flex items-start justify-between" >
228- < div >
229- < div className = "flex items-center gap-3" >
230- < h3 className = "font-mono font-semibold" > { result . name } </ h3 >
231- < Badge variant = "outline" className = "text-[10px] text-gray-400 border-gray-700" > { result . type . replace ( '_' , ' ' ) } </ Badge >
261+ { /* Results Content (only when not loading) */ }
262+ { ! loading && (
263+ < >
264+ { ( remaining <= 0 || rateLimitError ) && (
265+ < Card className = "bg-gradient-to-r from-blue-600/20 to-purple-600/20 border-blue-500/30 p-6 mb-6" >
266+ < h3 className = "text-lg font-semibold mb-2" > You've reached today's limit</ h3 >
267+ < p className = "text-gray-300 mb-4" >
268+ { rateLimitError || 'Sign up to get unlimited searches and index your own repos.' }
269+ </ p >
270+ < Button onClick = { ( ) => navigate ( '/signup' ) } className = "bg-white text-black hover:bg-gray-100" > Get started — it's free</ Button >
271+ </ Card >
272+ ) }
273+
274+ < div className = "space-y-4" >
275+ { results . map ( ( result , idx ) => (
276+ < Card key = { idx } className = "bg-[#111113] border-white/5 overflow-hidden hover:border-white/10 transition-all hover:scale-[1.01] duration-200" >
277+ < div className = "px-5 py-4 border-b border-white/5 flex items-start justify-between" >
278+ < div >
279+ < div className = "flex items-center gap-3" >
280+ < h3 className = "font-mono font-semibold" > { result . name } </ h3 >
281+ < Badge variant = "outline" className = "text-[10px] text-gray-400 border-gray-700" > { result . type . replace ( '_' , ' ' ) } </ Badge >
282+ </ div >
283+ < p className = "text-sm text-gray-500 font-mono mt-1" > { result . file_path . split ( '/' ) . slice ( - 2 ) . join ( '/' ) } </ p >
284+ </ div >
285+ < div className = "text-right" >
286+ < div className = "text-2xl font-bold text-blue-400" > { ( result . score * 100 ) . toFixed ( 0 ) } %</ div >
287+ < div className = "text-[10px] text-gray-500 uppercase tracking-wider" > match</ div >
288+ </ div >
232289 </ div >
233- < p className = "text-sm text-gray-500 font-mono mt-1" > { result . file_path . split ( '/' ) . slice ( - 2 ) . join ( '/' ) } </ p >
234- </ div >
235- < div className = "text-right" >
236- < div className = "text-2xl font-bold text-blue-400" > { ( result . score * 100 ) . toFixed ( 0 ) } %</ div >
237- < div className = "text-[10px] text-gray-500 uppercase tracking-wider" > match</ div >
238- </ div >
239- </ div >
240- < SyntaxHighlighter language = { result . language || 'python' } style = { oneDark } customStyle = { { margin : 0 , borderRadius : 0 , fontSize : '0.8rem' , background : '#0d0d0f' } } showLineNumbers startingLineNumber = { result . line_start || 1 } >
241- { result . code }
242- </ SyntaxHighlighter >
243- </ Card >
244- ) ) }
245- </ div >
290+ < SyntaxHighlighter language = { result . language || 'python' } style = { oneDark } customStyle = { { margin : 0 , borderRadius : 0 , fontSize : '0.8rem' , background : '#0d0d0f' } } showLineNumbers startingLineNumber = { result . line_start || 1 } >
291+ { result . code }
292+ </ SyntaxHighlighter >
293+ </ Card >
294+ ) ) }
295+ </ div >
246296
247- { results . length === 0 && ! loading && (
248- < div className = "text-center py-16" >
249- < div className = "text-5xl mb-4" > 🔍</ div >
250- < h3 className = "text-lg font-semibold mb-2" > No results found</ h3 >
251- < p className = "text-gray-500" > Try a different query</ p >
252- </ div >
297+ { results . length === 0 && (
298+ < div className = "text-center py-16" >
299+ < div className = "text-5xl mb-4" > 🔍</ div >
300+ < h3 className = "text-lg font-semibold mb-2" > No results found</ h3 >
301+ < p className = "text-gray-500" > Try a different query</ p >
302+ </ div >
303+ ) }
304+ </ >
253305 ) }
254306 </ div >
255307 </ section >
0 commit comments