359 lines
20 KiB
TypeScript
359 lines
20 KiB
TypeScript
import { Metadata } from 'next';
|
|
import Link from 'next/link';
|
|
import { Locale } from '../../../lib/i18n';
|
|
import { getTranslations } from '../../../lib/translations';
|
|
import { generateCanonicalUrl, generateAlternateLinks } from '../../../lib/seo-utils';
|
|
|
|
interface BlogPageProps {
|
|
params: {
|
|
locale: Locale;
|
|
};
|
|
}
|
|
|
|
export async function generateMetadata({ params }: BlogPageProps): Promise<Metadata> {
|
|
const t = getTranslations(params.locale);
|
|
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://your-domain.com';
|
|
const canonicalUrl = generateCanonicalUrl('/blog', params.locale, baseUrl);
|
|
const alternateLinks = generateAlternateLinks('/blog', baseUrl);
|
|
|
|
return {
|
|
title: t.blog.title,
|
|
description: t.blog.subtitle,
|
|
alternates: {
|
|
canonical: canonicalUrl,
|
|
languages: Object.fromEntries(alternateLinks.map((link) => [link.hrefLang, link.href])),
|
|
},
|
|
openGraph: {
|
|
title: t.blog.title,
|
|
description: t.blog.subtitle,
|
|
url: canonicalUrl,
|
|
type: 'website',
|
|
},
|
|
};
|
|
}
|
|
|
|
export default function BlogPage({ params }: BlogPageProps) {
|
|
const t = getTranslations(params.locale);
|
|
|
|
// 获取所有博客文章
|
|
const blogPosts = [
|
|
{
|
|
id: 'featured',
|
|
...t.blog.posts.featured,
|
|
image: 'featured',
|
|
featured: true,
|
|
},
|
|
{
|
|
id: 'post1',
|
|
...t.blog.posts.post1,
|
|
image: 'post1',
|
|
featured: false,
|
|
},
|
|
{
|
|
id: 'post2',
|
|
...t.blog.posts.post2,
|
|
image: 'post2',
|
|
featured: false,
|
|
},
|
|
{
|
|
id: 'post3',
|
|
...t.blog.posts.post3,
|
|
image: 'post3',
|
|
featured: false,
|
|
},
|
|
{
|
|
id: 'post4',
|
|
...t.blog.posts.post4,
|
|
image: 'post4',
|
|
featured: false,
|
|
},
|
|
{
|
|
id: 'post5',
|
|
...t.blog.posts.post5,
|
|
image: 'post5',
|
|
featured: false,
|
|
},
|
|
];
|
|
|
|
const getCategoryColor = (category: string) => {
|
|
switch (category) {
|
|
case 'playerStories':
|
|
return 'bg-amber-500';
|
|
case 'development':
|
|
return 'bg-green-500';
|
|
case 'ecoFacts':
|
|
return 'bg-emerald-500';
|
|
default:
|
|
return 'bg-blue-500';
|
|
}
|
|
};
|
|
|
|
const getCategoryName = (category: string) => {
|
|
switch (category) {
|
|
case 'playerStories':
|
|
return t.blog.categories.playerStories;
|
|
case 'development':
|
|
return t.blog.categories.development;
|
|
case 'ecoFacts':
|
|
return t.blog.categories.ecoFacts;
|
|
default:
|
|
return category;
|
|
}
|
|
};
|
|
|
|
const getImageGradient = (imageId: string) => {
|
|
const gradients = {
|
|
featured: 'from-purple-500 via-blue-500 to-indigo-600',
|
|
post1: 'from-amber-500 via-orange-500 to-red-500',
|
|
post2: 'from-cyan-500 via-blue-500 to-indigo-500',
|
|
post3: 'from-green-500 via-emerald-500 to-teal-500',
|
|
post4: 'from-pink-500 via-rose-500 to-red-500',
|
|
post5: 'from-violet-500 via-purple-500 to-indigo-500',
|
|
};
|
|
return gradients[imageId as keyof typeof gradients] || 'from-gray-500 to-gray-600';
|
|
};
|
|
|
|
const featuredPost = blogPosts.find((post) => post.featured);
|
|
const regularPosts = blogPosts.filter((post) => !post.featured);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-black">
|
|
{/* Hero Section */}
|
|
<section className="relative py-20 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="text-center mb-16">
|
|
<h1 className="text-5xl md:text-6xl font-bold text-white mb-6">
|
|
{t.blog.title}
|
|
</h1>
|
|
<p className="text-xl text-white/80 max-w-3xl mx-auto leading-relaxed">
|
|
{t.blog.subtitle}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Category Filter */}
|
|
<div className="flex flex-wrap justify-center gap-4 mb-12">
|
|
<button className="bg-white/10 backdrop-blur-md text-white px-6 py-3 rounded-full border border-white/20 hover:bg-white/20 transition-all duration-200">
|
|
{t.blog.categories.all}
|
|
</button>
|
|
<button className="bg-amber-500/20 backdrop-blur-md text-amber-300 px-6 py-3 rounded-full border border-amber-500/30 hover:bg-amber-500/30 transition-all duration-200">
|
|
{t.blog.categories.playerStories}
|
|
</button>
|
|
<button className="bg-green-500/20 backdrop-blur-md text-green-300 px-6 py-3 rounded-full border border-green-500/30 hover:bg-green-500/30 transition-all duration-200">
|
|
{t.blog.categories.development}
|
|
</button>
|
|
<button className="bg-emerald-500/20 backdrop-blur-md text-emerald-300 px-6 py-3 rounded-full border border-emerald-500/30 hover:bg-emerald-500/30 transition-all duration-200">
|
|
{t.blog.categories.ecoFacts}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Featured Post */}
|
|
{featuredPost && (
|
|
<section className="px-4 sm:px-6 lg:px-8 mb-20">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="text-center mb-12">
|
|
<h2 className="text-3xl font-bold text-white mb-4">
|
|
{t.blog.featured}
|
|
</h2>
|
|
</div>
|
|
|
|
<Link href={`/${params.locale}/blog/${featuredPost.id}`}>
|
|
<article className="group cursor-pointer">
|
|
<div className="bg-white/10 backdrop-blur-md rounded-3xl overflow-hidden border border-white/20 shadow-2xl hover:shadow-3xl transition-all duration-500 transform hover:scale-[1.02]">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-0">
|
|
{/* Featured Post Image */}
|
|
<div className="relative h-80 lg:h-96 overflow-hidden">
|
|
<div
|
|
className={`absolute inset-0 bg-gradient-to-br ${getImageGradient(featuredPost.image)} opacity-90`}
|
|
>
|
|
<div className="absolute inset-0 bg-black/20"></div>
|
|
{/* Animated particles */}
|
|
<div className="absolute inset-0">
|
|
{[...Array(8)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="absolute w-2 h-2 bg-white/30 rounded-full animate-pulse group-hover:animate-bounce"
|
|
style={{
|
|
left: `${20 + i * 10}%`,
|
|
top: `${30 + (i % 3) * 20}%`,
|
|
animationDelay: `${i * 0.2}s`,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="absolute top-6 left-6">
|
|
<span
|
|
className={`${getCategoryColor(featuredPost.category)} text-white px-4 py-2 rounded-full text-sm font-medium shadow-lg`}
|
|
>
|
|
{getCategoryName(featuredPost.category)}
|
|
</span>
|
|
</div>
|
|
<div className="absolute top-6 right-6">
|
|
<div className="bg-white/20 backdrop-blur-sm rounded-full px-3 py-1">
|
|
<span className="text-white text-sm font-medium">
|
|
{t.blog.featured}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Featured Post Content */}
|
|
<div className="p-8 lg:p-12 flex flex-col justify-center">
|
|
<div className="text-white/70 text-sm mb-4">
|
|
<div className="flex items-center space-x-4">
|
|
<span>{featuredPost.author}</span>
|
|
<span>•</span>
|
|
<span>{featuredPost.date}</span>
|
|
<span>•</span>
|
|
<span>{featuredPost.readTime}</span>
|
|
</div>
|
|
</div>
|
|
<h3 className="text-3xl font-bold text-white mb-6 group-hover:text-green-300 transition-colors duration-200 leading-tight">
|
|
{featuredPost.title}
|
|
</h3>
|
|
<p className="text-white/80 mb-8 leading-relaxed text-lg">
|
|
{featuredPost.excerpt}
|
|
</p>
|
|
<div className="flex items-center">
|
|
<span className="text-green-300 font-medium group-hover:text-green-200 transition-colors duration-200 flex items-center">
|
|
{t.blog.readMore}
|
|
<svg
|
|
className="w-5 h-5 ml-2 transform group-hover:translate-x-1 transition-transform duration-200"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M17 8l4 4m0 0l-4 4m4-4H3"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Regular Posts Grid */}
|
|
<section className="px-4 sm:px-6 lg:px-8 pb-20">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="text-center mb-12">
|
|
<h2 className="text-3xl font-bold text-white mb-4">
|
|
{t.blog.categories.all}
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{regularPosts.map((post) => (
|
|
<Link key={post.id} href={`/${params.locale}/blog/${post.id}`}>
|
|
<article className="group cursor-pointer h-full">
|
|
<div className="bg-white/10 backdrop-blur-md rounded-2xl overflow-hidden border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:scale-[1.02] h-full flex flex-col">
|
|
{/* Post Image */}
|
|
<div className="relative h-48 overflow-hidden">
|
|
<div
|
|
className={`absolute inset-0 bg-gradient-to-br ${getImageGradient(post.image)} opacity-90`}
|
|
>
|
|
<div className="absolute inset-0 bg-black/20"></div>
|
|
{/* Mini particles */}
|
|
<div className="absolute inset-0">
|
|
{[...Array(4)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="absolute w-1 h-1 bg-white/40 rounded-full animate-pulse group-hover:animate-ping"
|
|
style={{
|
|
left: `${30 + i * 15}%`,
|
|
top: `${40 + i * 10}%`,
|
|
animationDelay: `${i * 0.3}s`,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="absolute top-4 left-4">
|
|
<span
|
|
className={`${getCategoryColor(post.category)} text-white px-3 py-1 rounded-full text-xs font-medium`}
|
|
>
|
|
{getCategoryName(post.category)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Post Content */}
|
|
<div className="p-6 flex-1 flex flex-col">
|
|
<div className="text-white/60 text-sm mb-3">
|
|
<div className="flex items-center space-x-3">
|
|
<span>{post.author}</span>
|
|
<span>•</span>
|
|
<span>{post.readTime}</span>
|
|
</div>
|
|
</div>
|
|
<h3 className="text-xl font-bold text-white mb-3 group-hover:text-green-300 transition-colors duration-200 line-clamp-2 flex-shrink-0">
|
|
{post.title}
|
|
</h3>
|
|
<p className="text-white/70 mb-4 line-clamp-3 flex-1">
|
|
{post.excerpt}
|
|
</p>
|
|
<div className="flex items-center justify-between mt-auto">
|
|
<span className="text-white/50 text-sm">
|
|
{post.date}
|
|
</span>
|
|
<span className="text-green-300 font-medium group-hover:text-green-200 transition-colors duration-200 flex items-center">
|
|
{t.blog.readMore}
|
|
<svg
|
|
className="w-4 h-4 ml-1 transform group-hover:translate-x-1 transition-transform duration-200"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 5l7 7-7 7"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Newsletter Subscription */}
|
|
<section className="px-4 sm:px-6 lg:px-8 pb-20">
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 backdrop-blur-md rounded-3xl p-8 lg:p-12 border border-green-500/30 text-center">
|
|
<h2 className="text-3xl font-bold text-white mb-4">{t.blog.stayUpdated}</h2>
|
|
<p className="text-white/80 mb-8 text-lg max-w-2xl mx-auto">
|
|
{t.blog.stayUpdatedDesc}
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center max-w-md mx-auto">
|
|
<input
|
|
type="email"
|
|
placeholder="your@email.com"
|
|
className="flex-1 px-6 py-3 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
|
/>
|
|
<button className="bg-green-500 hover:bg-green-600 text-white px-8 py-3 rounded-full font-semibold transition-all duration-200 transform hover:scale-105 whitespace-nowrap">
|
|
{t.blog.subscribe}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|