112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
import { Link } from "@/i18n/navigation";
|
|
import { ArrowRight, Sparkles } from "lucide-react";
|
|
import { Container } from "@/components/ui/layout";
|
|
import { buttonClassName } from "@/components/ui/button-variants";
|
|
|
|
/**
|
|
* 首页 Hero 区。
|
|
*
|
|
* 对应 DESIGN.md 的 hero-band-sky:大气渐变(天空蓝 → 柔奶油)作为背景,
|
|
* 居中标题、副标题与按钮行。品牌薄荷绿仅用于单个强调 CTA。
|
|
*
|
|
* 这里用 CSS 渐变 + 径向光晕替代插画资源,避免引入额外图片依赖,
|
|
* 同时保持规范要求的「氛围感渐变」基调。
|
|
*/
|
|
export interface HomeHeroProps {
|
|
eyebrow: string;
|
|
title: string;
|
|
subtitle: string;
|
|
primaryLabel: string;
|
|
secondaryLabel: string;
|
|
stats: {
|
|
posts: number;
|
|
tags: number;
|
|
since: string;
|
|
postsLabel: string;
|
|
tagsLabel: string;
|
|
sinceLabel: string;
|
|
};
|
|
}
|
|
|
|
export function HomeHero({
|
|
eyebrow,
|
|
title,
|
|
subtitle,
|
|
primaryLabel,
|
|
secondaryLabel,
|
|
stats,
|
|
}: HomeHeroProps) {
|
|
return (
|
|
<section
|
|
aria-labelledby="hero-heading"
|
|
className="relative overflow-hidden border-b border-hairline-soft"
|
|
>
|
|
{/* 氛围渐变背景(装饰性,对辅助技术隐藏) */}
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-0 -z-10 bg-gradient-to-b from-hero-sky-from via-[#d9dbe0] to-hero-sky-to"
|
|
/>
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-0 -z-10 opacity-70 [background:radial-gradient(60%_50%_at_50%_0%,rgba(255,255,255,0.85),transparent_70%)]"
|
|
/>
|
|
|
|
<Container className="flex flex-col items-center py-section text-center sm:py-section-lg lg:py-hero">
|
|
{/* 大写微标签 */}
|
|
<p className="inline-flex items-center gap-xs rounded-full border border-white/60 bg-white/70 px-md py-[6px] text-micro-uppercase font-semibold uppercase tracking-[0.5px] text-steel backdrop-blur-sm">
|
|
<Sparkles className="size-3.5 text-brand-green-deep" aria-hidden="true" />
|
|
{eyebrow}
|
|
</p>
|
|
|
|
<h1
|
|
id="hero-heading"
|
|
className="mt-lg w-full max-w-[880px] text-heading-2 font-semibold text-ink text-balance-pretty sm:text-heading-1 lg:text-hero-display"
|
|
>
|
|
{title}
|
|
</h1>
|
|
|
|
<p className="mt-lg w-full max-w-[640px] text-body-md text-slate text-balance-pretty sm:text-subtitle">
|
|
{subtitle}
|
|
</p>
|
|
|
|
{/* 按钮行:品牌绿强调 CTA + 描边次级按钮 */}
|
|
<div className="mt-xxl flex flex-col items-center gap-sm sm:flex-row">
|
|
<Link
|
|
href="/#posts"
|
|
className={buttonClassName({ variant: "accent", size: "lg" })}
|
|
>
|
|
{primaryLabel}
|
|
<ArrowRight className="size-4" aria-hidden="true" />
|
|
</Link>
|
|
|
|
<Link
|
|
href="/about"
|
|
className={buttonClassName({ variant: "secondary", size: "lg" })}
|
|
>
|
|
{secondaryLabel}
|
|
</Link>
|
|
</div>
|
|
|
|
{/* 站点统计:窄屏下压缩间距,避免三个卡片把布局撑出横向滚动 */}
|
|
<dl className="mt-section grid w-full min-w-0 max-w-[560px] grid-cols-3 gap-xs sm:mt-hero sm:gap-md">
|
|
<StatItem value={String(stats.posts)} label={stats.postsLabel} />
|
|
<StatItem value={String(stats.tags)} label={stats.tagsLabel} />
|
|
<StatItem value={stats.since} label={stats.sinceLabel} />
|
|
</dl>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function StatItem({ value, label }: { value: string; label: string }) {
|
|
return (
|
|
<div className="flex min-w-0 flex-col items-center gap-[2px] rounded-lg border border-white/60 bg-white/55 px-xs py-sm backdrop-blur-sm sm:px-md">
|
|
<dt className="sr-only">{label}</dt>
|
|
<dd className="font-mono text-heading-5 font-semibold text-ink sm:text-heading-4">
|
|
{value}
|
|
</dd>
|
|
<dd className="text-caption text-steel">{label}</dd>
|
|
</div>
|
|
);
|
|
}
|