- nav: center links (首页/排行榜/我的), right-side AuthMenu + RemainingVotesBadge; image logo with responsive sizing - auth: replace /login route with global LoginModal triggered anywhere; "我的" intercepts unauth users with post-login redirect - home: full-screen Hero, redesigned Top12 (12 pill cards, top-3 glow), scroll-snap mandatory between Hero/Top12/candidates - home: candidates section with sticky filter that gains frosted-glass bg when stuck (matches nav) - filter: simplified tags (全部/舞蹈/声乐/rap/全能型); ArtistCard uniform purple vote button - ranking/me: remove Top12Bar; me header stacks 编辑资料/退出登录 vertically - typography: font-logo set to Orbitron; ✦ glyph in CYBER ✦ STAR preserved - layout: max-w-[1500px] unified across pages - docs: add design-spec.md + design-spec.html with full visual spec (lucide SVG, zero emoji policy) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { usePathname } from "next/navigation";
|
||
import { useSession } from "next-auth/react";
|
||
import { cn } from "@/lib/cn";
|
||
import { useLoginModalStore } from "@/lib/login-modal-store";
|
||
|
||
const NAV_ITEMS: Array<{
|
||
label: string;
|
||
href: string;
|
||
/** 若 true,未登录时点击会拦截并弹出登录弹窗 */
|
||
requireAuth?: boolean;
|
||
}> = [
|
||
{ label: "首页", href: "/" },
|
||
{ label: "排行榜", href: "/ranking" },
|
||
{ label: "我的", href: "/me", requireAuth: true },
|
||
];
|
||
|
||
interface NavLinksProps {
|
||
className?: string;
|
||
mobile?: boolean;
|
||
}
|
||
|
||
export default function NavLinks({ className, mobile = false }: NavLinksProps) {
|
||
const pathname = usePathname();
|
||
const { status } = useSession();
|
||
const openLogin = useLoginModalStore((s) => s.show);
|
||
|
||
const isActive = (href: string) => {
|
||
if (href === "/") return pathname === "/";
|
||
return pathname.startsWith(href);
|
||
};
|
||
|
||
const handleClick = (
|
||
e: React.MouseEvent<HTMLAnchorElement>,
|
||
item: (typeof NAV_ITEMS)[number],
|
||
) => {
|
||
if (item.requireAuth && status !== "authenticated") {
|
||
e.preventDefault();
|
||
// 登录成功后直接跳目标页面(如「我的」→ /me),不回首页
|
||
if (status === "unauthenticated") openLogin(item.href);
|
||
}
|
||
};
|
||
|
||
if (mobile) {
|
||
return (
|
||
<ul
|
||
className={cn(
|
||
"flex items-center gap-6 px-6 py-2.5 text-[13px] tracking-[0.1em] whitespace-nowrap",
|
||
className,
|
||
)}
|
||
>
|
||
{NAV_ITEMS.map((item) => {
|
||
const active = isActive(item.href);
|
||
return (
|
||
<li key={item.href}>
|
||
<Link
|
||
href={item.href}
|
||
onClick={(e) => handleClick(e, item)}
|
||
className={cn(
|
||
"transition-colors",
|
||
active ? "text-purple-300" : "text-white/55",
|
||
)}
|
||
>
|
||
{item.label}
|
||
</Link>
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<ul
|
||
className={cn(
|
||
"items-center gap-8 text-sm tracking-[0.1em]",
|
||
className,
|
||
)}
|
||
>
|
||
{NAV_ITEMS.map((item) => {
|
||
const active = isActive(item.href);
|
||
return (
|
||
<li key={item.href}>
|
||
<Link
|
||
href={item.href}
|
||
onClick={(e) => handleClick(e, item)}
|
||
className={cn(
|
||
"relative py-1 transition-colors",
|
||
active
|
||
? "text-purple-300 glow-text-purple"
|
||
: "text-white/65 hover:text-white",
|
||
)}
|
||
>
|
||
{item.label}
|
||
{active && (
|
||
<span
|
||
className="absolute -bottom-1 left-0 right-0 h-px bg-purple-400"
|
||
style={{ boxShadow: "0 0 8px #a78bfa" }}
|
||
/>
|
||
)}
|
||
</Link>
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
);
|
||
}
|