@@ -128,12 +128,14 @@ <h2 id="earthStoryCardTitle">Earth Story</h2>
128128 < iframe id ="storyFrame " title ="Earth Story " src ="about:blank "> </ iframe >
129129 </ div >
130130
131- <!-- Static starfield -->
131+ <!-- Animated starfield -->
132132 < script >
133133 ( function ( ) {
134134 const canvas = document . getElementById ( 'starfieldCanvas' ) ;
135135 const ctx = canvas . getContext ( '2d' ) ;
136136
137+ let stars = [ ] , vigGrad , w , h , animId ;
138+
137139 function rng ( seed ) {
138140 let s = seed ;
139141 return function ( ) {
@@ -144,44 +146,73 @@ <h2 id="earthStoryCardTitle">Earth Story</h2>
144146 } ;
145147 }
146148
147- function draw ( ) {
149+ function init ( ) {
148150 const dpr = window . devicePixelRatio || 1 ;
149- const w = window . innerWidth ;
150- const h = window . innerHeight ;
151+ w = window . innerWidth ;
152+ h = window . innerHeight ;
151153 canvas . width = w * dpr ;
152154 canvas . height = h * dpr ;
153155 canvas . style . width = w + 'px' ;
154156 canvas . style . height = h + 'px' ;
155157 ctx . setTransform ( dpr , 0 , 0 , dpr , 0 , 0 ) ;
156158
159+ vigGrad = ctx . createRadialGradient ( w / 2 , h / 2 , h * 0.2 , w / 2 , h / 2 , h * 0.9 ) ;
160+ vigGrad . addColorStop ( 0 , 'rgba(12,17,22,0)' ) ;
161+ vigGrad . addColorStop ( 1 , 'rgba(12,17,22,0.55)' ) ;
162+
163+ const rand = rng ( 42 ) ;
164+ const count = Math . round ( w * h / 1400 ) ;
165+ stars = [ ] ;
166+ for ( let i = 0 ; i < count ; i ++ ) {
167+ stars . push ( {
168+ x : rand ( ) * w ,
169+ y : rand ( ) * h ,
170+ size : rand ( ) * 1.4 + 0.3 ,
171+ baseAlpha : rand ( ) * 0.55 + 0.15 ,
172+ twinklePhase : rand ( ) * Math . PI * 2 ,
173+ twinkleSpeed : rand ( ) * 0.003 + 0.001 ,
174+ twinkleAmp : rand ( ) * 0.35 + 0.1 ,
175+ driftPhaseX : rand ( ) * Math . PI * 2 ,
176+ driftPhaseY : rand ( ) * Math . PI * 2 ,
177+ driftSpeedX : rand ( ) * 0.0001 + 0.00003 ,
178+ driftSpeedY : rand ( ) * 0.0001 + 0.00003 ,
179+ driftAmpX : rand ( ) * 1.5 + 0.5 ,
180+ driftAmpY : rand ( ) * 1.5 + 0.5 ,
181+ } ) ;
182+ }
183+ }
184+
185+ function animate ( ts ) {
157186 ctx . fillStyle = '#0C1116' ;
158187 ctx . fillRect ( 0 , 0 , w , h ) ;
159188
160- const rand = rng ( 42 ) ;
161- const count = Math . round ( w * h / 1400 ) ; /* ~850 stars on 1080p */
162-
163- for ( let i = 0 ; i < count ; i ++ ) {
164- const x = rand ( ) * w ;
165- const y = rand ( ) * h ;
166- const size = rand ( ) * 1.4 + 0.3 ;
167- const alpha = rand ( ) * 0.7 + 0.15 ;
189+ for ( const s of stars ) {
190+ const alpha = Math . max ( 0.05 , Math . min ( 0.9 ,
191+ s . baseAlpha + Math . sin ( ts * s . twinkleSpeed + s . twinklePhase ) * s . twinkleAmp
192+ ) ) ;
193+ const x = s . x + Math . sin ( ts * s . driftSpeedX + s . driftPhaseX ) * s . driftAmpX ;
194+ const y = s . y + Math . sin ( ts * s . driftSpeedY + s . driftPhaseY ) * s . driftAmpY ;
168195
169196 ctx . beginPath ( ) ;
170- ctx . arc ( x , y , size , 0 , Math . PI * 2 ) ;
197+ ctx . arc ( x , y , s . size , 0 , Math . PI * 2 ) ;
171198 ctx . fillStyle = `rgba(255,255,255,${ alpha . toFixed ( 2 ) } )` ;
172199 ctx . fill ( ) ;
173200 }
174201
175- /* faint radial vignette so the edges feel like deep space */
176- const grad = ctx . createRadialGradient ( w / 2 , h / 2 , h * 0.2 , w / 2 , h / 2 , h * 0.9 ) ;
177- grad . addColorStop ( 0 , 'rgba(12,17,22,0)' ) ;
178- grad . addColorStop ( 1 , 'rgba(12,17,22,0.55)' ) ;
179- ctx . fillStyle = grad ;
202+ ctx . fillStyle = vigGrad ;
180203 ctx . fillRect ( 0 , 0 , w , h ) ;
204+
205+ animId = requestAnimationFrame ( animate ) ;
181206 }
182207
183- draw ( ) ;
184- window . addEventListener ( 'resize' , draw ) ;
208+ init ( ) ;
209+ animId = requestAnimationFrame ( animate ) ;
210+
211+ window . addEventListener ( 'resize' , ( ) => {
212+ cancelAnimationFrame ( animId ) ;
213+ init ( ) ;
214+ animId = requestAnimationFrame ( animate ) ;
215+ } ) ;
185216 } ) ( ) ;
186217 </ script >
187218
0 commit comments