Files
ai-directory-platform/sites/the-ai-ledger/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

235 lines
9.1 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.
// 自动生成:站点首页组件(源自底座 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 };