feat(layout): add Navigation, Footer and Logo components with root layout shell
This commit is contained in:
parent
ba5287add8
commit
c441ed7026
@ -1,5 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Megrim, Audiowide, Cinzel, Inter } from "next/font/google";
|
import { Megrim, Audiowide, Cinzel, Inter } from "next/font/google";
|
||||||
|
import Navigation from "@/components/Navigation";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const megrim = Megrim({
|
const megrim = Megrim({
|
||||||
@ -51,7 +53,11 @@ export default function RootLayout({
|
|||||||
lang="zh-CN"
|
lang="zh-CN"
|
||||||
className={`${megrim.variable} ${audiowide.variable} ${cinzel.variable} ${inter.variable} h-full antialiased`}
|
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>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
154
src/app/page.tsx
154
src/app/page.tsx
@ -1,86 +1,88 @@
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex flex-col items-center justify-center px-6 py-16">
|
<section className="px-6 py-20 sm:py-28">
|
||||||
{/* Logo */}
|
<div className="max-w-5xl mx-auto flex flex-col items-center">
|
||||||
<h1
|
{/* Logo */}
|
||||||
className="font-logo text-7xl sm:text-8xl tracking-[0.5em] uppercase glow-text-purple text-center mb-4"
|
<h1
|
||||||
style={{ paddingLeft: "0.5em" }}
|
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-3 text-5xl sm:text-6xl align-middle">
|
Cyber
|
||||||
✦
|
<span className="text-purple-300 mx-2 sm:mx-3 text-4xl sm:text-6xl align-middle">
|
||||||
</span>
|
✦
|
||||||
Star
|
</span>
|
||||||
</h1>
|
Star
|
||||||
|
</h1>
|
||||||
|
|
||||||
{/* Subtitle */}
|
{/* Subtitle */}
|
||||||
<p className="font-label text-sm tracking-[0.4em] uppercase text-purple-300 mb-2">
|
<p className="font-label text-xs sm:text-sm tracking-[0.4em] uppercase text-purple-300 mb-2">
|
||||||
Virtual Idol Debut Project
|
Virtual Idol Debut Project
|
||||||
</p>
|
</p>
|
||||||
<p className="text-white/60 text-sm mb-12">
|
<p className="text-white/60 text-sm mb-10">
|
||||||
虚拟偶像 Top12 出道企划
|
虚拟偶像 Top12 出道企划
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Status badge */}
|
{/* 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">
|
<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="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">
|
<span className="font-label text-xs tracking-widest text-purple-300 uppercase">
|
||||||
Phase 2 · Theme Ready
|
Phase 3 · Layout Ready
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Theme verification swatches */}
|
{/* Theme verification swatches */}
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3 max-w-2xl">
|
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3 max-w-2xl">
|
||||||
{[
|
{[
|
||||||
{ name: "purple-300", className: "bg-purple-300" },
|
{ name: "purple-300", className: "bg-purple-300" },
|
||||||
{ name: "purple-500", className: "bg-purple-500" },
|
{ name: "purple-500", className: "bg-purple-500" },
|
||||||
{ name: "purple-700", className: "bg-purple-700" },
|
{ name: "purple-700", className: "bg-purple-700" },
|
||||||
{ name: "magenta", className: "bg-magenta" },
|
{ name: "magenta", className: "bg-magenta" },
|
||||||
{ name: "pink-400", className: "bg-pink-400" },
|
{ name: "pink-400", className: "bg-pink-400" },
|
||||||
{ name: "cyan-400", className: "bg-cyan-400" },
|
{ name: "cyan-400", className: "bg-cyan-400" },
|
||||||
].map((c) => (
|
].map((c) => (
|
||||||
<div key={c.name} className="text-center">
|
<div key={c.name} className="text-center">
|
||||||
<div
|
<div
|
||||||
className={`${c.className} h-16 rounded-lg border border-white/10`}
|
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>
|
<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">
|
||||||
</div>
|
<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 */}
|
<p className="mt-16 text-white/40 text-xs font-mono">
|
||||||
<div className="mt-12 grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl w-full">
|
Phase 4 → Core Components 即将开始
|
||||||
<div className="bg-surface border border-white/10 rounded-xl p-5">
|
</p>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
<p className="mt-16 text-white/40 text-xs font-mono">
|
|
||||||
Phase 3 → Layout + Navigation 即将开始
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/components/Footer.tsx
Normal file
57
src/components/Footer.tsx
Normal 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
41
src/components/Logo.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/components/Navigation.tsx
Normal file
110
src/components/Navigation.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user