85 lines
4.2 KiB
TypeScript
85 lines
4.2 KiB
TypeScript
"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={`/#trip`} 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>
|
||
);
|
||
}
|