Files
omniai-web/src/features/home/WelcomeSplash.tsx
T

123 lines
3.8 KiB
TypeScript
Raw Normal View History

2026-06-02 12:38:01 +08:00
import { useCallback, useEffect, useRef, useState } from "react";
interface WelcomeSplashProps {
onEnter: () => void;
}
const MATRIX_CHARS =
"01アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+[]{};:?/\\|~`";
const prefersReducedMotion = typeof window !== "undefined"
? window.matchMedia("(prefers-reduced-motion: reduce)").matches
: false;
2026-06-02 12:38:01 +08:00
export default function WelcomeSplash({ onEnter }: WelcomeSplashProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const rafRef = useRef(0);
const [showWelcome, setShowWelcome] = useState(true);
2026-06-02 12:38:01 +08:00
const [exiting, setExiting] = useState(false);
const handleEnter = useCallback(() => {
setExiting(true);
setTimeout(onEnter, prefersReducedMotion ? 0 : 700);
2026-06-02 12:38:01 +08:00
}, [onEnter]);
useEffect(() => {
const timer = setTimeout(() => setShowWelcome(true), prefersReducedMotion ? 0 : 6000);
2026-06-02 12:38:01 +08:00
return () => clearTimeout(timer);
}, []);
useEffect(() => {
if (prefersReducedMotion) {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle = "rgba(0, 0, 0, 0.85)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
return;
}
2026-06-02 12:38:01 +08:00
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let width = window.innerWidth;
let height = window.innerHeight;
canvas.width = width;
canvas.height = height;
const fontSize = width > 1200 ? 20 : width > 768 ? 18 : 14;
const columns = Math.floor(width / fontSize);
const drops: number[] = [];
for (let i = 0; i < columns; i++) {
drops[i] = Math.random() * -(height / fontSize);
}
function draw() {
ctx!.fillStyle = "rgba(0, 0, 0, 0.07)";
ctx!.fillRect(0, 0, width, height);
ctx!.font = `${fontSize}px "Courier New", monospace`;
ctx!.textAlign = "center";
for (let i = 0; i < drops.length; i++) {
const char = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
const intensity = Math.min(0.6 + (drops[i] / (height / fontSize)) * 0.4, 1);
const g = Math.floor(100 + 155 * intensity);
ctx!.fillStyle = `rgba(40, ${g}, 60, 0.9)`;
ctx!.shadowBlur = 6;
ctx!.shadowColor = "#0f0";
const x = i * fontSize + fontSize / 2;
const y = drops[i] * fontSize;
ctx!.fillText(char, x, y);
ctx!.shadowBlur = 0;
drops[i] += 0.7 + Math.random() * 1.4;
if (drops[i] * fontSize > height && Math.random() > 0.96) {
drops[i] = 0;
}
}
}
function animate() {
draw();
rafRef.current = requestAnimationFrame(animate);
}
animate();
function onResize() {
width = window.innerWidth;
height = window.innerHeight;
canvas!.width = width;
canvas!.height = height;
}
window.addEventListener("resize", onResize);
return () => {
cancelAnimationFrame(rafRef.current);
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div className={`welcome-splash${exiting ? " is-exiting" : ""}`}>
<canvas ref={canvasRef} className="welcome-splash__canvas" />
<div className="welcome-splash__ambient" />
<div className="welcome-splash__hero">
<h1 className="welcome-splash__title">OmniAI</h1>
<p className="welcome-splash__subtitle">The future with OmniAI</p>
{showWelcome && (
<button
type="button"
className="welcome-splash__enter"
onClick={handleEnter}
>
</button>
)}
</div>
</div>
);
}