11import { useNavigate } from "react-router-dom" ;
2+ import { useEffect , useRef } from "react" ;
23
34export default function Landing ( ) {
45 const navigate = useNavigate ( ) ;
6+ const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
7+
8+ useEffect ( ( ) => {
9+ const canvas = canvasRef . current ! ;
10+ const ctx = canvas . getContext ( "2d" ) ! ;
11+ let animId : number ;
12+
13+ type Node = { x : number ; y : number ; vx : number ; vy : number ; r : number ; pulse : number } ;
14+ let nodes : Node [ ] = [ ] ;
15+
16+ const resize = ( ) => {
17+ canvas . width = window . innerWidth ;
18+ canvas . height = window . innerHeight ;
19+ initNodes ( ) ;
20+ } ;
21+
22+ const initNodes = ( ) => {
23+ const count = Math . floor ( ( canvas . width * canvas . height ) / 18000 ) ;
24+ nodes = Array . from ( { length : count } , ( ) => ( {
25+ x : Math . random ( ) * canvas . width ,
26+ y : Math . random ( ) * canvas . height ,
27+ vx : ( Math . random ( ) - 0.5 ) * 0.28 ,
28+ vy : ( Math . random ( ) - 0.5 ) * 0.28 ,
29+ r : Math . random ( ) * 1.8 + 0.8 ,
30+ pulse : Math . random ( ) * Math . PI * 2 ,
31+ } ) ) ;
32+ } ;
33+
34+ const draw = ( ) => {
35+ const { width : W , height : H } = canvas ;
36+ ctx . clearRect ( 0 , 0 , W , H ) ;
37+ for ( const n of nodes ) {
38+ n . x += n . vx ; n . y += n . vy ; n . pulse += 0.012 ;
39+ if ( n . x < 0 || n . x > W ) n . vx *= - 1 ;
40+ if ( n . y < 0 || n . y > H ) n . vy *= - 1 ;
41+ }
42+ const maxDist = 130 ;
43+ for ( let i = 0 ; i < nodes . length ; i ++ ) {
44+ for ( let j = i + 1 ; j < nodes . length ; j ++ ) {
45+ const dx = nodes [ i ] . x - nodes [ j ] . x ;
46+ const dy = nodes [ i ] . y - nodes [ j ] . y ;
47+ const d = Math . sqrt ( dx * dx + dy * dy ) ;
48+ if ( d < maxDist ) {
49+ const alpha = ( 1 - d / maxDist ) * 0.35 ;
50+ ctx . strokeStyle = Math . random ( ) > 0.85
51+ ? `rgba(99,102,241,${ alpha } )`
52+ : `rgba(56,189,248,${ alpha * 0.7 } )` ;
53+ ctx . lineWidth = 0.6 ;
54+ ctx . beginPath ( ) ;
55+ ctx . moveTo ( nodes [ i ] . x , nodes [ i ] . y ) ;
56+ ctx . lineTo ( nodes [ j ] . x , nodes [ j ] . y ) ;
57+ ctx . stroke ( ) ;
58+ }
59+ }
60+ }
61+ for ( const n of nodes ) {
62+ const pulse = 0.5 + 0.5 * Math . sin ( n . pulse ) ;
63+ ctx . beginPath ( ) ;
64+ ctx . arc ( n . x , n . y , n . r * ( 0.85 + 0.15 * pulse ) , 0 , Math . PI * 2 ) ;
65+ ctx . fillStyle = `rgba(56,189,248,${ 0.5 + 0.4 * pulse } )` ;
66+ ctx . fill ( ) ;
67+ }
68+ animId = requestAnimationFrame ( draw ) ;
69+ } ;
70+
71+ resize ( ) ;
72+ draw ( ) ;
73+ window . addEventListener ( "resize" , resize ) ;
74+ return ( ) => { cancelAnimationFrame ( animId ) ; window . removeEventListener ( "resize" , resize ) ; } ;
75+ } , [ ] ) ;
576
677 return (
7- < div className = "min-h-screen flex flex-col items-center justify-center px-6 bg-white" >
8-
9- < h1 className = "text-4xl font-bold mb-4 text-center" >
10- CodeAtlas
11- </ h1 >
12-
13- < p className = "text-gray-500 mb-8 text-center max-w-xl" >
14- Understand any codebase visually.
15- Turn repositories into interactive dependency graphs.
16- </ p >
17-
18- < button
19- onClick = { ( ) => navigate ( "/login" ) }
20- className = "bg-black text-white px-6 py-2 rounded"
21- >
22- Get Started
23- </ button >
24-
25- < p className = "mt-4 text-sm text-gray-400" >
26- No setup required
27- </ p >
28- </ div >
78+ < >
79+ < style > { `
80+ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=JetBrains+Mono:wght@300;400&display=swap');
81+
82+ .ca-root {
83+ background: #050810;
84+ color: #e8eaf0;
85+ font-family: 'Syne', sans-serif;
86+ min-height: 100vh;
87+ overflow: hidden;
88+ display: flex;
89+ align-items: center;
90+ justify-content: center;
91+ position: relative;
92+ }
93+ .ca-canvas { position: fixed; inset: 0; z-index: 0; opacity: 0.55; }
94+ .ca-grid {
95+ position: fixed; inset: 0; z-index: 0; pointer-events: none;
96+ background-image:
97+ linear-gradient(rgba(56,189,248,0.03) 1px, transparent 1px),
98+ linear-gradient(90deg, rgba(56,189,248,0.03) 1px, transparent 1px);
99+ background-size: 60px 60px;
100+ }
101+ .ca-orb {
102+ position: fixed; border-radius: 50%; filter: blur(80px); pointer-events: none; z-index: 0;
103+ }
104+ .ca-orb1 {
105+ width: 400px; height: 400px;
106+ background: radial-gradient(circle, rgba(56,189,248,0.12) 0%, transparent 70%);
107+ top: -100px; right: -80px;
108+ }
109+ .ca-orb2 {
110+ width: 300px; height: 300px;
111+ background: radial-gradient(circle, rgba(99,102,241,0.10) 0%, transparent 70%);
112+ bottom: -80px; left: -60px;
113+ }
114+ .ca-container {
115+ position: relative; z-index: 10;
116+ display: flex; flex-direction: column; align-items: center; text-align: center;
117+ padding: 2rem; max-width: 680px;
118+ animation: caFadeUp 0.9s cubic-bezier(0.16,1,0.3,1) both;
119+ }
120+ @keyframes caFadeUp {
121+ from { opacity: 0; transform: translateY(28px); }
122+ to { opacity: 1; transform: translateY(0); }
123+ }
124+ .ca-eyebrow {
125+ font-family: 'JetBrains Mono', monospace; font-size: 0.7rem;
126+ font-weight: 300; letter-spacing: 0.22em; text-transform: uppercase;
127+ color: #38bdf8; border: 1px solid rgba(56,189,248,0.25);
128+ padding: 0.35rem 1rem; border-radius: 999px; margin-bottom: 2.2rem;
129+ background: rgba(56,189,248,0.06);
130+ animation: caFadeUp 0.9s 0.1s cubic-bezier(0.16,1,0.3,1) both;
131+ }
132+ .ca-h1 {
133+ font-size: clamp(3.8rem, 10vw, 6rem); font-weight: 800;
134+ line-height: 0.95; letter-spacing: -0.03em; margin-bottom: 1.6rem;
135+ animation: caFadeUp 0.9s 0.18s cubic-bezier(0.16,1,0.3,1) both;
136+ background: linear-gradient(135deg, #ffffff 30%, #94a3b8 100%);
137+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
138+ }
139+ .ca-accent {
140+ background: linear-gradient(90deg, #38bdf8, #818cf8);
141+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
142+ }
143+ .ca-p {
144+ font-family: 'JetBrains Mono', monospace; font-size: 0.95rem; font-weight: 300;
145+ line-height: 1.75; color: #94a3b8; max-width: 440px; margin-bottom: 3rem;
146+ animation: caFadeUp 0.9s 0.26s cubic-bezier(0.16,1,0.3,1) both;
147+ }
148+ .ca-btn-group {
149+ display: flex; flex-direction: column; align-items: center; gap: 0.9rem;
150+ animation: caFadeUp 0.9s 0.34s cubic-bezier(0.16,1,0.3,1) both;
151+ }
152+ .ca-btn {
153+ position: relative; font-family: 'Syne', sans-serif; font-size: 0.9rem;
154+ font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
155+ background: linear-gradient(135deg, #38bdf8, #6366f1);
156+ color: #fff; border: none; padding: 0.85rem 2.6rem; border-radius: 4px;
157+ cursor: pointer; overflow: hidden;
158+ transition: transform 0.18s ease, box-shadow 0.18s ease;
159+ box-shadow: 0 0 0 1px rgba(56,189,248,0.3), 0 8px 32px rgba(56,189,248,0.18);
160+ }
161+ .ca-btn:hover {
162+ transform: translateY(-2px);
163+ box-shadow: 0 0 0 1px rgba(56,189,248,0.5), 0 16px 48px rgba(56,189,248,0.28);
164+ }
165+ .ca-btn:active { transform: translateY(0); }
166+ .ca-sub {
167+ font-family: 'JetBrains Mono', monospace; font-size: 0.7rem;
168+ color: #475569; letter-spacing: 0.1em;
169+ }
170+ ` } </ style >
171+
172+ < div className = "ca-root" >
173+ < canvas ref = { canvasRef } className = "ca-canvas" />
174+ < div className = "ca-grid" />
175+ < div className = "ca-orb ca-orb1" />
176+ < div className = "ca-orb ca-orb2" />
177+
178+ < div className = "ca-container" >
179+ < div className = "ca-eyebrow" > Visual Code Intelligence</ div >
180+ < h1 className = "ca-h1" > Code< span className = "ca-accent" > Atlas</ span > </ h1 >
181+ < p className = "ca-p" >
182+ Understand any codebase visually.< br />
183+ Turn repositories into interactive dependency graphs.
184+ </ p >
185+ < div className = "ca-btn-group" >
186+ < button className = "ca-btn" onClick = { ( ) => navigate ( "/login" ) } >
187+ Get Started
188+ </ button >
189+ < span className = "ca-sub" > No setup required</ span >
190+ </ div >
191+ </ div >
192+ </ div >
193+ </ >
29194 ) ;
30195}
0 commit comments