Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface WelcomeSplashProps {
|
||||
onEnter: () => void;
|
||||
}
|
||||
|
||||
const MATRIX_CHARS =
|
||||
"01アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+[]{};:?/\\|~`";
|
||||
|
||||
export default function WelcomeSplash({ onEnter }: WelcomeSplashProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const rafRef = useRef(0);
|
||||
const [showWelcome, setShowWelcome] = useState(false);
|
||||
const [exiting, setExiting] = useState(false);
|
||||
|
||||
const handleEnter = useCallback(() => {
|
||||
setExiting(true);
|
||||
setTimeout(onEnter, 700);
|
||||
}, [onEnter]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setShowWelcome(true), 6000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user