Files
ai-directory-platform/sites/ai-stack/src/site/home.tsx
T
AI Directory Platform 6baf0246d2 feat: 深度设计五站视觉语言 + 语言路由修复
- 语言路由:localeDetection 关闭,/ 固定英文、/zh 固定中文,URL 决定语言
  (不再按 cookie/Accept-Language 自动跳转;语言切换器显式切换不受影响)
- 五站独立视觉语言(data-theme 驱动的设计系统层):
  · AI Stack:深蓝渐变 Hero + 统计条 + 卡片左色条 hover(专业 SaaS)
  · LaunchAI:橙色渐变 Banner + 斜切底边 + 暖白底(活力社区)
  · The AI Ledger:黑色报头日期条 + 衬线字体 + 直角 + 双细线 + 报纸排版(媒体)
  · DevAI.tools:全站深色 + 等宽字体 + 终端 Hero + 扫描线纹理 + 系统状态条(终端)
  · findai:纯白极简 + 紫色聚焦光圈 + 无界卡片(搜索引擎)
- 修复 :first-of-type 误伤(hero 样式收敛到专用 class)
2026-09-09 19:22:58 +00:00

296 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
// 自动生成:站点首页组件(源自底座 classic 形态,品牌来自 @/site/brand)
import { siteConfig } from "@/site/brand";
import { Search, TrendingUp, Sparkles, Clock, Award, LayoutGrid, MessageSquareQuote } from "lucide-react";
import { getTranslations } from "next-intl/server";
import { ToolCard, ToolCardGrid } from "@/components/tool-card";
import { LocaleLink } from "@/components/locale-link";
import { SectionHeader } from "@/kit/section";
import { ThemeHeader } from "@/kit/theme-header";
import { ThemeFooter } from "@/kit/theme-footer";
import { getHomeDataSafe } from "@/kit/home-data";
/** Classic:专业 AI Directory(Clean / Dense / Professional) */
const navItems = [
{ label: "Discover", href: "/tools" },
{ label: "Categories", href: "/tools" },
{ label: "Use Cases", href: "/ai-tools-for" },
{ label: "Best Lists", href: "/best" },
{ label: "Rankings", href: "/rankings" },
{ label: "Blog", href: "/blog" },
{ label: "Submit", href: "/submit" },
];
const footerGroups = [
{
title: "Discover",
links: [
{ label: "All Tools", href: "/tools" },
{ label: "Use Cases", href: "/ai-tools-for" },
{ label: "Best Lists", href: "/best" },
{ label: "Rankings", href: "/rankings" },
],
},
{
title: "Community",
links: [
{ label: "Submit a Tool", href: "/submit" },
{ label: "Blog", href: "/blog" },
{ label: "Dashboard", href: "/dashboard" },
],
},
{
title: "Legal",
links: [
{ label: "Terms", href: "/terms" },
{ label: "Privacy", href: "/privacy" },
{ label: "Contact", href: "/contact" },
],
},
];
async function SiteHome({ locale }: { locale: string }) {
const t = await getTranslations("siteHome");
const th = await getTranslations("hero");
const data = await getHomeDataSafe(8);
return (
<div className="bg-background">
{/* Hero */}
<section className="hero-dark border-b border-border">
<div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 md:py-24">
<h1 className="mx-auto max-w-3xl text-3xl font-bold tracking-tight text-foreground sm:text-4xl md:text-5xl">
{th("title")}
</h1>
<p className="mx-auto mt-4 max-w-2xl text-sm text-muted-foreground sm:text-base">
{th("description")}
</p>
<form action={`/${locale}/tools`} className="mx-auto mt-8 max-w-xl">
<div className="relative">
<Search className="absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
name="search"
placeholder={t("searchPlaceholder")}
className="h-12 w-full rounded-full border border-border bg-background pl-11 pr-28 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
aria-label="Search AI tools"
/>
<button
type="submit"
className="absolute right-1.5 top-1.5 h-9 rounded-full bg-primary px-5 text-xs font-semibold text-primary-foreground transition-opacity hover:opacity-90"
>
{t("searchButton")}
</button>
</div>
</form>
{/* 统计条(深色 Hero 内的白色数字) */}
<div className="mx-auto mt-10 flex max-w-lg items-center justify-center gap-10 border-t border-white/15 pt-6">
{[
{ v: "1,000+", l: "AI tools" },
{ v: "14", l: "Categories" },
{ v: "7", l: "Rankings" },
].map((s) => (
<div key={s.l} className="text-center">
<div className="text-2xl font-bold text-white">{s.v}</div>
<div className="text-xs text-white/60">{s.l}</div>
</div>
))}
</div>
<div className="mt-8 flex flex-wrap items-center justify-center gap-2 text-xs">
<span className="text-white/60">{t("popularLabel")}</span>
{t.raw("popularSearches").map((q: string) => (
<LocaleLink
key={q}
href={`/tools?search=${q}`}
className="rounded-full bg-white/10 px-2.5 py-1 text-white/80 transition-colors hover:bg-white/20"
>
{q}
</LocaleLink>
))}
</div>
</div>
</section>
<div className="mx-auto max-w-7xl space-y-12 px-4 py-12 sm:px-6">
{/* Trending */}
{data.trending.length > 0 && (
<section>
<SectionHeader
title={t("trendingTitle")}
subtitle={t("trendingSubtitle")}
viewAllHref="/rankings?board=trending"
viewAllLabel={t("allRankings")}
/>
<ToolCardGrid tools={data.trending} locale={locale} ranked />
</section>
)}
{/* Categories */}
{data.categories.length > 0 && (
<section>
<SectionHeader
title={t("categoriesTitle")}
viewAllHref="/tools"
viewAllLabel={t("categoriesViewAll")}
/>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
{data.categories.slice(0, 10).map((category) => (
<LocaleLink
key={category.id}
href={`/category/${category.slug}`}
className="group rounded-xl border border-border p-4 transition-all hover:border-primary/30 hover:shadow-sm"
>
<LayoutGrid className="h-4 w-4 text-primary" />
<h3 className="mt-2 truncate text-sm font-medium text-foreground group-hover:text-primary">
{locale === "zh" && category.nameZh
? category.nameZh
: category.nameEn}
</h3>
<p className="mt-0.5 text-xs text-muted-foreground tabular-nums">
{category.toolCount} tools
</p>
</LocaleLink>
))}
</div>
</section>
)}
{/* New + Editor's Choice 两列 */}
<div className="grid gap-12 lg:grid-cols-2">
{data.newest.length > 0 && (
<section>
<SectionHeader
title={t("newTitle")}
viewAllHref="/rankings?board=new"
/>
<ToolCardGrid tools={data.newest.slice(0, 4)} locale={locale} />
</section>
)}
{data.editorsChoice.length > 0 && (
<section>
<SectionHeader
title={t("editorsTitle")}
viewAllHref="/rankings?board=editors-choice"
/>
<ToolCardGrid tools={data.editorsChoice.slice(0, 4)} locale={locale} />
</section>
)}
</div>
{/* Use Cases */}
{data.useCases.length > 0 && (
<section>
<SectionHeader
title={t("useCasesTitle")}
subtitle={t("useCasesSubtitle")}
viewAllHref="/ai-tools-for"
viewAllLabel={t("useCasesViewAll")}
/>
<div className="flex flex-wrap gap-2">
{data.useCases.slice(0, 12).map((uc) => (
<LocaleLink
key={uc.id}
href={`/ai-tools-for/${uc.slug}`}
className="rounded-full border border-border px-3.5 py-1.5 text-xs text-foreground transition-colors hover:border-primary/40 hover:bg-primary/5"
>
{locale === "zh" && uc.nameZh ? uc.nameZh : uc.nameEn}
<span className="ml-1.5 text-muted-foreground tabular-nums">
{uc.toolCount}
</span>
</LocaleLink>
))}
</div>
</section>
)}
{/* Best Lists */}
{data.collections.length > 0 && (
<section>
<SectionHeader
title={t("bestListsTitle")}
subtitle={t("bestListsSubtitle")}
viewAllHref="/best"
viewAllLabel={t("bestListsViewAll")}
/>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{data.collections.slice(0, 3).map((col) => (
<LocaleLink
key={col.id}
href={`/best/${col.slug}`}
className="group rounded-xl border border-border p-5 transition-all hover:border-primary/30 hover:shadow-sm"
>
<Award className="h-4 w-4 text-primary" />
<h3 className="mt-2 text-sm font-semibold text-foreground group-hover:text-primary">
{locale === "zh" && col.nameZh ? col.nameZh : col.nameEn}
</h3>
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
{locale === "zh" && col.introZh ? col.introZh : col.introEn}
</p>
<p className="mt-2 text-xs text-muted-foreground tabular-nums">
{col.toolCount} tools
</p>
</LocaleLink>
))}
</div>
</section>
)}
{/* Latest Reviews */}
{data.latestReviews.length > 0 && (
<section>
<SectionHeader title={t("reviewsTitle")} />
<div className="grid gap-3 md:grid-cols-2">
{data.latestReviews.map((review, i) => (
<LocaleLink
key={i}
href={`/tools/${review.toolSlug}#reviews`}
className="rounded-xl border border-border p-4 transition-colors hover:border-primary/30"
>
<div className="flex items-center gap-2">
<MessageSquareQuote className="h-3.5 w-3.5 text-primary" />
<span className="text-xs font-medium text-foreground">
{review.authorName}
</span>
<span className="text-xs text-muted-foreground">
on {review.toolName}
</span>
<span className="ml-auto text-xs font-semibold text-amber-500 tabular-nums">
{review.rating}/5
</span>
</div>
<p className="mt-2 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
{review.content}
</p>
</LocaleLink>
))}
</div>
</section>
)}
{/* Submit CTA */}
<section className="rounded-2xl border border-border bg-secondary/30 p-8 text-center">
<Sparkles className="mx-auto h-5 w-5 text-primary" />
<h2 className="mt-3 text-lg font-semibold text-foreground">
Built an AI tool?
</h2>
<p className="mx-auto mt-1 max-w-md text-sm text-muted-foreground">
Submit it to {siteConfig.brand.name} and get discovered by thousands of users
looking for tools like yours.
</p>
<LocaleLink
href="/submit"
className="mt-4 inline-flex items-center gap-1.5 rounded-full bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90"
>
<TrendingUp className="h-4 w-4" />
Submit your tool
</LocaleLink>
</section>
</div>
</div>
);
}
export { SiteHome };