@@ -8,12 +8,14 @@ type CanvasGridEffectProps = {
88
99type GridWave = {
1010 bornAt : number ;
11+ strength : number ;
1112 x : number ;
1213 y : number ;
1314} ;
1415
1516const GRID_TINT = [ 125 , 182 , 255 ] as const ;
16- const WAVE_LIFETIME = 1_250 ;
17+ const WAVE_LIFETIME = 1_650 ;
18+ const AMBIENT_WAVE_INTERVAL = 3_800 ;
1719
1820function clamp ( value : number , minimum : number , maximum : number ) {
1921 return Math . min ( Math . max ( value , minimum ) , maximum ) ;
@@ -51,6 +53,7 @@ export function CanvasGridEffect({
5153 let previousPointer = { x : - cellSize , y : - cellSize } ;
5254 let pointer = { x : - cellSize * 4 , y : - cellSize * 4 } ;
5355 let waves : GridWave [ ] = [ ] ;
56+ let ambientWaveIndex = 0 ;
5457 let width = 1 ;
5558 let height = 1 ;
5659 let pixelRatio = 1 ;
@@ -98,47 +101,75 @@ export function CanvasGridEffect({
98101
99102 for ( const wave of activeWaves ) {
100103 const progress = clamp ( ( now - wave . bornAt ) / WAVE_LIFETIME , 0 , 1 ) ;
101- const radius = progress * maxDimension * 0.72 ;
104+ const radius = progress * maxDimension * 0.78 ;
102105 const distance = Math . hypot ( centerX - wave . x , centerY - wave . y ) ;
103106 const ring = Math . exp (
104- - Math . pow ( ( distance - radius ) / ( cellSize * 1.35 ) , 2 ) ,
107+ - Math . pow ( ( distance - radius ) / ( cellSize * 1.5 ) , 2 ) ,
105108 ) ;
106- waveLift = Math . max ( waveLift , ring * ( 1 - progress ) ) ;
109+ waveLift = Math . max (
110+ waveLift ,
111+ ring * ( 1 - progress ) * wave . strength ,
112+ ) ;
113+ }
114+
115+ const lift = clamp ( pointerLift * 0.66 + waveLift , 0 , 1 ) ;
116+ const inset = 4 - lift * 2.35 ;
117+ const offsetY = - lift * 7 ;
118+ const alpha = intensity * ( 0.035 + lift * 0.22 ) ;
119+ const fillAlpha = intensity * ( 0.004 + lift * 0.045 ) ;
120+ const tileX = column * cellSize + inset ;
121+ const tileY = row * cellSize + inset + offsetY ;
122+ const tileWidth = cellSize - inset * 2 ;
123+ const depth = lift * 5 ;
124+
125+ if ( depth > 0.25 ) {
126+ drawingContext . fillStyle = `rgba(${ GRID_TINT . join ( ', ' ) } , ${ intensity * lift * 0.055 } )` ;
127+ drawingContext . beginPath ( ) ;
128+ drawingContext . moveTo ( tileX , tileY + tileWidth ) ;
129+ drawingContext . lineTo ( tileX + tileWidth , tileY + tileWidth ) ;
130+ drawingContext . lineTo ( tileX + tileWidth , tileY + tileWidth + depth ) ;
131+ drawingContext . lineTo ( tileX , tileY + tileWidth + depth ) ;
132+ drawingContext . closePath ( ) ;
133+ drawingContext . fill ( ) ;
134+
135+ drawingContext . fillStyle = `rgba(${ GRID_TINT . join ( ', ' ) } , ${ intensity * lift * 0.035 } )` ;
136+ drawingContext . beginPath ( ) ;
137+ drawingContext . moveTo ( tileX + tileWidth , tileY ) ;
138+ drawingContext . lineTo ( tileX + tileWidth , tileY + tileWidth ) ;
139+ drawingContext . lineTo ( tileX + tileWidth , tileY + tileWidth + depth ) ;
140+ drawingContext . lineTo ( tileX + tileWidth + depth , tileY + depth ) ;
141+ drawingContext . closePath ( ) ;
142+ drawingContext . fill ( ) ;
107143 }
108144
109- const lift = clamp ( pointerLift * 0.58 + waveLift , 0 , 1 ) ;
110- const inset = 4 - lift * 2.2 ;
111- const offsetY = - lift * 4 ;
112- const alpha = intensity * ( 0.018 + lift * 0.16 ) ;
113- const fillAlpha = intensity * lift * 0.025 ;
145+ drawingContext . fillStyle = `rgba(${ GRID_TINT . join ( ', ' ) } , ${ fillAlpha } )` ;
146+ drawingContext . fillRect ( tileX , tileY , tileWidth , tileWidth ) ;
114147
115148 drawingContext . strokeStyle = `rgba(${ GRID_TINT . join ( ', ' ) } , ${ alpha } )` ;
116149 drawingContext . lineWidth = 1 ;
117- drawingContext . strokeRect (
118- column * cellSize + inset ,
119- row * cellSize + inset + offsetY ,
120- cellSize - inset * 2 ,
121- cellSize - inset * 2 ,
122- ) ;
123-
124- if ( fillAlpha > 0.002 ) {
125- drawingContext . fillStyle = `rgba(${ GRID_TINT . join ( ', ' ) } , ${ fillAlpha } )` ;
126- drawingContext . fillRect (
127- column * cellSize + inset ,
128- row * cellSize + inset + offsetY ,
129- cellSize - inset * 2 ,
130- cellSize - inset * 2 ,
131- ) ;
132- }
150+ drawingContext . strokeRect ( tileX , tileY , tileWidth , tileWidth ) ;
133151 }
134152 }
135153
136154 if ( activeWaves . length > 0 ) scheduleDraw ( ) ;
137155 }
138156
139- const addWave = ( x : number , y : number , now : number ) => {
140- waves = [ ...waves . slice ( - 3 ) , { bornAt : now , x, y } ] ;
157+ const addWave = ( x : number , y : number , now : number , strength = 1 ) => {
158+ waves = [ ...waves . slice ( - 4 ) , { bornAt : now , strength , x, y } ] ;
141159 lastWaveAt = now ;
160+ scheduleDraw ( ) ;
161+ } ;
162+
163+ const addAmbientWave = ( ) => {
164+ if ( reducedMotion || ! isVisible || isPointerInside ) return ;
165+ const positions = [
166+ [ 0.72 , 0.3 ] ,
167+ [ 0.28 , 0.62 ] ,
168+ [ 0.58 , 0.78 ] ,
169+ ] as const ;
170+ const [ xRatio , yRatio ] = positions [ ambientWaveIndex % positions . length ] ;
171+ ambientWaveIndex += 1 ;
172+ addWave ( width * xRatio , height * yRatio , performance . now ( ) , 0.58 ) ;
142173 } ;
143174
144175 const handlePointerMove = ( event : PointerEvent ) => {
@@ -152,7 +183,7 @@ export function CanvasGridEffect({
152183 pointer = { x, y } ;
153184 isPointerInside = true ;
154185 if ( distance > cellSize * 1.25 && now - lastWaveAt > 90 ) {
155- addWave ( x , y , now ) ;
186+ addWave ( x , y , now , 1 ) ;
156187 previousPointer = { x, y } ;
157188 }
158189 scheduleDraw ( ) ;
@@ -181,9 +212,17 @@ export function CanvasGridEffect({
181212 host . addEventListener ( 'pointermove' , handlePointerMove ) ;
182213 host . addEventListener ( 'pointerleave' , handlePointerLeave ) ;
183214 resize ( ) ;
215+ if ( ! reducedMotion ) {
216+ addWave ( width * 0.72 , height * 0.32 , performance . now ( ) , 0.62 ) ;
217+ }
218+ const ambientWaveTimer = window . setInterval (
219+ addAmbientWave ,
220+ AMBIENT_WAVE_INTERVAL ,
221+ ) ;
184222
185223 return ( ) => {
186224 window . cancelAnimationFrame ( frame ) ;
225+ window . clearInterval ( ambientWaveTimer ) ;
187226 resizeObserver . disconnect ( ) ;
188227 intersectionObserver . disconnect ( ) ;
189228 motionPreference . removeEventListener ( 'change' , handleMotionChange ) ;
0 commit comments