人生轨迹代码初始化

This commit is contained in:
2025-12-21 16:57:54 +08:00
parent 06a3638c29
commit f3c06ce6af
42 changed files with 7746 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
import React, { useState, useEffect } from 'react';
import { useStoreData } from '../hooks/useStoreData';
import { Store } from '../utils/store';
import { AudioEngine } from '../utils/audioEngine';
import { TimelineView } from '../components/views/TimelineView';
import { ScriptView } from '../components/views/ScriptView';
import { PathView } from '../components/views/PathView';
import {
Compass,
BookOpen,
Film,
Map,
Music,
Volume2,
VolumeX,
RotateCcw,
Menu,
X
} from 'lucide-react';
import clsx from 'clsx';
export function DashboardPage() {
const data = useStoreData();
const [activeTab, setActiveTab] = useState('timeline');
const [isMusicPlaying, setIsMusicPlaying] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Initialize Audio Engine state based on store
useEffect(() => {
// Sync initial state if needed
if (!data.audioMuted) {
// AudioEngine manages its own state
}
}, []);
const handleMusicToggle = async () => {
try {
const playing = AudioEngine.toggle();
setIsMusicPlaying(playing);
} catch (e) {
console.error("Audio failed", e);
}
};
const handleReset = () => {
if (window.confirm('确定要清空所有数据重新开始吗?这将回到注册页面。')) {
Store.reset();
// App.jsx will handle the redirect because store data changes
}
};
const NavButton = ({ tab, icon: Icon, label, mobileLabel }) => (
<button
onClick={() => { setActiveTab(tab); setIsMobileMenuOpen(false); }}
className={clsx(
"w-full text-left px-4 py-3 rounded-xl flex items-center gap-3 transition-all duration-300 relative overflow-hidden group",
activeTab === tab
? "bg-primary/20 text-primary font-bold shadow-[0_0_15px_rgba(16,185,129,0.1)] border border-primary/20"
: "text-gray-400 hover:text-white hover:bg-white/5 border border-transparent"
)}
>
<Icon className={clsx("w-5 h-5 transition-transform", activeTab === tab ? "text-primary" : "text-gray-400 group-hover:text-white", activeTab !== tab && "group-hover:scale-110")} />
<span className="hidden md:inline">{label}</span>
<span className="md:hidden">{mobileLabel || label}</span>
{activeTab === tab && (
<div className="absolute right-0 top-0 bottom-0 w-1 bg-primary shadow-[0_0_10px_#10b981]"></div>
)}
</button>
);
return (
<div className="min-h-screen flex flex-col md:flex-row transition-all duration-500 font-sans text-gray-100 bg-deep-sea overflow-hidden">
{/* Ambient Background */}
<div className="fixed inset-0 pointer-events-none z-0">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_50%_50%,rgba(16,185,129,0.05),transparent_50%)]"></div>
<div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-primary/5 rounded-full blur-[120px] animate-pulse-slow"></div>
</div>
{/* Mobile Header */}
<div className="md:hidden bg-black/20 backdrop-blur-xl border-b border-white/10 p-4 flex justify-between items-center z-50 relative">
<div className="flex items-center gap-2 font-bold text-lg">
<Compass className="text-primary w-6 h-6 animate-spin-slow" />
<span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent">人生轨迹</span>
</div>
<button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>
{isMobileMenuOpen ? <X className="text-white" /> : <Menu className="text-white" />}
</button>
</div>
{/* Sidebar (Desktop) / Drawer (Mobile) */}
<nav className={clsx(
"bg-black/20 backdrop-blur-xl border-r border-white/10 w-full md:w-72 flex-shrink-0 flex flex-col justify-between z-40 fixed md:relative h-[calc(100vh-64px)] md:h-screen top-16 md:top-0 left-0 transition-transform duration-300",
isMobileMenuOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"
)}>
{/* Background Decoration */}
<div className="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-primary/5 pointer-events-none"></div>
<div className="p-6 overflow-y-auto relative z-10">
<h1 className="hidden md:flex text-2xl font-bold tracking-wider mb-8 items-center gap-3 text-transparent bg-clip-text bg-gradient-to-r from-primary to-white">
<Compass className="text-primary stroke-2 animate-spin-slow" /> 人生轨迹
</h1>
{/* User Card */}
<div className="flex items-center gap-4 mb-8 p-4 bg-white/5 rounded-2xl backdrop-blur-sm border border-white/10 hover:bg-white/10 transition-colors cursor-default group">
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-inner relative overflow-hidden group-hover:scale-105 transition-transform shrink-0 border border-white/20">
{data.userProfile.nickname?.[0] || 'U'}
<div className="absolute inset-0 bg-white/20 opacity-0 group-hover:opacity-100 transition-opacity"></div>
</div>
<div className="overflow-hidden">
<div className="font-bold text-white truncate">{data.userProfile.nickname || '旅人'}</div>
<div className="text-xs text-primary flex items-center gap-1.5 mt-0.5">
<span className="w-1.5 h-1.5 rounded-full bg-primary shadow-[0_0_5px_rgba(16,185,129,0.5)] animate-pulse"></span>
{data.userProfile.mbti} · {data.userProfile.zodiac || '未知'}
</div>
</div>
</div>
{/* Navigation */}
<div className="space-y-2">
<NavButton tab="timeline" icon={BookOpen} label="时空日记" mobileLabel="日记" />
<NavButton tab="script" icon={Film} label="剧本生成器" mobileLabel="剧本" />
<NavButton tab="path" icon={Map} label="实现路径" mobileLabel="路径" />
</div>
</div>
<div className="p-6 text-xs text-gray-500 border-t border-white/5 space-y-4 bg-black/40 backdrop-blur-md md:bg-transparent relative z-10">
{/* Music Player */}
<button
onClick={handleMusicToggle}
className={clsx(
"flex items-center justify-between w-full px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10 transition-all border group",
isMusicPlaying ? "border-primary/30 shadow-[0_0_10px_rgba(16,185,129,0.1)]" : "border-white/5"
)}
>
<div className="flex items-center gap-3 text-gray-300">
<div className={clsx("w-8 h-8 rounded-full flex items-center justify-center transition-colors", isMusicPlaying ? "bg-primary/20 text-primary" : "bg-white/10 text-gray-400")}>
{isMusicPlaying ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
</div>
<span>疗愈背景音</span>
</div>
{isMusicPlaying && (
<div className="flex gap-0.5 h-3 items-end">
<span className="w-0.5 bg-primary animate-music-wave-1"></span>
<span className="w-0.5 bg-primary animate-music-wave-2"></span>
<span className="w-0.5 bg-primary animate-music-wave-3"></span>
<span className="w-0.5 bg-primary animate-music-wave-4"></span>
</div>
)}
</button>
<button
onClick={handleReset}
className="flex items-center gap-2 hover:text-red-400 transition-colors w-full px-4 py-2 rounded hover:bg-red-500/10 justify-center"
>
<RotateCcw className="w-3 h-3" /> 重置所有数据
</button>
</div>
</nav>
{/* Main Content Area */}
<main className="flex-1 overflow-y-auto h-[calc(100vh-64px)] md:h-screen relative z-10 scroll-smooth p-4 md:p-0 custom-scrollbar">
{activeTab === 'timeline' && <TimelineView />}
{activeTab === 'script' && <ScriptView onSwitchToPath={() => setActiveTab('path')} />}
{activeTab === 'path' && <PathView onSwitchToScript={() => setActiveTab('script')} />}
</main>
</div>
);
}
+194
View File
@@ -0,0 +1,194 @@
import React, { useState, useEffect } from 'react';
import { Compass, ArrowRight, X, Phone, Lock, Loader2 } from 'lucide-react';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import request from '../utils/request';
import clsx from 'clsx';
// PncyssD Prototype: Landing Page
export function LandingPage({ onStart }) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [showLoginModal, setShowLoginModal] = useState(false);
const [loading, setLoading] = useState(false);
// Login Form State
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [countdown, setCountdown] = useState(0);
useEffect(() => {
const token = localStorage.getItem('token');
if (token) setIsLoggedIn(true);
}, []);
useEffect(() => {
let timer;
if (countdown > 0) {
timer = setInterval(() => setCountdown(c => c - 1), 1000);
}
return () => clearInterval(timer);
}, [countdown]);
const handleGetCode = async () => {
if (!phone) return alert('请输入手机号');
if (!/^1[3-9]\d{9}$/.test(phone)) return alert('手机号格式不正确');
try {
// Backend: /auth/sms-code?phone=... (AuthController.java)
// Note: "Business type" is not required by the current backend implementation.
const res = await request.get('/auth/sms-code', { params: { phone } });
if (res.code === 200) {
setCountdown(60);
// Display backend message or dev code
const msg = res.data?.message || '验证码已发送';
if (res.data?.code) {
alert(`【测试模式】${msg}\n验证码: ${res.data.code}`);
} else {
alert(msg);
}
} else {
console.warn('SMS Code Error:', res);
alert(res.message || '发送失败,请稍后重试');
}
} catch (e) {
console.error('Failed to get SMS code:', e);
const errorMsg = e.response?.data?.message || '网络连接异常,请检查您的网络设置';
alert(errorMsg);
}
};
const handleLogin = async () => {
if (!phone || !code) return alert('请填写完整信息');
setLoading(true);
try {
const res = await request.post('/auth/login', { phone, smsCode: code });
if (res.code === 200) {
const { accessToken } = res.data;
localStorage.setItem('token', accessToken);
setIsLoggedIn(true);
setShowLoginModal(false);
// Clear sensitive data
setPhone('');
setCode('');
} else {
alert(res.message || '登录失败');
}
} catch (e) {
console.error(e);
alert('登录异常,请检查网络');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex flex-col items-center justify-center text-center p-6 relative overflow-hidden">
{/* Background & Effects */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/20 rounded-full blur-[100px] animate-pulse-slow"></div>
<div className="relative z-10 space-y-8 max-w-lg mx-auto w-full">
<div className="mb-4 inline-block p-4 rounded-full bg-white/5 border border-white/10 shadow-2xl animate-float">
<Compass className="w-16 h-16 text-primary icon-spin-slow" strokeWidth={1.5} />
</div>
<h1 className="text-4xl md:text-6xl font-bold text-white tracking-tight landing-title">
人生轨迹
<span className="block text-xl md:text-2xl font-light mt-4 text-primary/80">Life Trajectory</span>
</h1>
{/* Buttons */}
<div className="flex flex-col items-center justify-center gap-4 mt-12 w-full max-w-xs mx-auto">
{isLoggedIn ? (
<Button
variant="primary"
size="lg"
onClick={onStart}
className="w-full animate-in fade-in zoom-in duration-500"
>
开启旅程 <ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform ml-2" />
</Button>
) : (
<Button
variant="primary"
size="lg"
onClick={() => setShowLoginModal(true)}
className="w-full shadow-[0_0_20px_rgba(16,185,129,0.3)] hover:shadow-[0_0_30px_rgba(16,185,129,0.5)] transition-all"
>
登录账号
</Button>
)}
</div>
</div>
{/* Login Modal */}
{showLoginModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
<div className="relative w-full max-w-md bg-[#0f172a] border border-white/10 rounded-2xl shadow-2xl p-8 overflow-hidden">
{/* Modal Background */}
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-[80px] -translate-y-1/2 translate-x-1/2 pointer-events-none"></div>
<button
onClick={() => setShowLoginModal(false)}
className="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"
>
<X className="w-6 h-6" />
</button>
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-2">
<span className="w-1 h-6 bg-primary rounded-full"></span>
欢迎回来
</h2>
<div className="space-y-4">
<div className="space-y-1 text-left">
<label className="text-xs font-bold text-gray-400 uppercase tracking-wider ml-1">手机号</label>
<div className="relative">
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Input
placeholder="请输入手机号"
className="pl-10"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
</div>
</div>
<div className="space-y-1 text-left">
<label className="text-xs font-bold text-gray-400 uppercase tracking-wider ml-1">验证码</label>
<div className="flex gap-3">
<div className="relative flex-1">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Input
placeholder="请输入验证码"
className="pl-10"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
</div>
<Button
variant="secondary"
className="w-28 whitespace-nowrap"
onClick={handleGetCode}
disabled={countdown > 0}
>
{countdown > 0 ? `${countdown}s` : '获取验证码'}
</Button>
</div>
</div>
<Button
variant="primary"
className="w-full mt-6 py-3 text-lg"
onClick={handleLogin}
disabled={loading}
>
{loading ? <Loader2 className="w-5 h-5 animate-spin mx-auto" /> : '登 录'}
</Button>
</div>
</div>
</div>
)}
</div>
);
}
+155
View File
@@ -0,0 +1,155 @@
import React, { useState } from 'react';
import { User, Lock, Eye, EyeOff, ArrowRight, ArrowLeft } from 'lucide-react';
import { GlassCard } from '../components/ui/GlassCard';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Checkbox } from '../components/ui/Checkbox';
import { Store } from '../utils/store';
export function LoginPage({ onLoginSuccess, onBack, onSignUp }) {
const [formData, setFormData] = useState({
username: '',
password: '',
rememberMe: false
});
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
// Mock login delay
setTimeout(() => {
if (!formData.username || !formData.password) {
setError('请输入用户名和密码');
setIsLoading(false);
return;
}
// Simple mock validation
if (formData.password.length < 6) {
setError('密码长度不能少于6位');
setIsLoading(false);
return;
}
// Success
Store.updateProfile({
nickname: formData.username,
// In a real app, we'd store a token
});
setIsLoading(false);
onLoginSuccess();
}, 1000);
};
return (
<div className="min-h-screen flex items-center justify-center p-4 relative z-10 animate-fade-in">
<div className="absolute top-8 left-8">
<button
onClick={onBack}
className="flex items-center gap-2 text-white/60 hover:text-white transition-colors group"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
<span>返回首页</span>
</button>
</div>
<GlassCard className="w-full max-w-md p-8 md:p-10 relative overflow-hidden">
{/* Decorative elements */}
<div className="absolute -top-10 -right-10 w-40 h-40 bg-primary/20 rounded-full blur-3xl pointer-events-none"></div>
<div className="absolute -bottom-10 -left-10 w-40 h-40 bg-aurora-green/20 rounded-full blur-3xl pointer-events-none"></div>
<div className="relative z-10">
<div className="text-center mb-10">
<h2 className="text-3xl font-bold text-white mb-2 tracking-tight">欢迎回来</h2>
<p className="text-gray-400">登录您的 Emotion Museum 账号</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-300 ml-1">用户名 / 邮箱</label>
<div className="relative group">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-primary transition-colors" />
<Input
type="text"
placeholder="请输入您的账号"
className="pl-12 w-full"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-300 ml-1">密码</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-primary transition-colors" />
<Input
type={showPassword ? "text" : "password"}
placeholder="请输入您的密码"
className="pl-12 pr-12 w-full"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors focus:outline-none"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
<div className="flex items-center justify-between pt-2">
<Checkbox
label="记住我"
checked={formData.rememberMe}
onChange={(checked) => setFormData({...formData, rememberMe: checked})}
/>
<button type="button" className="text-sm text-primary hover:text-aurora-green transition-colors">
忘记密码
</button>
</div>
{error && (
<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-200 text-sm flex items-center gap-2 animate-shake">
<span className="w-1.5 h-1.5 rounded-full bg-red-500"></span>
{error}
</div>
)}
<Button
type="submit"
className="w-full"
size="lg"
isLoading={isLoading}
>
{!isLoading && (
<>
立即登录 <ArrowRight className="w-4 h-4 ml-2" />
</>
)}
</Button>
</form>
<div className="mt-8 text-center text-sm text-gray-400">
还没有账号
<button
onClick={onSignUp}
className="text-primary hover:text-aurora-green font-bold ml-1 hover:underline transition-all"
>
立即注册
</button>
</div>
</div>
</GlassCard>
</div>
);
}
+235
View File
@@ -0,0 +1,235 @@
import React, { useState } from 'react';
import { Store } from '../utils/store';
import { ArrowLeft, ArrowRight, Check, X, Sparkles, Star, AlertCircle, CheckCircle } from 'lucide-react';
import { Button } from '../components/ui/Button';
import { Input, Select, Textarea } from '../components/ui/Input';
import clsx from 'clsx';
const ZODIAC_SIGNS = [
"白羊座", "金牛座", "双子座", "巨蟹座",
"狮子座", "处女座", "天秤座", "天蝎座",
"射手座", "摩羯座", "水瓶座", "双鱼座"
];
const MBTI_TYPES = ['INTJ','INTP','ENTJ','ENTP','INFJ','INFP','ENFJ','ENFP','ISTJ','ISFJ','ESTJ','ESFJ','ISTP','ISFP','ESTP','ESFP'];
export function OnboardingPage({ onFinish }) {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState(Store.get().userProfile);
const [toast, setToast] = useState(null); // { msg, type }
const showToast = (msg, type = 'error') => {
setToast({ msg, type });
setTimeout(() => setToast(null), 2500);
};
const updateFormData = (key, value) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
const updateHistory = (type, field, value) => {
setFormData(prev => ({
...prev,
history: {
...prev.history,
[type]: {
...prev.history?.[type],
[field]: value
}
}
}));
};
const handleNext = () => {
if (step === 0) {
if (!formData.nickname?.trim()) { showToast('请填写昵称'); return; }
if (!formData.mbti) { showToast('请选择MBTI人格类型'); return; }
} else if (step === 4) {
if (!formData.futureVision?.trim()) { showToast('写下一句对未来的期许吧'); return; }
Store.updateProfile(formData);
Store.completeOnboarding();
onFinish();
return;
}
setStep(prev => prev + 1);
};
const handlePrev = () => setStep(prev => prev - 1);
return (
<div className="min-h-screen flex flex-col relative overflow-hidden transition-colors duration-1000">
{/* Background Blobs */}
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/10 rounded-full blur-[80px] -translate-y-1/2 translate-x-1/2 animate-pulse-slow"></div>
<div className="absolute bottom-0 left-0 w-80 h-80 bg-secondary/20 rounded-full blur-[80px] translate-y-1/3 -translate-x-1/3 animate-pulse-slow" style={{ animationDelay: '2s' }}></div>
{/* Toast */}
{toast && (
<div className={clsx(
"fixed top-10 left-1/2 -translate-x-1/2 px-6 py-3 rounded-full shadow-xl z-50 animate-fade-in flex items-center gap-2",
toast.type === 'error' ? 'bg-red-500/90 text-white' : 'bg-green-500/90 text-white'
)}>
{toast.type === 'error' ? <AlertCircle className="w-4 h-4" /> : <CheckCircle className="w-4 h-4" />}
{toast.msg}
</div>
)}
{/* Header */}
<div className="relative z-10 px-6 py-8 flex justify-between items-center">
<div className="flex gap-1">
{[0,1,2,3,4].map(i => (
<div key={i} className={clsx(
"h-1 rounded-full transition-all duration-500",
i <= step ? 'w-8 bg-primary shadow-[0_0_10px_rgba(42,157,143,0.5)]' : 'w-2 bg-white/10'
)}></div>
))}
</div>
<button className="text-white/40 hover:text-white transition-colors" onClick={() => { if(confirm('退出注册将不保存进度,确定吗?')) window.location.reload() }}>
<X className="w-6 h-6 hover:rotate-90 transition-transform" />
</button>
</div>
{/* Content */}
<div className="flex-1 flex flex-col justify-center px-6 pb-24 max-w-xl mx-auto w-full relative z-10">
{step === 0 && (
<div className="animate-fade-in space-y-6">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-white">你是谁</h2>
<p className="text-gray-400 text-sm mt-2">让我们先认识一下彼此</p>
</div>
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-gray-400 mb-1 ml-1">昵称 <span className="text-red-400">*</span></label>
<Input
placeholder="你希望世界如何称呼你?"
value={formData.nickname || ''}
onChange={e => updateFormData('nickname', e.target.value)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-gray-400 mb-1 ml-1">性别</label>
<Select value={formData.gender || 'secret'} onChange={e => updateFormData('gender', e.target.value)}>
<option value="male"></option>
<option value="female"></option>
<option value="secret">保密</option>
</Select>
</div>
<div>
<label className="block text-xs font-bold text-gray-400 mb-1 ml-1">星座</label>
<Select value={formData.zodiac || ''} onChange={e => updateFormData('zodiac', e.target.value)}>
<option value="" disabled>选择星座</option>
{ZODIAC_SIGNS.map(z => <option key={z} value={z}>{z}</option>)}
</Select>
</div>
</div>
<div className="grid grid-cols-1 gap-4">
<div>
<label className="block text-xs font-bold text-gray-400 mb-1 ml-1">MBTI 人格 <span className="text-red-400">*</span></label>
<Select value={formData.mbti || ''} onChange={e => updateFormData('mbti', e.target.value)}>
<option value="" disabled>选择你的类型</option>
{MBTI_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
</Select>
</div>
</div>
<div>
<label className="block text-xs font-bold text-gray-400 mb-1 ml-1">兴趣爱好 (用逗号分隔)</label>
<Input
placeholder="例如:摄影, 登山, 编程"
value={(formData.hobbies || []).join(', ')}
onChange={e => updateFormData('hobbies', e.target.value.split(/[,]/).map(s => s.trim()))}
/>
</div>
</div>
</div>
)}
{step === 1 && (
<div className="animate-fade-in space-y-6">
<div className="text-center mb-8">
<span className="text-amber-300 text-xs tracking-widest uppercase mb-2 block">Part 1 / 3</span>
<h2 className="text-2xl font-bold text-white">你是如何成为了现在的自己</h2>
<p className="text-gray-400 text-sm mt-2">闭上眼回到开始的地方你的童年是怎么度过的</p>
</div>
<div className="bg-white/5 p-6 rounded-2xl border border-amber-500/20 shadow-lg shadow-amber-500/5">
<label className="block text-xs font-bold text-amber-300/80 mb-2">大概的时间</label>
<Input type="date" className="mb-4" value={formData.history?.childhood?.date || ''} onChange={e => updateHistory('childhood', 'date', e.target.value)} />
<label className="block text-xs font-bold text-amber-300/80 mb-2">记忆中的画面</label>
<Textarea rows={5} placeholder="比如:外婆的蒲扇,炎热的下午,或者第一次离家..." value={formData.history?.childhood?.content || ''} onChange={e => updateHistory('childhood', 'content', e.target.value)} />
</div>
</div>
)}
{step === 2 && (
<div className="animate-fade-in space-y-6">
<div className="text-center mb-8">
<span className="text-primary text-xs tracking-widest uppercase mb-2 block">Part 2 / 3</span>
<h2 className="text-2xl font-bold text-white">闪闪发光的日子</h2>
<p className="text-gray-400 text-sm mt-2">在这个过程中让你感到非常开心的经历是什么</p>
</div>
<div className="bg-white/5 p-6 rounded-2xl border border-primary/20 shadow-lg shadow-primary/5">
<label className="block text-xs font-bold text-primary/80 mb-2">发生日期</label>
<Input type="date" className="mb-4" value={formData.history?.peak?.date || ''} onChange={e => updateHistory('peak', 'date', e.target.value)} />
<label className="block text-xs font-bold text-primary/80 mb-2">那发生了什么</label>
<Textarea rows={5} placeholder="比如:收到心仪的offer,一次完美的旅行,或者被理解的瞬间..." value={formData.history?.peak?.content || ''} onChange={e => updateHistory('peak', 'content', e.target.value)} />
</div>
</div>
)}
{step === 3 && (
<div className="animate-fade-in space-y-6">
<div className="text-center mb-8">
<span className="text-blue-400 text-xs tracking-widest uppercase mb-2 block">Part 3 / 3</span>
<h2 className="text-2xl font-bold text-white">风雨兼程</h2>
<p className="text-gray-400 text-sm mt-2">一段十分沮丧和低谷的时光你是如何度过的</p>
</div>
<div className="bg-white/5 p-6 rounded-2xl border border-blue-400/20 shadow-lg shadow-blue-400/5">
<label className="block text-xs font-bold text-blue-400/80 mb-2">发生日期</label>
<Input type="date" className="mb-4" value={formData.history?.valley?.date || ''} onChange={e => updateHistory('valley', 'date', e.target.value)} />
<label className="block text-xs font-bold text-blue-400/80 mb-2">当时的感受与成长</label>
<Textarea rows={5} placeholder="那个挑战是什么?现在回看,它带给了你什么?" value={formData.history?.valley?.content || ''} onChange={e => updateHistory('valley', 'content', e.target.value)} />
</div>
</div>
)}
{step === 4 && (
<div className="animate-fade-in space-y-6">
<div className="text-center mb-10">
<h2 className="text-2xl font-bold text-white">你未来想成为怎样的人</h2>
<p className="text-gray-400 text-sm mt-2">对自己的憧憬以及对理想生活状态的憧憬</p>
</div>
<div className="relative group">
<Sparkles className="absolute -top-6 -left-4 text-accent w-8 h-8 animate-pulse-slow" />
<Textarea rows={4} className="text-lg leading-relaxed text-center group-hover:border-accent/50 transition-colors" placeholder="三年后,我希望过着这样的生活..." value={formData.futureVision || ''} onChange={e => updateFormData('futureVision', e.target.value)} />
<Star className="absolute -bottom-6 -right-4 text-accent w-6 h-6 animate-pulse-slow" style={{ animationDelay: '1s' }} />
</div>
</div>
)}
</div>
{/* Footer Controls */}
<div className="fixed bottom-0 left-0 w-full p-6 bg-deep-sea/80 backdrop-blur-lg border-t border-white/5 flex justify-between items-center z-20">
{step > 0 ? (
<button onClick={handlePrev} className="text-gray-400 hover:text-white flex items-center gap-2 transition-colors group">
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" /> 上一步
</button>
) : <div></div>}
<Button onClick={handleNext} className="flex items-center gap-2 px-8 group hover:scale-105 active:scale-95 shadow-lg shadow-primary/20">
<span>{step === 4 ? '完成创建' : '下一步'}</span>
{step === 4 ? <Check className="w-4 h-4" /> : <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />}
</Button>
</div>
</div>
);
}