104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
import toolsData from "../data/tools.json";
|
||
|
||
const categories = toolsData.categories as Array<{
|
||
icon: string;
|
||
title: string;
|
||
color: string;
|
||
bg: string;
|
||
text: string;
|
||
tools: Array<{ name: string; desc: string; href: string }>;
|
||
}>;
|
||
|
||
function truncateDesc(desc: string, maxLen = 5) {
|
||
if (!desc || desc.length <= maxLen) return desc;
|
||
return desc.slice(0, maxLen) + "...";
|
||
}
|
||
|
||
const toolStats = (() => {
|
||
const total = categories.reduce((s, c) => s + c.tools.length, 0);
|
||
return [
|
||
{ value: `${total}+`, label: "精选工具" },
|
||
{ value: String(categories.length), label: "分类" },
|
||
{ value: "数字游民", label: "出海" },
|
||
{ value: "佣金", label: "推荐" },
|
||
];
|
||
})();
|
||
|
||
export default function Tools() {
|
||
return (
|
||
<section id="tools" className="py-20 sm:py-28">
|
||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||
<div className="text-center">
|
||
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-sky-200 bg-sky-50 px-4 py-1.5 text-sm font-medium text-sky-700">
|
||
🚀 50+ 精选工具
|
||
</div>
|
||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
|
||
<span className="gradient-text">工具推荐</span>
|
||
</h2>
|
||
<p className="mx-auto mt-4 max-w-2xl text-lg text-slate-600">
|
||
数字游民出海必备工具精选,含游民社区、VPS、金融收款、出海掘金等,每个链接可点击跳转。
|
||
</p>
|
||
</div>
|
||
|
||
<div className="mt-14 grid gap-5 overflow-visible sm:grid-cols-2 lg:grid-cols-4">
|
||
{categories.map((cat) => (
|
||
<div
|
||
key={cat.title}
|
||
className="card-hover group overflow-visible rounded-2xl border border-slate-100 bg-white shadow-sm"
|
||
>
|
||
<div className={`h-1.5 bg-gradient-to-r ${cat.color}`} />
|
||
<div className="p-5">
|
||
<div className="mb-3 flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-2xl">{cat.icon}</span>
|
||
<h3 className="font-bold text-slate-900">{cat.title}</h3>
|
||
</div>
|
||
<span className={`rounded-full px-2 py-0.5 text-xs font-semibold ${cat.bg} ${cat.text}`}>
|
||
{cat.tools.length}
|
||
</span>
|
||
</div>
|
||
<div className="space-y-2">
|
||
{cat.tools.map((tool) => (
|
||
<a
|
||
key={tool.name}
|
||
href={tool.href}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="group/item flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm transition-colors hover:bg-slate-100/80 hover:text-sky-600"
|
||
>
|
||
<span className="font-medium text-slate-700">{tool.name}</span>
|
||
<span className="relative text-slate-400">
|
||
{truncateDesc(tool.desc)}
|
||
{tool.desc && tool.desc.length > 5 && (
|
||
<span className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 w-max max-w-[90vw] -translate-x-1/2 whitespace-normal break-words rounded-lg bg-slate-800 px-3 py-2 text-xs text-white opacity-0 shadow-lg transition-opacity group-hover/item:opacity-100">
|
||
{tool.desc}
|
||
</span>
|
||
)}
|
||
</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Stats bar */}
|
||
<div className="mt-14 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||
{toolStats.map((s) => (
|
||
<div
|
||
key={s.label}
|
||
className="rounded-2xl border border-slate-100 bg-white p-5 text-center shadow-sm"
|
||
>
|
||
<div className="text-3xl font-extrabold text-sky-600">
|
||
{s.value}
|
||
</div>
|
||
<div className="mt-1 text-sm text-slate-500">{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|