'use client'; import { useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; 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); 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)' }}>
); } const fixedOptions = { fixed_gender: ['男生', '女生', '不想说'], fixed_age: ['20以下', '20-25', '26-30', '31-40', '40以上'], fixed_birth_month: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], fixed_birth_day: Array.from({ length: 31 }, (_, i) => `${i + 1}日`), }; function QuestionView({ prompt, onAnswer }) { const [visible, setVisible] = useState(false); const [input, setInput] = useState(''); const [leaving, setLeaving] = useState(false); const [picked, setPicked] = useState(''); const inputRef = useRef(null); useEffect(() => { setVisible(false); setInput(''); setLeaving(false); setPicked(''); const t = setTimeout(() => { setVisible(true); if ((prompt?.type || 'text') === 'text') { setTimeout(() => inputRef.current?.focus(), 600); } }, 100); return () => clearTimeout(t); }, [prompt]); const submit = (val) => { const answer = (val || '').trim(); if (!answer) return; setLeaving(true); setTimeout(() => onAnswer(answer), 700); }; const type = prompt?.type || 'text'; const isChoice = type === 'choice' || type.startsWith('fixed_'); const question = prompt?.question || prompt?.reply || ''; const options = type === 'choice' ? (prompt.options || []) : (fixedOptions[type] || []); const compact = type === 'fixed_birth_day'; return (
{question}
{isChoice ? (
{options.map((op, i) => { const selected = picked === op; const othersFade = picked && picked !== op; return ( ); })}
) : (