Files
2026-10-08 11:09:37 +08:00

235 lines
6.7 KiB
TypeScript

import type { Metadata } from "next";
import { getTranslations, setRequestLocale } from "next-intl/server";
import {
Briefcase,
MapPin,
Mail,
Code2,
Gauge,
Palette,
Wrench,
} from "lucide-react";
import { Container, Section } from "@/components/ui/layout";
import { Card } from "@/components/ui/card";
import { buttonClassName } from "@/components/ui/button-variants";
import { Reveal } from "@/components/motion/reveal";
import { siteConfig } from "@/lib/site-config";
/**
* 关于页渲染策略:ISR 缓存 60 秒。
* 路由段配置必须是字面量(Next.js 会静态分析)。
*/
export const revalidate = 60;
type AboutPageProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({
params,
}: AboutPageProps): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "About" });
return {
title: t("title"),
description: t("description"),
alternates: { canonical: `/${locale}/about` },
openGraph: {
type: "profile",
title: t("title"),
description: t("description"),
url: `${siteConfig.url}/${locale}/about`,
images: [{ url: siteConfig.ogImage }],
},
};
}
/** 关注方向 —— 图标仅为装饰,语义由文本承载 */
const FOCUS_AREAS = [
{
icon: Gauge,
title: "Web 性能",
description:
"关注核心网页指标、渲染路径与打包体积,把「快」当作可度量的工程目标而不是感觉。",
},
{
icon: Code2,
title: "开发者体验",
description:
"好的工具应该让正确的事情变简单。投入最多的地方是构建流程、类型系统与文档。",
},
{
icon: Palette,
title: "设计系统",
description:
"把设计规范翻译成可执行的代码约束,让一致性由类型系统保证,而不是靠代码评审。",
},
{
icon: Wrench,
title: "工程化实践",
description:
"测试策略、CI/CD、可观测性——那些看不见但决定了项目能走多远的基建。",
},
] as const;
export default async function AboutPage({ params }: AboutPageProps) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("About");
const { author } = siteConfig;
const facts = [
{ icon: Briefcase, label: t("role"), value: author.jobTitle },
{ icon: MapPin, label: t("location"), value: author.location },
{ icon: Mail, label: t("email"), value: author.email },
];
return (
<>
{/* 页头 */}
<Section
spacing="sm"
className="border-b border-hairline-soft bg-surface-soft"
>
<Container size="narrow">
<p className="text-micro-uppercase font-semibold uppercase tracking-[0.5px] text-steel">
{t("eyebrow")}
</p>
<h1 className="mt-sm text-heading-1 font-semibold text-ink">
{t("title")}
</h1>
<p className="mt-md max-w-[640px] text-subtitle text-steel text-balance-pretty">
{t("description")}
</p>
</Container>
</Section>
<Section spacing="default">
<Container size="narrow">
<div className="grid grid-cols-1 gap-xxl lg:grid-cols-[minmax(0,1fr)_280px]">
{/* 主栏:简介 + 关注方向 */}
<div className="flex flex-col gap-section">
<section aria-labelledby="intro-heading">
<h2
id="intro-heading"
className="text-heading-3 font-semibold text-ink"
>
{t("introTitle")}
</h2>
<div className="mt-md flex flex-col gap-md text-body-md leading-[1.75] text-slate">
<p>{author.bio}</p>
<p>
这个博客用来记录我在日常工作中遇到的问题和解决思路。
我倾向于写下那些「当时找不到答案、后来自己想明白了」的内容,
因为这类经验对别人往往也最有价值。
</p>
<p>
写作风格上,我更在意准确而不是完整:与其罗列所有可能性,
不如把一条路径讲透,并说清楚它为什么成立、在什么情况下会失效。
</p>
</div>
</section>
<section aria-labelledby="focus-heading">
<h2
id="focus-heading"
className="text-heading-3 font-semibold text-ink"
>
{t("techTitle")}
</h2>
<p className="mt-xs text-body-sm text-steel">
{t("techDescription")}
</p>
<div className="mt-lg grid grid-cols-1 gap-md sm:grid-cols-2">
{FOCUS_AREAS.map((area, index) => (
<Reveal key={area.title} delay={index * 0.05}>
<Card className="flex h-full flex-col gap-xs" padding="md">
<area.icon
className="size-5 text-brand-green-deep"
aria-hidden="true"
/>
<h3 className="text-heading-5 font-semibold text-ink">
{area.title}
</h3>
<p className="text-body-sm text-steel">
{area.description}
</p>
</Card>
</Reveal>
))}
</div>
</section>
</div>
{/* 侧栏:基本信息 + 联系方式 */}
<aside className="flex flex-col gap-xl">
<Card
padding="md"
variant="feature"
className="flex flex-col gap-md"
>
<h2 className="text-heading-5 font-semibold text-ink">
基本信息
</h2>
<dl className="flex flex-col gap-sm">
{facts.map((fact) => (
<div key={fact.label} className="flex items-start gap-sm">
<fact.icon
className="mt-[2px] size-4 shrink-0 text-steel"
aria-hidden="true"
/>
<div className="min-w-0">
<dt className="text-caption text-steel">
{fact.label}
</dt>
<dd className="break-words text-body-sm text-ink">
{fact.value}
</dd>
</div>
</div>
))}
</dl>
</Card>
<Card padding="md" className="flex flex-col gap-sm">
<h2 className="text-heading-5 font-semibold text-ink">
{t("contactTitle")}
</h2>
<p className="text-body-sm text-steel">
{t("contactDescription")}
</p>
<ul className="mt-xs flex flex-col gap-xs">
{siteConfig.social.map((item) => (
<li key={item.label}>
<a
href={item.href}
target={
item.href.startsWith("mailto:") ? undefined : "_blank"
}
rel="noopener noreferrer"
className={buttonClassName({
variant: "secondary",
size: "sm",
className: "w-full justify-start",
})}
>
{item.label}
</a>
</li>
))}
</ul>
</Card>
</aside>
</div>
</Container>
</Section>
</>
);
}