- 语言路由: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)
235 lines
9.1 KiB
TypeScript
235 lines
9.1 KiB
TypeScript
// 自动生成:站点首页组件(源自底座 editorial 形态,品牌来自 @/site/brand)
|
||
import { siteConfig } from "@/site/brand";
|
||
import { BookOpen, PenTool, ArrowRight, Star } from "lucide-react";
|
||
import { getTranslations } from "next-intl/server";
|
||
import { LocaleLink } from "@/components/locale-link";
|
||
import { SectionHeader } from "@/kit/section";
|
||
import { getHomeDataSafe } from "@/kit/home-data";
|
||
|
||
/** Editorial:AI 媒体 + 工具目录(Serif / 内容优先) */
|
||
|
||
|
||
const navItems = [
|
||
{ label: "Stories", href: "/blog" },
|
||
{ label: "Guides", href: "/ai-tools-for" },
|
||
{ label: "Best Picks", href: "/best" },
|
||
{ label: "Directory", href: "/tools" },
|
||
{ label: "Rankings", href: "/rankings" },
|
||
{ label: "Submit", href: "/submit" },
|
||
];
|
||
|
||
const footerGroups = [
|
||
{
|
||
title: "Read",
|
||
links: [
|
||
{ label: "Latest Stories", href: "/blog" },
|
||
{ label: "Buying Guides", href: "/best" },
|
||
{ label: "Use Case Guides", href: "/ai-tools-for" },
|
||
],
|
||
},
|
||
{
|
||
title: "Directory",
|
||
links: [
|
||
{ label: "All Tools", href: "/tools" },
|
||
{ label: "Community Rankings", href: "/rankings" },
|
||
{ label: "Submit a Tool", href: "/submit" },
|
||
],
|
||
},
|
||
{
|
||
title: "Company",
|
||
links: [
|
||
{ label: "Contact", href: "/contact" },
|
||
{ label: "Terms", href: "/terms" },
|
||
{ label: "Privacy", href: "/privacy" },
|
||
],
|
||
},
|
||
];
|
||
|
||
async function SiteHome({ locale }: { locale: string }) {
|
||
const t = await getTranslations("siteHome");
|
||
const th = await getTranslations("hero");
|
||
const data = await getHomeDataSafe(6);
|
||
const featured = data.articles[0];
|
||
const editorPicks = data.editorsChoice.slice(0, 4);
|
||
|
||
return (
|
||
<div className="min-h-screen bg-background">
|
||
{/* Masthead:黑色报头条(当日日期 + 期号),报纸头版签名 */}
|
||
<div className="bg-foreground text-background">
|
||
<div className="mx-auto flex max-w-4xl items-center justify-between px-4 py-2 text-[11px] uppercase tracking-[0.25em] sm:px-6">
|
||
<span>{new Date().toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })}</span>
|
||
<span className="hidden sm:inline">Est. 2026</span>
|
||
<span>Daily</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Masthead */}
|
||
<section className="border-b border-border">
|
||
<div className="mx-auto max-w-4xl px-4 py-16 text-center sm:px-6 md:py-20">
|
||
<p className="text-xs font-medium uppercase tracking-[0.3em] text-muted-foreground">
|
||
{t("mastheadLabel")}
|
||
</p>
|
||
<h1 className="mt-4 text-4xl font-bold tracking-tight text-foreground md:text-5xl">
|
||
{th("title")}
|
||
</h1>
|
||
<p className="mx-auto mt-4 max-w-xl text-base leading-relaxed text-muted-foreground">
|
||
{th("description")}
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<div className="mx-auto max-w-4xl space-y-14 px-4 py-12 sm:px-6">
|
||
{/* Featured Story */}
|
||
{featured && (
|
||
<section>
|
||
<div className="mb-4 flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-primary">
|
||
<BookOpen className="h-3.5 w-3.5" />
|
||
{t("featuredLabel")}
|
||
</div>
|
||
<LocaleLink href={`/blog/${featured.slug}`} className="group block">
|
||
<h2 className="text-2xl font-bold leading-snug text-foreground group-hover:underline md:text-3xl">
|
||
{featured.title}
|
||
</h2>
|
||
{featured.description && (
|
||
<p className="mt-3 text-base leading-relaxed text-muted-foreground">
|
||
{featured.description}
|
||
</p>
|
||
)}
|
||
<span className="mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary">
|
||
{t("featuredRead")}
|
||
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
|
||
</span>
|
||
</LocaleLink>
|
||
</section>
|
||
)}
|
||
|
||
{/* Editor's Picks */}
|
||
{editorPicks.length > 0 && (
|
||
<section>
|
||
<div className="mb-4 flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-primary">
|
||
<PenTool className="h-3.5 w-3.5" />
|
||
{t("picksEditorialLabel")}
|
||
</div>
|
||
<div className="divide-y divide-border border-y border-border">
|
||
{editorPicks.map((tool, i) => (
|
||
<LocaleLink
|
||
key={tool.id}
|
||
href={`/tools/${tool.slug}`}
|
||
className="group flex items-baseline gap-4 py-4"
|
||
>
|
||
<span className="text-sm font-bold text-muted-foreground tabular-nums">
|
||
{String(i + 1).padStart(2, "0")}
|
||
</span>
|
||
<div className="min-w-0 flex-1">
|
||
<h3 className="text-base font-semibold text-foreground group-hover:underline">
|
||
{locale === "zh" && tool.nameZh ? tool.nameZh : tool.nameEn}
|
||
</h3>
|
||
<p className="mt-0.5 line-clamp-1 text-sm text-muted-foreground">
|
||
{tool.taglineEn || tool.descriptionEn}
|
||
</p>
|
||
</div>
|
||
{(tool.ratingAvg ?? 0) > 0 && (
|
||
<span className="inline-flex shrink-0 items-center gap-1 text-sm font-semibold text-foreground">
|
||
<Star className="h-3.5 w-3.5 fill-amber-400 text-amber-400" />
|
||
{(tool.ratingAvg ?? 0).toFixed(1)}
|
||
</span>
|
||
)}
|
||
</LocaleLink>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Best Lists(编辑内容型榜单) */}
|
||
{data.collections.length > 0 && (
|
||
<section>
|
||
<SectionHeader
|
||
title={t("guidesTitle")}
|
||
subtitle={t("guidesSubtitle")}
|
||
viewAllHref="/best"
|
||
viewAllLabel={t("guidesViewAll")}
|
||
/>
|
||
<div className="grid gap-6 sm:grid-cols-2">
|
||
{data.collections.slice(0, 4).map((col) => (
|
||
<LocaleLink
|
||
key={col.id}
|
||
href={`/best/${col.slug}`}
|
||
className="group border-t-2 border-foreground/80 pt-4"
|
||
>
|
||
<h3 className="text-lg font-bold leading-snug text-foreground group-hover:underline">
|
||
{locale === "zh" && col.nameZh ? col.nameZh : col.nameEn}
|
||
</h3>
|
||
<p className="mt-2 line-clamp-2 text-sm leading-relaxed text-muted-foreground">
|
||
{locale === "zh" && col.introZh ? col.introZh : col.introEn}
|
||
</p>
|
||
<p className="mt-2 text-xs uppercase tracking-wide text-muted-foreground">
|
||
{col.toolCount} {t("toolsCompared")}
|
||
</p>
|
||
</LocaleLink>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Latest articles */}
|
||
{data.articles.length > 1 && (
|
||
<section>
|
||
<SectionHeader title={t("storiesTitle")} viewAllHref="/blog" />
|
||
<div className="grid gap-6 sm:grid-cols-2">
|
||
{data.articles.slice(1).map((article) => (
|
||
<LocaleLink key={article.slug} href={`/blog/${article.slug}`} className="group">
|
||
<h3 className="text-base font-semibold leading-snug text-foreground group-hover:underline">
|
||
{article.title}
|
||
</h3>
|
||
{article.description && (
|
||
<p className="mt-1.5 line-clamp-2 text-sm text-muted-foreground">
|
||
{article.description}
|
||
</p>
|
||
)}
|
||
</LocaleLink>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Community signal */}
|
||
{data.latestReviews.length > 0 && (
|
||
<section>
|
||
<SectionHeader
|
||
title={t("communityTitle")}
|
||
viewAllHref="/tools"
|
||
viewAllLabel={t("communityViewAll")}
|
||
/>
|
||
<div className="grid gap-6 sm:grid-cols-2">
|
||
{data.latestReviews.slice(0, 4).map((review, i) => (
|
||
<blockquote
|
||
key={i}
|
||
className="border-l-2 border-primary/40 pl-4"
|
||
>
|
||
<p className="text-sm italic leading-relaxed text-foreground">
|
||
"{review.content.slice(0, 140)}
|
||
{review.content.length > 140 ? "..." : ""}"
|
||
</p>
|
||
<footer className="mt-2 text-xs text-muted-foreground">
|
||
{review.authorName} on{" "}
|
||
<LocaleLink
|
||
href={`/tools/${review.toolSlug}`}
|
||
className="font-medium text-foreground hover:underline"
|
||
>
|
||
{review.toolName}
|
||
</LocaleLink>
|
||
{" · "}
|
||
{review.rating}/5
|
||
</footer>
|
||
</blockquote>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export { SiteHome };
|