'use client'; import { useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; // ── 星空Canvas ────────────────────────────────────────── function Starfield() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); let animId; const STARS = 180; const stars = Array.from({ length: STARS }, () => ({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, z: Math.random() * window.innerWidth, pz: 0, })); const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); const cx = () => canvas.width / 2; const cy = () => canvas.height / 2; const tick = () => { ctx.fillStyle = 'rgba(5,3,15,0.25)'; ctx.fillRect(0, 0, canvas.width, canvas.height); stars.forEach((s) => { s.pz = s.z; s.z -= 4; if (s.z <= 0) { s.x = Math.random() * canvas.width; s.y = Math.random() * canvas.height; s.z = canvas.width; s.pz = s.z; } const sx = (s.x - cx()) * (canvas.width / s.z) + cx(); const sy = (s.y - cy()) * (canvas.width / s.z) + cy(); const px = (s.x - cx()) * (canvas.width / s.pz) + cx(); const py = (s.y - cy()) * (canvas.width / s.pz) + cy(); const size = Math.max(0.3, (1 - s.z / canvas.width) * 2.5); const alpha = 1 - s.z / canvas.width; ctx.strokeStyle = `rgba(200,190,255,${alpha})`; ctx.lineWidth = size; ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(sx, sy); ctx.stroke(); }); animId = requestAnimationFrame(tick); }; tick(); return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize); }; }, []); return ( ); } // ── 淡入淡出文字组件 ────────────────────────────────────── function FadeText({ text, visible, className = '' }) { return (
{text}
); } // ── 开场序列 ────────────────────────────────────────────── function Intro({ onDone }) { const [phase, setPhase] = useState(0); // 0: 黑 → 1: 灵镜出现 → 2: 灵镜消失 → 3: 副标题出现 → 4: 副标题消失 → 5: 按钮出现 useEffect(() => { const timers = [ setTimeout(() => setPhase(1), 600), setTimeout(() => setPhase(2), 2200), setTimeout(() => setPhase(3), 3000), setTimeout(() => setPhase(4), 4800), setTimeout(() => setPhase(5), 5600), ]; return () => timers.forEach(clearTimeout); }, []); return (
= 5 ? 1 : 0, transform: phase >= 5 ? 'translateY(0)' : 'translateY(12px)' }} >
); } // ── 单问题沉浸视图 ──────────────────────────────────────── function QuestionView({ question, onAnswer, questionIndex, total }) { const [visible, setVisible] = useState(false); const [input, setInput] = useState(''); const [leaving, setLeaving] = useState(false); const inputRef = useRef(null); useEffect(() => { setVisible(false); setInput(''); setLeaving(false); const t = setTimeout(() => { setVisible(true); setTimeout(() => inputRef.current?.focus(), 600); }, 100); return () => clearTimeout(t); }, [question]); const submit = () => { if (!input.trim()) return; setLeaving(true); setTimeout(() => onAnswer(input.trim()), 800); }; return (
{/* 问题 */}
{question}
{/* 输入 */}