@@ -5,14 +5,28 @@ import { useTheme } from "next-themes";
55
66import { HeroCredit } from "./hero/HeroCredit" ;
77import { HERO_UNIFORM_SIZE } from "./hero/prelude" ;
8- import { createPass , type Pass } from "./hero/runner" ;
9- import type { HeroContext , ShaderEntry } from "./hero/types" ;
8+ import {
9+ createFadeOverlay ,
10+ createPass ,
11+ type FadeOverlay ,
12+ type Pass ,
13+ } from "./hero/runner" ;
14+ import { HERO_BACKGROUND , type HeroContext , type ShaderEntry } from "./hero/types" ;
1015
1116interface HeroShaderProps {
1217 entry : ShaderEntry ;
1318 className ?: string ;
1419}
1520
21+ // Seconds over which the shader fades in from the background color.
22+ const FADE_IN_SECONDS = 1 ;
23+
24+ const hexToRgb = ( hex : string ) : [ number , number , number ] => [
25+ parseInt ( hex . slice ( 1 , 3 ) , 16 ) / 255 ,
26+ parseInt ( hex . slice ( 3 , 5 ) , 16 ) / 255 ,
27+ parseInt ( hex . slice ( 5 , 7 ) , 16 ) / 255 ,
28+ ] ;
29+
1630export function HeroShader ( { entry, className } : HeroShaderProps ) {
1731 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
1832 const { resolvedTheme } = useTheme ( ) ;
@@ -65,6 +79,7 @@ export function HeroShader({ entry, className }: HeroShaderProps) {
6579 let device : GPUDevice | null = null ;
6680 let uniformBuffer : GPUBuffer | null = null ;
6781 let pass : Pass | null = null ;
82+ let fade : FadeOverlay | null = null ;
6883
6984 ( async ( ) => {
7085 const adapter = await navigator . gpu . requestAdapter ( ) ;
@@ -119,6 +134,11 @@ export function HeroShader({ entry, className }: HeroShaderProps) {
119134 pass = createPass ( ctx , entry . shader , uniformBuffer , { isMobile } ) ;
120135 pass . resize ( canvas . width , canvas . height ) ;
121136
137+ fade = createFadeOverlay (
138+ ctx ,
139+ hexToRgb ( HERO_BACKGROUND [ entry . appearance !== "light" ? "dark" : "light" ] ) ,
140+ ) ;
141+
122142 const shaderStart = performance . now ( ) ;
123143 let frame = 0 ;
124144 const uniforms = new Float32Array ( HERO_UNIFORM_SIZE / 4 ) ;
@@ -150,7 +170,13 @@ export function HeroShader({ entry, className }: HeroShaderProps) {
150170 pass ?. update ( frame , time ) ;
151171
152172 const encoder = device . createCommandEncoder ( ) ;
153- pass ?. encode ( encoder , context . getCurrentTexture ( ) . createView ( ) ) ;
173+ const target = context . getCurrentTexture ( ) . createView ( ) ;
174+ pass ?. encode ( encoder , target ) ;
175+ // Fade in from the background color over the first second.
176+ const fadeAlpha = 1 - Math . min ( 1 , time / FADE_IN_SECONDS ) ;
177+ if ( fadeAlpha > 0 ) {
178+ fade ?. encode ( encoder , target , fadeAlpha ) ;
179+ }
154180 device . queue . submit ( [ encoder . finish ( ) ] ) ;
155181
156182 frame += 1 ;
@@ -168,6 +194,7 @@ export function HeroShader({ entry, className }: HeroShaderProps) {
168194 disposed = true ;
169195 cancelAnimationFrame ( animationId ) ;
170196 pass ?. destroy ( ) ;
197+ fade ?. destroy ( ) ;
171198 uniformBuffer ?. destroy ( ) ;
172199 device ?. destroy ( ) ;
173200 } ;
@@ -199,7 +226,7 @@ export function HeroShader({ entry, className }: HeroShaderProps) {
199226 < canvas
200227 ref = { canvasRef }
201228 className = "block h-full w-full"
202- style = { { background : dark ? "#050508 " : "#f6f6fa" } }
229+ style = { { background : HERO_BACKGROUND [ dark ? "dark " : "light" ] } }
203230 />
204231 < HeroCredit entry = { entry } dark = { dark } />
205232 </ div >
0 commit comments