202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
import Link from "next/link";
|
|
import { ArrowUpRight, Sparkles } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
|
|
type NavLinkItem = {
|
|
id: string;
|
|
name: string;
|
|
url: string;
|
|
};
|
|
|
|
type StatItem = {
|
|
label: string;
|
|
value: string;
|
|
};
|
|
|
|
const mainTabs = [
|
|
{ key: "home", label: "首页", href: "/lovart" },
|
|
{ key: "pricing", label: "定价", href: "/lovart/pricing" },
|
|
{ key: "news", label: "新闻", href: "/lovart/news" },
|
|
] as const;
|
|
|
|
const socialLinks = [
|
|
{ label: "Discord", href: "https://discord.com/invite/3Fr4KPrwbA" },
|
|
{ label: "X", href: "https://twitter.com/lovart_ai" },
|
|
{ label: "YouTube", href: "https://www.youtube.com/@lovart_ai" },
|
|
{ label: "LinkedIn", href: "https://www.linkedin.com/company/lovart-ai" },
|
|
{ label: "Instagram", href: "https://www.instagram.com/lovart.ai" },
|
|
{ label: "TikTok", href: "https://www.tiktok.com/@lovart_ai" },
|
|
];
|
|
|
|
function resolveDocsLink(navLinks: NavLinkItem[], keywords: string[]) {
|
|
const loweredKeywords = keywords.map((keyword) => keyword.toLowerCase());
|
|
return (
|
|
navLinks.find((link) =>
|
|
loweredKeywords.some((keyword) => link.name.toLowerCase().includes(keyword))
|
|
)?.url ?? null
|
|
);
|
|
}
|
|
|
|
export function MarketingShell({
|
|
siteName,
|
|
siteLogo,
|
|
navLinks,
|
|
active,
|
|
bannerText,
|
|
stats,
|
|
children,
|
|
}: {
|
|
siteName: string;
|
|
siteLogo?: string | null;
|
|
navLinks: NavLinkItem[];
|
|
active: "home" | "pricing" | "news";
|
|
bannerText?: string | null;
|
|
stats?: StatItem[];
|
|
children: ReactNode;
|
|
}) {
|
|
const docsUrl = resolveDocsLink(navLinks, ["文档", "docs"]);
|
|
const blogUrl = resolveDocsLink(navLinks, ["博客", "blog"]);
|
|
const changelogUrl = resolveDocsLink(navLinks, ["更新", "changelog", "release"]);
|
|
|
|
return (
|
|
<div className="min-h-dvh bg-[#050814] text-zinc-100">
|
|
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(1200px_620px_at_14%_-12%,rgba(14,165,233,0.35),transparent_52%),radial-gradient(920px_560px_at_92%_4%,rgba(251,146,60,0.2),transparent_48%),linear-gradient(160deg,#090f1f_0%,#050814_48%,#080b15_100%)]" />
|
|
<div className="pointer-events-none fixed inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:44px_44px] opacity-15" />
|
|
|
|
<header className="sticky top-0 z-30 border-b border-white/10 bg-[#050814]/80 backdrop-blur-xl">
|
|
{bannerText ? (
|
|
<div className="border-b border-cyan-300/20 bg-cyan-300/10 px-4 py-1 text-center text-[11px] text-cyan-50 sm:text-xs">
|
|
{bannerText}
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-4 sm:px-6">
|
|
<Link href="/lovart" className="group inline-flex items-center gap-2.5">
|
|
{siteLogo ? (
|
|
<img src={siteLogo} alt={siteName} className="h-8 w-8 rounded-lg object-cover" />
|
|
) : (
|
|
<span className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-300 to-blue-500">
|
|
<Sparkles className="h-4 w-4 text-zinc-900" />
|
|
</span>
|
|
)}
|
|
<span className="text-sm font-semibold tracking-wide text-zinc-100 transition group-hover:text-white">
|
|
{siteName}
|
|
</span>
|
|
</Link>
|
|
|
|
<nav className="hidden items-center gap-5 text-sm lg:flex">
|
|
{mainTabs.map((tab) => (
|
|
<Link
|
|
key={tab.key}
|
|
href={tab.href}
|
|
className={
|
|
active === tab.key
|
|
? "text-white"
|
|
: "text-zinc-300 transition hover:text-white"
|
|
}
|
|
>
|
|
{tab.label}
|
|
</Link>
|
|
))}
|
|
{blogUrl ? (
|
|
<a className="text-zinc-300 transition hover:text-white" href={blogUrl} rel="noreferrer" target="_blank">
|
|
博客
|
|
</a>
|
|
) : null}
|
|
{changelogUrl ? (
|
|
<a className="text-zinc-300 transition hover:text-white" href={changelogUrl} rel="noreferrer" target="_blank">
|
|
更新日志
|
|
</a>
|
|
) : null}
|
|
{docsUrl ? (
|
|
<a className="inline-flex items-center gap-1 text-zinc-300 transition hover:text-white" href={docsUrl} rel="noreferrer" target="_blank">
|
|
文档
|
|
<ArrowUpRight className="h-3.5 w-3.5" />
|
|
</a>
|
|
) : null}
|
|
</nav>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Link
|
|
href="/login"
|
|
className="rounded-lg border border-white/20 px-3 py-1.5 text-xs text-zinc-200 hover:border-white/40 hover:text-white"
|
|
>
|
|
登录
|
|
</Link>
|
|
<Link
|
|
href="/register"
|
|
className="rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-zinc-900 hover:bg-zinc-200"
|
|
>
|
|
免费开始
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{stats && stats.length > 0 ? (
|
|
<div className="border-t border-white/10">
|
|
<div className="mx-auto flex w-full max-w-6xl flex-wrap items-center gap-2 px-4 py-2 sm:px-6">
|
|
{stats.map((item) => (
|
|
<span
|
|
key={item.label}
|
|
className="inline-flex items-center gap-1 rounded-full border border-white/15 bg-white/[0.03] px-2.5 py-1 text-[11px] text-zinc-200"
|
|
>
|
|
<span className="text-zinc-400">{item.label}</span>
|
|
<strong className="font-semibold text-white">{item.value}</strong>
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</header>
|
|
|
|
<main className="relative z-10 mx-auto w-full max-w-6xl px-4 pb-20 pt-8 sm:px-6">{children}</main>
|
|
|
|
<div className="fixed bottom-3 left-1/2 z-30 flex -translate-x-1/2 items-center gap-1 rounded-full border border-white/15 bg-[#0b1020]/80 p-1 backdrop-blur sm:hidden">
|
|
{mainTabs.map((tab) => (
|
|
<Link
|
|
key={tab.key}
|
|
className={`rounded-full px-3 py-1 text-xs ${
|
|
active === tab.key ? "bg-white text-zinc-900" : "text-zinc-200"
|
|
}`}
|
|
href={tab.href}
|
|
>
|
|
{tab.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
<footer className="relative z-10 border-t border-white/10 px-4 py-7 text-xs text-zinc-400 sm:px-6">
|
|
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-3">
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
{socialLinks.map((item) => (
|
|
<a
|
|
key={item.label}
|
|
href={item.href}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
className="transition hover:text-zinc-100"
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
{navLinks.slice(0, 5).map((link) => (
|
|
<a
|
|
key={link.id}
|
|
href={link.url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="transition hover:text-zinc-100"
|
|
>
|
|
{link.name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|