nomadweb/frontend/src/components/WorldMap.tsx
2026-08-29 02:43:20 -05:00

85 lines
4.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 { useState } from "react";
import Link from "next/link";
import type { Destination } from "@/lib/types";
interface Props { destinations: Destination[] }
export default function WorldMap({ destinations }: Props) {
const [selected, setSelected] = useState<Destination | null>(null);
return (
<section className="section map-section" id="map">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">🗺️ WORLD MAP</span>
<h2>全球游民热力图</h2>
<p>点击地图上的标记,探索热门数字游民聚集地</p>
</div>
<div className="map-wrapper reveal">
<div className="map-svg-container">
<svg className="world-map" viewBox="0 0 1000 500">
<defs>
<radialGradient id="mapGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="#4ECDC4" stopOpacity="0.15" />
<stop offset="100%" stopColor="#4ECDC4" stopOpacity="0" />
</radialGradient>
<filter id="pinGlow">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
</defs>
<rect width="1000" height="500" fill="url(#mapGlow)" rx="20" />
<g className="continents" opacity="0.25">
<path d="M180 120 Q220 100 280 110 Q320 130 310 170 Q290 200 250 210 Q200 220 170 190 Q150 160 180 120Z" fill="#4ECDC4" />
<path d="M420 100 Q480 90 530 110 Q560 140 550 180 Q520 210 470 200 Q430 180 420 140Z" fill="#4ECDC4" />
<path d="M530 130 Q600 120 660 140 Q700 170 690 210 Q650 240 590 230 Q540 210 530 170Z" fill="#4ECDC4" />
<path d="M700 160 Q780 150 830 180 Q860 220 840 260 Q790 280 730 260 Q690 230 700 190Z" fill="#4ECDC4" />
<path d="M200 260 Q250 250 290 280 Q310 320 280 360 Q240 380 200 360 Q170 330 180 290Z" fill="#4ECDC4" />
</g>
{destinations.map((d) => (
<g key={d.slug} className={`map-pin${selected?.slug === d.slug ? " active" : ""}`}
transform={`translate(${d.map_x}, ${d.map_y})`}
onClick={() => setSelected(d)} style={{ cursor: "pointer" }}>
<circle r="20" fill="rgba(255,107,107,0.15)" className="pin-pulse" />
<circle r="8" fill="#FF6B6B" filter="url(#pinGlow)" />
<text y="-16" textAnchor="middle" fontSize="14">{d.emoji}</text>
</g>
))}
</svg>
</div>
<div className="map-info-panel">
{selected ? (
<div className="map-info-city">
<div className="city-emoji">{selected.emoji}</div>
<h3>{selected.name}, {selected.country}</h3>
<span className="modal-tag">{selected.tag}</span>
<p className="city-desc">{selected.description}</p>
<div className="map-info-stats">
<div className="map-stat"><strong>¥{selected.cost.toLocaleString()}</strong>月生活费</div>
<div className="map-stat"><strong>{selected.speed}Mbps</strong>网速</div>
<div className="map-stat"><strong>{selected.temperature}°C</strong>均温</div>
<div className="map-stat"><strong>⭐ {selected.rating}</strong>评分</div>
</div>
<div className="map-info-actions">
<Link href={`/destinations/${selected.slug}`} className="btn btn-primary btn-sm">
查看详情 →
</Link>
<Link href="/plan" className="btn btn-ghost btn-sm">🗓️ 加入计划</Link>
</div>
</div>
) : (
<div className="map-info-default">
<span className="map-info-icon">👆</span>
<h3>选择一个城市</h3>
<p>点击地图上的标记查看详细信息</p>
</div>
)}
</div>
</div>
</div>
</section>
);
}