nomadweb/frontend/src/components/NomadScore.tsx
2026-08-28 19:49:10 -05:00

143 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
const QUESTIONS = [
{
q: "你的远程工作收入是否稳定?",
options: [
{ label: "非常稳定,有固定客户/薪资", score: 25, emoji: "💪" },
{ label: "基本稳定,偶尔波动", score: 18, emoji: "👍" },
{ label: "还在起步阶段", score: 8, emoji: "🌱" },
],
},
{
q: "你的月预算大约多少?",
options: [
{ label: "¥5,000 以下", score: 20, emoji: "💰" },
{ label: "¥5,000 – 10,000", score: 22, emoji: "💳" },
{ label: "¥10,000 以上", score: 25, emoji: "💎" },
],
},
{
q: "你对签证/税务了解多少?",
options: [
{ label: "已研究清楚,有方案", score: 25, emoji: "📋" },
{ label: "了解基础,还需深入", score: 15, emoji: "📖" },
{ label: "完全不了解", score: 5, emoji: "❓" },
],
},
{
q: "你的英语/沟通能力如何?",
options: [
{ label: "流利,可无障碍交流", score: 25, emoji: "🗣️" },
{ label: "基础够用,能应对日常", score: 15, emoji: "💬" },
{ label: "需要提升", score: 8, emoji: "📚" },
],
},
{
q: "你愿意离开舒适区吗?",
options: [
{ label: "非常期待新体验", score: 25, emoji: "🚀" },
{ label: "愿意尝试,略有顾虑", score: 18, emoji: "🌤️" },
{ label: "更喜欢熟悉的环境", score: 8, emoji: "🏠" },
],
},
];
const LEVELS = [
{ min: 0, max: 40, label: "🌱 萌芽游民", desc: "建议先从短期旅居开始,选择清迈或巴厘岛积累经验", color: "#FF6B6B" },
{ min: 41, max: 70, label: "🌤️ 准游民", desc: "你已经具备基础条件,可以规划 3-6 个月的旅居试点", color: "#FFE66D" },
{ min: 71, max: 90, label: "🚀 就绪游民", desc: "条件成熟!可以开始规划你的环球旅居之旅了", color: "#4ECDC4" },
{ min: 91, max: 100, label: "👑 资深游民", desc: "你就是为这种生活而生的!随时可以出发", color: "#A78BFA" },
];
export default function NomadScore() {
const [step, setStep] = useState(0);
const [scores, setScores] = useState<number[]>([]);
const [done, setDone] = useState(false);
const [animating, setAnimating] = useState(false);
const totalScore = scores.reduce((a, b) => a + b, 0);
const maxScore = QUESTIONS.length * 25;
const pct = Math.round((totalScore / maxScore) * 100);
const level = LEVELS.find((l) => pct >= l.min && pct <= l.max) || LEVELS[LEVELS.length - 1];
const select = (score: number) => {
setAnimating(true);
const next = [...scores, score];
setScores(next);
setTimeout(() => {
setAnimating(false);
if (step < QUESTIONS.length - 1) setStep(step + 1);
else setDone(true);
}, 300);
};
const reset = () => {
setStep(0);
setScores([]);
setDone(false);
};
return (
<section className="section nomad-score-section" id="nomad-score">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">📊 READINESS</span>
<h2>游民就绪度测评</h2>
<p>5 个问题,测测你离数字游民生活还有多远</p>
</div>
<div className="nomad-score-card reveal">
{!done ? (
<div className={`nomad-quiz${animating ? " animating" : ""}`}>
<div className="nomad-quiz-progress">
{QUESTIONS.map((_, i) => (
<div key={i} className={`nomad-quiz-dot${i <= step ? " filled" : ""}${i === step ? " current" : ""}`} />
))}
</div>
<span className="nomad-quiz-step">问题 {step + 1} / {QUESTIONS.length}</span>
<h3>{QUESTIONS[step].q}</h3>
<div className="nomad-quiz-options">
{QUESTIONS[step].options.map((opt) => (
<button key={opt.label} className="nomad-quiz-option" onClick={() => select(opt.score)}>
<span>{opt.emoji}</span>
<span>{opt.label}</span>
</button>
))}
</div>
</div>
) : (
<div className="nomad-result">
<div className="nomad-score-ring" style={{ "--score-color": level.color } as React.CSSProperties}>
<svg viewBox="0 0 120 120">
<circle cx="60" cy="60" r="52" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="8" />
<circle
cx="60" cy="60" r="52" fill="none"
stroke={level.color} strokeWidth="8"
strokeLinecap="round"
strokeDasharray={`${pct * 3.27} 327`}
transform="rotate(-90 60 60)"
className="nomad-score-arc"
/>
</svg>
<div className="nomad-score-center">
<strong>{pct}</strong>
<span>就绪度</span>
</div>
</div>
<h3 className="nomad-level-label">{level.label}</h3>
<p className="nomad-level-desc">{level.desc}</p>
<div className="nomad-result-actions">
<button className="btn btn-ghost" onClick={reset}>🔄 重新测评</button>
<a href="#destinations" className="btn btn-primary">探索目的地 🌍</a>
</div>
</div>
)}
</div>
</div>
</section>
);
}