mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00

* Included WebAssembly asset `yachtpit.js` for cockpit functionality. * Added Bevy MIT license file. * Implemented a service worker to cache assets locally instead of fetching them remotely. * Added collapsible functionality to **Tweakbox** and included the `@chakra-ui/icons` dependency. * Applied the `hidden` prop to the Tweakbox Heading for better accessibility. * Refactored **Particles** component for improved performance, clarity, and maintainability. * Introduced helper functions for particle creation and count management. * Added responsive resizing with particle repositioning. * Optimized animation updates, including velocity adjustments for speed changes. * Ensured canvas size and particle state are cleanly managed on component unmount.
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
import { useBreakpointValue, useTheme } from '@chakra-ui/react';
|
||
import React, { useEffect, useRef, useMemo } from 'react';
|
||
|
||
const MATRIX_CHARS =
|
||
'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||
|
||
interface MatrixRainProps {
|
||
speed?: number;
|
||
glow?: boolean;
|
||
intensity?: number;
|
||
}
|
||
|
||
export const MatrixRain: React.FC<MatrixRainProps> = ({
|
||
speed = 1,
|
||
glow = false,
|
||
intensity = 1,
|
||
}) => {
|
||
const fontSize = useBreakpointValue({ base: 14, md: 18, lg: 22 }) ?? 14;
|
||
const theme = useTheme();
|
||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||
const animationRef = useRef<number | null>(null);
|
||
const dropsRef = useRef<number[]>([]);
|
||
const columnsRef = useRef<number>(0);
|
||
|
||
const colors = useMemo(
|
||
() => ({
|
||
background: theme.colors.background.primary,
|
||
textAccent: theme.colors.text.accent,
|
||
}),
|
||
[theme.colors.background.primary, theme.colors.text.accent],
|
||
);
|
||
|
||
const colorsRef = useRef(colors);
|
||
colorsRef.current = colors;
|
||
|
||
useEffect(() => {
|
||
const canvas = canvasRef.current;
|
||
if (!canvas) return;
|
||
const ctx = canvas.getContext('2d');
|
||
if (!ctx) return;
|
||
|
||
const resize = () => {
|
||
canvas.width = window.innerWidth;
|
||
canvas.height = window.innerHeight;
|
||
|
||
const newColumns = Math.floor(canvas.width / fontSize);
|
||
if (newColumns !== columnsRef.current) {
|
||
columnsRef.current = newColumns;
|
||
const newDrops: number[] = [];
|
||
|
||
for (let i = 0; i < newColumns; i++) {
|
||
if (i < dropsRef.current.length) {
|
||
newDrops[i] = dropsRef.current[i];
|
||
} else {
|
||
newDrops[i] = Math.random() * (canvas.height / fontSize);
|
||
}
|
||
}
|
||
dropsRef.current = newDrops;
|
||
}
|
||
};
|
||
|
||
resize();
|
||
window.addEventListener('resize', resize);
|
||
|
||
if (dropsRef.current.length === 0) {
|
||
const columns = Math.floor(canvas.width / fontSize);
|
||
columnsRef.current = columns;
|
||
|
||
for (let i = 0; i < columns; i++) {
|
||
dropsRef.current[i] = Math.random() * (canvas.height / fontSize);
|
||
}
|
||
}
|
||
|
||
const draw = () => {
|
||
if (!ctx || !canvas) return;
|
||
|
||
const currentColors = colorsRef.current;
|
||
|
||
ctx.fillStyle = currentColors.background;
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||
|
||
ctx.font = `${fontSize}px monospace`;
|
||
|
||
for (let i = 0; i < dropsRef.current.length; i++) {
|
||
const text = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
|
||
const x = i * fontSize;
|
||
const y = dropsRef.current[i] * fontSize;
|
||
|
||
ctx.fillStyle = currentColors.textAccent;
|
||
if (glow) {
|
||
ctx.shadowBlur = 10;
|
||
ctx.shadowColor = currentColors.textAccent;
|
||
}
|
||
ctx.fillText(text, x, y);
|
||
|
||
if (y > canvas.height) {
|
||
dropsRef.current[i] = -Math.random() * 5;
|
||
} else {
|
||
dropsRef.current[i] += (0.1 + Math.random() * 0.5) * speed * intensity;
|
||
}
|
||
}
|
||
|
||
animationRef.current = requestAnimationFrame(draw);
|
||
};
|
||
|
||
animationRef.current = requestAnimationFrame(draw);
|
||
|
||
return () => {
|
||
window.removeEventListener('resize', resize);
|
||
if (animationRef.current) {
|
||
cancelAnimationFrame(animationRef.current);
|
||
}
|
||
};
|
||
}, [fontSize, speed, glow, intensity]);
|
||
|
||
return <canvas ref={canvasRef} style={{ display: 'block' }} />;
|
||
};
|