11import { type RefObject , useEffect } from 'react'
22
3- type StarType = 'tiny' | 'medium' | 'bright'
4-
5- interface Star {
3+ interface Particle {
64 x : number ; y : number
75 vx : number ; vy : number
86 r : number
9- glowR : number
10- alpha : number
117 baseAlpha : number
8+ alpha : number
129 phase : number
1310 phaseSpd : number
14- cr : number ; cg : number ; cb : number // color components
15- type : StarType
16- spikes : boolean
11+ cr : number ; cg : number ; cb : number
1712}
1813
19- // Star color palette — white, blue-white, warm-white, cyan-white, lavender
2014const COLORS : [ number , number , number ] [ ] = [
2115 [ 255 , 255 , 255 ] ,
22- [ 210 , 230 , 255 ] ,
23- [ 180 , 210 , 255 ] ,
24- [ 255 , 248 , 210 ] ,
25- [ 160 , 225 , 255 ] ,
26- [ 220 , 205 , 255 ] ,
27- [ 200 , 240 , 255 ] ,
16+ [ 210 , 228 , 255 ] ,
17+ [ 190 , 215 , 255 ] ,
18+ [ 255 , 248 , 220 ] ,
19+ [ 200 , 238 , 255 ] ,
2820]
2921
3022export function useStarfield ( canvasRef : RefObject < HTMLCanvasElement | null > ) {
@@ -37,10 +29,10 @@ export function useStarfield(canvasRef: RefObject<HTMLCanvasElement | null>) {
3729 const rnd = ( a : number , b : number ) => a + Math . random ( ) * ( b - a )
3830
3931 let W = 0 , H = 0
40- let stars : Star [ ] = [ ]
32+ let particles : Particle [ ] = [ ]
4133 let t = 0 , rafId = 0 , paused = false
4234
43- const count = ( ) => Math . min ( Math . floor ( ( window . innerWidth * window . innerHeight ) / 2800 ) , 520 )
35+ const count = ( ) => Math . min ( Math . floor ( ( window . innerWidth * window . innerHeight ) / 1400 ) , 900 )
4436 const isLight = ( ) => document . documentElement . dataset . theme === 'light'
4537
4638 function resize ( ) {
@@ -53,117 +45,63 @@ export function useStarfield(canvasRef: RefObject<HTMLCanvasElement | null>) {
5345 ctx . scale ( dpr , dpr )
5446 }
5547
56- function mkStar ( ) : Star {
57- const roll = Math . random ( )
58- const type : StarType = roll < 0.12 ? 'bright' : roll < 0.38 ? 'medium' : 'tiny'
48+ function mkParticle ( ) : Particle {
49+ const bright = Math . random ( ) < 0.08 // 8% ligeramente más visibles
5950 const [ cr , cg , cb ] = COLORS [ Math . floor ( Math . random ( ) * COLORS . length ) ]
60-
61- const r = type === 'bright' ? rnd ( 1.6 , 2.8 ) : type === 'medium' ? rnd ( 0.8 , 1.5 ) : rnd ( 0.25 , 0.75 )
62- const speed = type === 'bright' ? 0.04 : type === 'medium' ? 0.07 : 0.12
63-
6451 return {
6552 x : rnd ( 0 , W / dpr ) ,
6653 y : rnd ( 0 , H / dpr ) ,
67- vx : rnd ( - speed , speed ) ,
68- vy : rnd ( - speed * 0.8 , speed * 0.8 ) ,
69- r,
70- glowR : type === 'bright' ? rnd ( 10 , 20 ) : rnd ( 3 , 7 ) ,
71- baseAlpha : type === 'bright' ? rnd ( 0.75 , 1.0 ) : type === 'medium' ? rnd ( 0.4 , 0.75 ) : rnd ( 0.15 , 0.55 ) ,
54+ vx : rnd ( - 0.06 , 0.06 ) ,
55+ vy : rnd ( - 0.05 , 0.05 ) ,
56+ r : bright ? rnd ( 0.6 , 1.1 ) : rnd ( 0.15 , 0.55 ) ,
57+ baseAlpha : bright ? rnd ( 0.5 , 0.9 ) : rnd ( 0.08 , 0.45 ) ,
7258 alpha : 0 ,
7359 phase : rnd ( 0 , PI2 ) ,
74- phaseSpd : type === 'bright' ? rnd ( 0.004 , 0.012 ) : rnd ( 0.01 , 0.03 ) ,
60+ phaseSpd : rnd ( 0.005 , 0.022 ) ,
7561 cr, cg, cb,
76- type,
77- spikes : type === 'bright' && Math . random ( ) < 0.65 ,
7862 }
7963 }
8064
8165 function init ( ) {
82- stars = Array . from ( { length : count ( ) } , mkStar )
83- }
84-
85- function drawSpikes ( x : number , y : number , r : number , cr : number , cg : number , cb : number , a : number ) {
86- const len = r * 14
87- const lw = r * 0.7
88- for ( let i = 0 ; i < 4 ; i ++ ) {
89- const angle = ( i * Math . PI ) / 2
90- const ex = x + Math . cos ( angle ) * len
91- const ey = y + Math . sin ( angle ) * len
92- const grd = ctx . createLinearGradient ( x , y , ex , ey )
93- grd . addColorStop ( 0 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.75 } )` )
94- grd . addColorStop ( 0.4 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.2 } )` )
95- grd . addColorStop ( 1 , `rgba(${ cr } ,${ cg } ,${ cb } ,0)` )
96- ctx . beginPath ( )
97- ctx . moveTo ( x , y )
98- ctx . lineTo ( ex , ey )
99- ctx . strokeStyle = grd
100- ctx . lineWidth = lw
101- ctx . stroke ( )
102- }
66+ particles = Array . from ( { length : count ( ) } , mkParticle )
10367 }
10468
10569 function draw ( ) {
10670 const W2 = W / dpr , H2 = H / dpr
10771 ctx . clearRect ( 0 , 0 , W2 , H2 )
10872 t ++
10973
110- const lightMode = isLight ( )
74+ const lightFactor = isLight ( ) ? 0.2 : 1
11175
112- for ( const s of stars ) {
113- // drift
114- s . x += s . vx
115- s . y += s . vy
116- if ( s . x < - 15 ) s . x = W2 + 15
117- if ( s . x > W2 + 15 ) s . x = - 15
118- if ( s . y < - 15 ) s . y = H2 + 15
119- if ( s . y > H2 + 15 ) s . y = - 15
76+ for ( const p of particles ) {
77+ p . x += p . vx
78+ p . y += p . vy
79+ if ( p . x < - 5 ) p . x = W2 + 5
80+ if ( p . x > W2 + 5 ) p . x = - 5
81+ if ( p . y < - 5 ) p . y = H2 + 5
82+ if ( p . y > H2 + 5 ) p . y = - 5
12083
121- // twinkle
122- const tw = 0.35 + 0.65 * ( 0.5 + 0.5 * Math . sin ( t * s . phaseSpd + s . phase ) )
123- // light mode: reduce visibility significantly
124- const alphaScale = lightMode ? 0.25 : 1
125- s . alpha = s . baseAlpha * tw * alphaScale
84+ const tw = 0.4 + 0.6 * ( 0.5 + 0.5 * Math . sin ( t * p . phaseSpd + p . phase ) )
85+ p . alpha = p . baseAlpha * tw * lightFactor
12686
127- const { cr, cg, cb } = s
128- const a = s . alpha
87+ const { cr, cg, cb } = p
88+ const a = p . alpha
12989
130- if ( s . type === 'tiny' ) {
131- // simple dot only — no gradient cost
90+ // halo muy sutil solo para las levemente más grandes
91+ if ( p . r > 0.5 ) {
92+ const grd = ctx . createRadialGradient ( p . x , p . y , 0 , p . x , p . y , p . r * 3.5 )
93+ grd . addColorStop ( 0 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.18 } )` )
94+ grd . addColorStop ( 1 , `rgba(${ cr } ,${ cg } ,${ cb } ,0)` )
13295 ctx . beginPath ( )
133- ctx . arc ( s . x , s . y , s . r , 0 , PI2 )
134- ctx . fillStyle = `rgba( ${ cr } , ${ cg } , ${ cb } , ${ a } )`
96+ ctx . arc ( p . x , p . y , p . r * 3.5 , 0 , PI2 )
97+ ctx . fillStyle = grd
13598 ctx . fill ( )
136- continue
137- }
138-
139- // outer glow
140- const grd = ctx . createRadialGradient ( s . x , s . y , 0 , s . x , s . y , s . glowR )
141- if ( s . type === 'bright' ) {
142- grd . addColorStop ( 0 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.35 } )` )
143- grd . addColorStop ( 0.3 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.12 } )` )
144- grd . addColorStop ( 1 , `rgba(${ cr } ,${ cg } ,${ cb } ,0)` )
145- } else {
146- grd . addColorStop ( 0 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.2 } )` )
147- grd . addColorStop ( 1 , `rgba(${ cr } ,${ cg } ,${ cb } ,0)` )
148- }
149- ctx . beginPath ( )
150- ctx . arc ( s . x , s . y , s . glowR , 0 , PI2 )
151- ctx . fillStyle = grd
152- ctx . fill ( )
153-
154- // spikes for bright stars
155- if ( s . spikes ) {
156- drawSpikes ( s . x , s . y , s . r , cr , cg , cb , a )
15799 }
158100
159- // bright core with radial gradient
160- const core = ctx . createRadialGradient ( s . x , s . y , 0 , s . x , s . y , s . r * 2 )
161- core . addColorStop ( 0 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a } )` )
162- core . addColorStop ( 0.4 , `rgba(${ cr } ,${ cg } ,${ cb } ,${ a * 0.8 } )` )
163- core . addColorStop ( 1 , `rgba(${ cr } ,${ cg } ,${ cb } ,0)` )
101+ // punto central
164102 ctx . beginPath ( )
165- ctx . arc ( s . x , s . y , s . r * 2 , 0 , PI2 )
166- ctx . fillStyle = core
103+ ctx . arc ( p . x , p . y , p . r , 0 , PI2 )
104+ ctx . fillStyle = `rgba( ${ cr } , ${ cg } , ${ cb } , ${ a } )`
167105 ctx . fill ( )
168106 }
169107 }
0 commit comments