1-
21import { useEffect , useRef } from 'react' ;
32
4- const MatrixBackground = ( ) => {
3+ // --- For reusability, we can define props for customization ---
4+ interface ConsoleLogBackgroundProps {
5+ /** The font size of the log text */
6+ fontSize ?: number ;
7+ /** The interval in milliseconds to add a new line */
8+ newLineInterval ?: number ;
9+ /** Opacity of the canvas element */
10+ opacity ?: number ;
11+ }
12+
13+ // --- Define a type for our log lines for better structure ---
14+ interface LogLine {
15+ text : string ;
16+ color : string ;
17+ }
18+
19+ // --- A helper function to generate realistic, dynamic log lines ---
20+ const generateLogLine = ( ) : LogLine => {
21+ const logLevels = [
22+ { prefix : '[INFO]' , color : '#888' } ,
23+ { prefix : '[SUCCESS]' , color : '#2E7D32' } , // Darker Green
24+ { prefix : '[WARN]' , color : '#ED6C02' } , // Orange
25+ { prefix : '[ERROR]' , color : '#D32F2F' } , // Red
26+ { prefix : '[DEBUG]' , color : '#0288D1' } , // Blue
27+ ] ;
28+
29+ const actions = [
30+ 'Initializing' , 'Compiling' , 'Fetching' , 'Rendering' , 'Connecting to' , 'Authenticating' ,
31+ 'Validating' , 'Deploying' , 'Optimizing' , 'Resolving dependencies for'
32+ ] ;
33+
34+ const subjects = [
35+ 'CoreModule' , 'AuthService' , 'API_Endpoint:/v1/users' , 'WebSocket' , 'AssetPipeline' ,
36+ 'Component:Header' , 'ServiceWorker' , 'GraphQL_Query:GetUser' , 'Stylesheet:main.css'
37+ ] ;
38+
39+ const statuses = [ '...DONE' , '...OK' , '...FAILED' , '...IN_PROGRESS' ] ;
40+
41+ const level = logLevels [ Math . floor ( Math . random ( ) * logLevels . length ) ] ;
42+ const action = actions [ Math . floor ( Math . random ( ) * actions . length ) ] ;
43+ const subject = subjects [ Math . floor ( Math . random ( ) * subjects . length ) ] ;
44+ const status = statuses [ Math . floor ( Math . random ( ) * statuses . length ) ] ;
45+ const timestamp = new Date ( ) . toISOString ( ) ;
46+
47+ // Make ERROR lines more distinct
48+ if ( level . prefix === '[ERROR]' ) {
49+ return {
50+ text : `${ timestamp } ${ level . prefix } Failed to ${ action . toLowerCase ( ) } ${ subject } . Status: ${ status } ` ,
51+ color : level . color ,
52+ } ;
53+ }
54+
55+ return {
56+ text : `${ timestamp } ${ level . prefix } ${ action } ${ subject } ${ Math . random ( ) > 0.7 ? status : '' } ` ,
57+ color : level . color ,
58+ } ;
59+ } ;
60+
61+
62+ const ConsoleLogBackground = ( {
63+ fontSize = 12 ,
64+ newLineInterval = 300 , // Add a new line every 300ms
65+ opacity = 0.15 ,
66+ } : ConsoleLogBackgroundProps ) => {
567 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
68+ // Use a ref for log lines to avoid re-renders on update
69+ const logLinesRef = useRef < LogLine [ ] > ( [ ] ) ;
670
771 useEffect ( ( ) => {
872 const canvas = canvasRef . current ;
@@ -11,62 +75,86 @@ const MatrixBackground = () => {
1175 const ctx = canvas . getContext ( '2d' ) ;
1276 if ( ! ctx ) return ;
1377
14- const resizeCanvas = ( ) => {
15- canvas . width = window . innerWidth ;
16- canvas . height = window . innerHeight ;
78+ let animationFrameId : number ;
79+ let lastLineTime = 0 ;
80+ const lineHeight = fontSize * 1.5 ; // Spacing between lines
81+
82+ // --- This function initializes or re-initializes the canvas state ---
83+ const initialize = ( ) => {
84+ canvas . width = window . innerWidth * window . devicePixelRatio ;
85+ canvas . height = window . innerHeight * window . devicePixelRatio ;
86+ canvas . style . width = `${ window . innerWidth } px` ;
87+ canvas . style . height = `${ window . innerHeight } px` ;
88+ ctx . scale ( window . devicePixelRatio , window . devicePixelRatio ) ;
89+
90+ // Pre-fill the screen with some lines so it's not empty on load
91+ const maxLines = Math . ceil ( canvas . height / lineHeight ) ;
92+ if ( logLinesRef . current . length === 0 ) {
93+ for ( let i = 0 ; i < maxLines ; i ++ ) {
94+ logLinesRef . current . unshift ( generateLogLine ( ) ) ;
95+ }
96+ }
1797 } ;
1898
19- resizeCanvas ( ) ;
20- window . addEventListener ( 'resize' , resizeCanvas ) ;
21-
22- // Matrix characters
23- const matrix = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789@#$%^&*()*&^%+-/~{[|`]}" ;
24- const matrixArray = matrix . split ( "" ) ;
25-
26- const fontSize = 10 ;
27- const columns = canvas . width / fontSize ;
28- const drops : number [ ] = [ ] ;
29-
30- // Initialize drops
31- for ( let x = 0 ; x < columns ; x ++ ) {
32- drops [ x ] = 1 ;
33- }
34-
35- const draw = ( ) => {
36- // Semi-transparent background for trailing effect
37- ctx . fillStyle = 'rgba(0, 0, 0, 0.04)' ;
38- ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
99+ // --- The core animation function ---
100+ const draw = ( timestamp : number ) => {
101+ // Add a new line based on the interval
102+ if ( timestamp - lastLineTime > newLineInterval ) {
103+ lastLineTime = timestamp ;
104+ logLinesRef . current . push ( generateLogLine ( ) ) ;
105+
106+ // Keep the array from growing infinitely
107+ const maxLines = Math . ceil ( canvas . height / lineHeight ) + 5 ; // +5 for buffer
108+ if ( logLinesRef . current . length > maxLines ) {
109+ logLinesRef . current . shift ( ) ;
110+ }
111+ }
39112
40- ctx . fillStyle = '#0F0' ;
113+ // Clear the canvas for the new frame
114+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
41115 ctx . font = `${ fontSize } px monospace` ;
42-
43- for ( let i = 0 ; i < drops . length ; i ++ ) {
44- const text = matrixArray [ Math . floor ( Math . random ( ) * matrixArray . length ) ] ;
45- ctx . fillStyle = `rgba(0, 255, 0, ${ Math . random ( ) * 0.5 + 0.1 } )` ;
46- ctx . fillText ( text , i * fontSize , drops [ i ] * fontSize ) ;
47-
48- if ( drops [ i ] * fontSize > canvas . height && Math . random ( ) > 0.975 ) {
49- drops [ i ] = 0 ;
50- }
51- drops [ i ] ++ ;
116+
117+ // Draw lines from the bottom up
118+ const lines = logLinesRef . current ;
119+ for ( let i = 0 ; i < lines . length ; i ++ ) {
120+ const line = lines [ lines . length - 1 - i ] ; // Get line from end of array
121+ const y = canvas . height / window . devicePixelRatio - ( i * lineHeight ) ;
122+
123+ // Stop drawing if we're off-screen
124+ if ( y < - lineHeight ) break ;
125+
126+ ctx . fillStyle = line . color ;
127+ ctx . fillText ( line . text , 10 , y ) ;
52128 }
53129 } ;
54130
55- const interval = setInterval ( draw , 35 ) ;
131+ const animate = ( timestamp : number ) => {
132+ draw ( timestamp ) ;
133+ animationFrameId = window . requestAnimationFrame ( animate ) ;
134+ } ;
135+
136+ const handleResize = ( ) => {
137+ // Re-initialize canvas dimensions but keep existing logs
138+ initialize ( ) ;
139+ } ;
140+
141+ initialize ( ) ;
142+ window . addEventListener ( 'resize' , handleResize ) ;
143+ animate ( 0 ) ; // Start the animation
56144
57145 return ( ) => {
58- clearInterval ( interval ) ;
59- window . removeEventListener ( 'resize' , resizeCanvas ) ;
146+ window . cancelAnimationFrame ( animationFrameId ) ;
147+ window . removeEventListener ( 'resize' , handleResize ) ;
60148 } ;
61- } , [ ] ) ;
149+ } , [ fontSize , newLineInterval ] ) ;
62150
63151 return (
64152 < canvas
65153 ref = { canvasRef }
66- className = "fixed inset-0 pointer-events-none z-0 opacity-20 "
67- style = { { background : 'transparent' } }
154+ className = "fixed inset-0 pointer-events-none z-0"
155+ style = { { background : 'transparent' , opacity } }
68156 />
69157 ) ;
70158} ;
71159
72- export default MatrixBackground ;
160+ export default ConsoleLogBackground ;
0 commit comments