nomadweb/frontend/src/components/SpinGlobe.tsx
eric f13722123a Add spin globe, flight estimator, events calendar, and privacy page
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 23:10:54 -05:00

92 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { useRef, useState } from "react";
import Link from "next/link";
import type { Destination } from "@/lib/types";
interface Props {
destinations: Destination[];
}
export default function SpinGlobe({ destinations }: Props) {
const [spinning, setSpinning] = useState(false);
const [result, setResult] = useState<Destination | null>(null);
const [display, setDisplay] = useState<Destination | null>(null);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const spin = () => {
if (spinning || destinations.length === 0) return;
setSpinning(true);
setResult(null);
let ticks = 0;
const total = 18 + Math.floor(Math.random() * 8);
const winner = destinations[Math.floor(Math.random() * destinations.length)];
timerRef.current = setInterval(() => {
ticks++;
setDisplay(destinations[Math.floor(Math.random() * destinations.length)]);
if (ticks >= total) {
if (timerRef.current) clearInterval(timerRef.current);
setDisplay(winner);
setResult(winner);
setSpinning(false);
}
}, 90);
};
const shown = display || destinations[0];
return (
<section className="section spin-section" id="spin">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">🎲 SPIN</span>
<h2>命运转盘 · 下一站去哪?</h2>
<p>不知道去哪?让命运帮你选一座游民城市</p>
</div>
<div className="spin-card reveal">
<div className={`spin-orb${spinning ? " spinning" : ""}`}>
<span className="spin-emoji">{shown?.emoji || "🌍"}</span>
<div className="spin-orb-ring" />
<div className="spin-orb-ring spin-orb-ring-2" />
</div>
<h3 className="spin-city-name">
{shown ? `${shown.name}, ${shown.country}` : "准备就绪"}
</h3>
{shown && !spinning && !result && (
<p className="spin-hint">点击下方按钮,开启随机旅居冒险</p>
)}
{spinning && <p className="spin-hint">正在穿越时区… ✈️</p>}
{result && !spinning && (
<div className="spin-result">
<p className="spin-result-tag">🎯 命运指引你去</p>
<div className="spin-result-meta">
<span>💰 ¥{result.cost.toLocaleString()}/月</span>
<span>📶 {result.speed}Mbps</span>
<span>⭐ {result.rating}</span>
</div>
<p className="spin-result-desc">{result.description}</p>
<div className="spin-result-actions">
<Link href={`/destinations/${result.slug}`} className="btn btn-primary">
查看详情 →
</Link>
<button className="btn btn-ghost" onClick={spin}>再转一次 🎲</button>
</div>
</div>
)}
{!result && (
<button className="btn btn-primary spin-btn" onClick={spin} disabled={spinning}>
{spinning ? "旋转中…" : "🎲 转动命运转盘"}
</button>
)}
</div>
</div>
</section>
);
}