feat(layout): add Navigation, Footer and Logo components with root layout shell

This commit is contained in:
iye 2026-05-12 09:32:46 +08:00
parent ba5287add8
commit c441ed7026
5 changed files with 293 additions and 77 deletions

View File

@ -1,5 +1,7 @@
import type { Metadata } from "next";
import { Megrim, Audiowide, Cinzel, Inter } from "next/font/google";
import Navigation from "@/components/Navigation";
import Footer from "@/components/Footer";
import "./globals.css";
const megrim = Megrim({
@ -51,7 +53,11 @@ export default function RootLayout({
lang="zh-CN"
className={`${megrim.variable} ${audiowide.variable} ${cinzel.variable} ${inter.variable} h-full antialiased`}
>
<body className="min-h-full">{children}</body>
<body className="min-h-full flex flex-col">
<Navigation />
<main className="flex-1">{children}</main>
<Footer />
</body>
</html>
);
}

View File

@ -1,86 +1,88 @@
export default function Home() {
return (
<main className="min-h-screen flex flex-col items-center justify-center px-6 py-16">
{/* Logo */}
<h1
className="font-logo text-7xl sm:text-8xl tracking-[0.5em] uppercase glow-text-purple text-center mb-4"
style={{ paddingLeft: "0.5em" }}
>
Cyber
<span className="text-purple-300 mx-3 text-5xl sm:text-6xl align-middle">
</span>
Star
</h1>
<section className="px-6 py-20 sm:py-28">
<div className="max-w-5xl mx-auto flex flex-col items-center">
{/* Logo */}
<h1
className="font-logo text-6xl sm:text-8xl tracking-[0.4em] uppercase glow-text-purple text-center mb-4"
style={{ paddingLeft: "0.4em" }}
>
Cyber
<span className="text-purple-300 mx-2 sm:mx-3 text-4xl sm:text-6xl align-middle">
</span>
Star
</h1>
{/* Subtitle */}
<p className="font-label text-sm tracking-[0.4em] uppercase text-purple-300 mb-2">
Virtual Idol Debut Project
</p>
<p className="text-white/60 text-sm mb-12">
Top12
</p>
{/* Subtitle */}
<p className="font-label text-xs sm:text-sm tracking-[0.4em] uppercase text-purple-300 mb-2">
Virtual Idol Debut Project
</p>
<p className="text-white/60 text-sm mb-10">
Top12
</p>
{/* Status badge */}
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-purple-500/10 border border-purple-500/30 mb-12">
<span className="w-2 h-2 rounded-full bg-purple-400 animate-pulse-glow"></span>
<span className="font-label text-xs tracking-widest text-purple-300 uppercase">
Phase 2 · Theme Ready
</span>
</div>
{/* Status badge */}
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-purple-500/10 border border-purple-500/30 mb-12">
<span className="w-2 h-2 rounded-full bg-purple-400 animate-pulse-glow"></span>
<span className="font-label text-xs tracking-widest text-purple-300 uppercase">
Phase 3 · Layout Ready
</span>
</div>
{/* Theme verification swatches */}
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3 max-w-2xl">
{[
{ name: "purple-300", className: "bg-purple-300" },
{ name: "purple-500", className: "bg-purple-500" },
{ name: "purple-700", className: "bg-purple-700" },
{ name: "magenta", className: "bg-magenta" },
{ name: "pink-400", className: "bg-pink-400" },
{ name: "cyan-400", className: "bg-cyan-400" },
].map((c) => (
<div key={c.name} className="text-center">
<div
className={`${c.className} h-16 rounded-lg border border-white/10`}
/>
<p className="text-[10px] text-white/40 mt-1 font-mono">{c.name}</p>
{/* Theme verification swatches */}
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3 max-w-2xl">
{[
{ name: "purple-300", className: "bg-purple-300" },
{ name: "purple-500", className: "bg-purple-500" },
{ name: "purple-700", className: "bg-purple-700" },
{ name: "magenta", className: "bg-magenta" },
{ name: "pink-400", className: "bg-pink-400" },
{ name: "cyan-400", className: "bg-cyan-400" },
].map((c) => (
<div key={c.name} className="text-center">
<div
className={`${c.className} h-16 rounded-lg border border-white/10`}
/>
<p className="text-[10px] text-white/40 mt-1 font-mono">{c.name}</p>
</div>
))}
</div>
{/* Font verification */}
<div className="mt-12 grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl w-full">
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Logo Font · Megrim
</p>
<p className="font-logo text-3xl tracking-widest">Cyber Star</p>
</div>
))}
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Display Font · Audiowide
</p>
<p className="font-display text-3xl tracking-wider">VOTE 2026</p>
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Label Font · Cinzel
</p>
<p className="font-label text-xl tracking-widest font-semibold">
DEBUT PROJECT
</p>
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Body Font · Inter
</p>
<p className="font-body text-lg"> · Aria</p>
</div>
</div>
{/* Font verification */}
<div className="mt-12 grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl w-full">
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Logo Font · Megrim
</p>
<p className="font-logo text-3xl tracking-widest">Cyber Star</p>
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Display Font · Audiowide
</p>
<p className="font-display text-3xl tracking-wider">VOTE 2026</p>
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Label Font · Cinzel
</p>
<p className="font-label text-xl tracking-widest font-semibold">
DEBUT PROJECT
</p>
</div>
<div className="bg-surface border border-white/10 rounded-xl p-5">
<p className="font-label text-[10px] tracking-widest text-purple-300 uppercase mb-2">
Body Font · Inter
</p>
<p className="font-body text-lg"> · Aria</p>
</div>
<p className="mt-16 text-white/40 text-xs font-mono">
Phase 4 Core Components
</p>
</div>
<p className="mt-16 text-white/40 text-xs font-mono">
Phase 3 Layout + Navigation
</p>
</main>
</section>
);
}

57
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,57 @@
import Link from "next/link";
import Logo from "./Logo";
const FOOTER_LINKS = [
{ label: "活动规则", href: "/rules" },
{ label: "隐私协议", href: "/privacy" },
{ label: "用户协议", href: "/terms" },
{ label: "联系客服", href: "/support" },
] as const;
export default function Footer() {
return (
<footer className="border-t border-white/[0.06] bg-[rgba(8,5,26,0.6)] backdrop-blur-sm mt-20">
<div className="max-w-7xl mx-auto px-6 sm:px-8 py-10 grid gap-8 md:grid-cols-3 items-start">
{/* Brand */}
<div>
<Logo size="sm" href={null} />
<p className="mt-3 text-xs text-white/45 leading-relaxed">
Top12
<br />
Cyber Star · Virtual Idol Debut Project
</p>
</div>
{/* Links */}
<nav className="md:col-span-1">
<p className="font-label text-[10px] tracking-[0.3em] uppercase text-purple-300 mb-3">
Information
</p>
<ul className="space-y-2 text-sm">
{FOOTER_LINKS.map((link) => (
<li key={link.href}>
<Link
href={link.href}
className="text-white/55 hover:text-purple-300 transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
{/* Caption */}
<div className="text-xs text-white/40 md:text-right">
<p className="font-label tracking-[0.25em] uppercase text-purple-300/70 mb-2">
© 2026 Cyber Star
</p>
<p>All rights reserved.</p>
<p className="mt-1 font-mono text-white/30">
airlabs.art · powered by Next.js
</p>
</div>
</div>
</footer>
);
}

41
src/components/Logo.tsx Normal file
View File

@ -0,0 +1,41 @@
import Link from "next/link";
type LogoSize = "sm" | "md" | "lg" | "xl";
interface LogoProps {
size?: LogoSize;
href?: string | null;
className?: string;
}
const SIZE_MAP: Record<LogoSize, { text: string; star: string; gap: string }> = {
sm: { text: "text-base", star: "text-xs", gap: "mx-1" },
md: { text: "text-xl sm:text-2xl", star: "text-sm", gap: "mx-1.5" },
lg: { text: "text-4xl sm:text-5xl", star: "text-2xl", gap: "mx-2" },
xl: { text: "text-6xl sm:text-7xl", star: "text-4xl", gap: "mx-3" },
};
export default function Logo({
size = "md",
href = "/",
className = "",
}: LogoProps) {
const { text, star, gap } = SIZE_MAP[size];
const inner = (
<span
className={`font-logo tracking-[0.25em] uppercase glow-text-purple inline-flex items-baseline ${text} ${className}`}
>
Cyber
<span className={`text-purple-300 ${gap} ${star}`}></span>
Star
</span>
);
if (!href) return inner;
return (
<Link href={href} className="inline-block hover:opacity-90 transition-opacity">
{inner}
</Link>
);
}

View File

@ -0,0 +1,110 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import Logo from "./Logo";
const NAV_ITEMS = [
{ label: "HOME", href: "/" },
{ label: "VOTE", href: "/vote" },
{ label: "RANKING", href: "/ranking" },
{ label: "NEWS", href: "/news" },
{ label: "ABOUT", href: "/about" },
] as const;
export default function Navigation() {
const pathname = usePathname();
const isActive = (href: string) => {
if (href === "/") return pathname === "/";
return pathname.startsWith(href);
};
return (
<header className="sticky top-0 z-50 backdrop-blur-xl bg-[rgba(13,10,36,0.85)] border-b border-white/[0.08]">
<nav className="max-w-7xl mx-auto h-16 px-6 sm:px-8 flex items-center gap-8">
<Logo size="md" />
{/* Nav links */}
<ul className="hidden md:flex items-center gap-8 flex-1 font-display text-xs tracking-[0.25em]">
{NAV_ITEMS.map((item) => {
const active = isActive(item.href);
return (
<li key={item.href}>
<Link
href={item.href}
className={`relative py-1 transition-colors uppercase ${
active
? "text-purple-300 glow-text-purple"
: "text-white/65 hover:text-white"
}`}
>
{item.label}
{active && (
<span
className="absolute -bottom-0.5 left-0 right-0 h-px bg-purple-400"
style={{ boxShadow: "0 0 8px #a78bfa" }}
/>
)}
</Link>
</li>
);
})}
</ul>
{/* Right side */}
<div className="ml-auto flex items-center gap-3">
{/* Search button (icon-only on mobile) */}
<button
type="button"
aria-label="搜索"
className="w-9 h-9 rounded-md flex items-center justify-center text-white/65 hover:text-white hover:bg-white/[0.06] transition-colors"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
</button>
{/* Login button */}
<Link
href="/login"
className="font-display text-[10px] sm:text-xs tracking-[0.2em] uppercase px-4 sm:px-5 h-9 inline-flex items-center justify-center rounded border border-[var(--border-purple)] text-purple-300 hover:bg-purple-500/10 hover:shadow-[0_0_20px_rgba(139,92,246,0.3)] transition-all"
>
Login / Sign Up
</Link>
</div>
</nav>
{/* Mobile nav links (horizontal scroll) */}
<div className="md:hidden border-t border-white/[0.05] overflow-x-auto">
<ul className="flex items-center gap-6 px-6 py-2.5 font-display text-[11px] tracking-[0.25em] whitespace-nowrap">
{NAV_ITEMS.map((item) => {
const active = isActive(item.href);
return (
<li key={item.href}>
<Link
href={item.href}
className={`uppercase transition-colors ${
active ? "text-purple-300" : "text-white/55"
}`}
>
{item.label}
</Link>
</li>
);
})}
</ul>
</div>
</header>
);
}