84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { memo, useState } from "react";
|
|
import { City } from "../data/cities";
|
|
|
|
interface CityCardProps {
|
|
city: City;
|
|
rank: number;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
function CityCardComponent({ city, rank, onClick }: CityCardProps) {
|
|
const [hovered, setHovered] = useState(false);
|
|
|
|
return (
|
|
<div
|
|
role="button"
|
|
tabIndex={0}
|
|
onClick={onClick}
|
|
onKeyDown={(e) => e.key === "Enter" && onClick?.()}
|
|
className="city-card cursor-pointer relative overflow-hidden rounded-xl min-h-[200px]"
|
|
style={{
|
|
background: `linear-gradient(135deg, ${city.gradientFrom}, ${city.gradientTo})`,
|
|
}}
|
|
onMouseEnter={() => setHovered(true)}
|
|
onMouseLeave={() => setHovered(false)}
|
|
>
|
|
<span className="absolute bottom-8 right-2 text-[70px] sm:text-[90px] opacity-[0.12] select-none pointer-events-none leading-none">
|
|
{city.icon}
|
|
</span>
|
|
|
|
<div
|
|
className={`absolute inset-0 z-10 flex flex-col justify-between p-3 sm:p-4 transition-opacity duration-300 ${hovered ? "opacity-0" : "opacity-100"}`}
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<span className="text-white/70 text-base sm:text-xl font-bold">{rank}</span>
|
|
<span className="text-[10px] sm:text-xs bg-white/20 backdrop-blur-sm rounded-full px-1.5 sm:px-2 py-0.5 sm:py-1 text-white">
|
|
📡 {city.internetSpeed}Mbps
|
|
</span>
|
|
</div>
|
|
|
|
<div className="text-center -mt-2">
|
|
<h3 className="text-xl sm:text-[28px] font-bold text-white drop-shadow-md tracking-wide">
|
|
{city.name}
|
|
</h3>
|
|
<p className="text-xs sm:text-sm text-white/60 mt-0.5">
|
|
{city.countryEmoji} {city.country}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex items-center gap-1 mb-1.5">
|
|
<span className="text-[9px] sm:text-[11px] bg-white/15 backdrop-blur-sm rounded-full px-1.5 py-0.5 text-white/80">
|
|
👥 {city.nomadsNow} 人在此
|
|
</span>
|
|
<span className="text-[9px] sm:text-[11px] bg-white/15 backdrop-blur-sm rounded-full px-1.5 py-0.5 text-white/80">
|
|
🌡 {city.temperature}°C
|
|
</span>
|
|
</div>
|
|
<div className="flex items-end justify-between text-white text-xs sm:text-sm">
|
|
<span className="text-white/80 text-[10px] sm:text-xs line-clamp-1 max-w-[60%]">
|
|
{city.description}
|
|
</span>
|
|
<span className="font-semibold shrink-0">
|
|
¥{city.costPerMonth.toLocaleString()}/月
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className={`absolute inset-0 z-20 bg-[#2a2a2a]/95 hidden sm:flex flex-col justify-center p-4 sm:p-5 transition-opacity duration-300 ${hovered ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
|
>
|
|
<div className="text-center text-white">
|
|
<p className="text-lg font-bold">{city.name}</p>
|
|
<p className="text-sm text-white/70 mt-1">{city.description}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default memo(CityCardComponent);
|