NebulaCloud/app/[locale]/blog/[slug]/BlogPostClient.tsx
2025-09-15 17:28:58 +08:00

342 lines
15 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import Navigation from '../../../../components/Navigation';
import FloatingLanguageSwitcher from '../../../../components/FloatingLanguageSwitcher';
import Footer from '../../../../components/Footer';
import { Locale } from '../../../../lib/i18n';
import { getTranslations, Translations } from '../../../../lib/translations';
import { updateDocumentMeta } from '../../../../lib/seo-utils';
// Gentle Particle Background Component
function GentleParticleBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Set canvas size
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Gentle particle system
const particles: Array<{
x: number;
y: number;
vx: number;
vy: number;
size: number;
opacity: number;
}> = [];
// Create particles
for (let i = 0; i < 60; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.2,
vy: (Math.random() - 0.5) * 0.2,
size: Math.random() * 2 + 1,
opacity: Math.random() * 0.3 + 0.1,
});
}
// Animation loop
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach((particle) => {
// Update position
particle.x += particle.vx;
particle.y += particle.vy;
// Wrap around edges
if (particle.x < 0) particle.x = canvas.width;
if (particle.x > canvas.width) particle.x = 0;
if (particle.y < 0) particle.y = canvas.height;
if (particle.y > canvas.height) particle.y = 0;
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${particle.opacity})`;
ctx.fill();
});
requestAnimationFrame(animate);
};
animate();
return () => {
window.removeEventListener('resize', resizeCanvas);
};
}, []);
return (
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full"
style={{ pointerEvents: 'none' }}
/>
);
}
type Post = Translations['blog']['posts']['featured']; // Assuming all posts have the same structure
export default function BlogPostClient({
locale,
post,
t,
}: {
locale: Locale;
post: Post | null;
t: Translations;
}) {
const router = useRouter();
// Update meta tags when locale changes
useEffect(() => {
updateDocumentMeta(locale);
}, [locale]);
if (!post) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold text-white mb-4">Post Not Found</h1>
<Link
href={`/${locale}/blog`}
className="text-green-400 hover:text-green-300 underline"
>
Back to Blog
</Link>
</div>
</div>
);
}
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;
}
};
return (
<div className="min-h-screen relative overflow-hidden">
{/* Gentle Background with Overlay */}
<div
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `linear-gradient(rgba(30, 58, 138, 0.8), rgba(15, 23, 42, 0.9)), url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800"><defs><linearGradient id="gentle1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:%234F46E5;stop-opacity:1" /><stop offset="50%" style="stop-color:%237C3AED;stop-opacity:1" /><stop offset="100%" style="stop-color:%231E40AF;stop-opacity:1" /></linearGradient></defs><rect width="1200" height="800" fill="url(%23gentle1)"/><path d="M0,400 Q300,300 600,350 T1200,300 L1200,800 L0,800 Z" fill="%23312E81" opacity="0.5"/><path d="M0,500 Q400,400 800,450 T1200,400 L1200,800 L0,800 Z" fill="%231E3A8A" opacity="0.3"/></svg>')`,
}}
/>
{/* Gentle Particle Background */}
<GentleParticleBackground />
{/* Navigation */}
<Navigation locale={locale} />
{/* Floating Language Switcher */}
<FloatingLanguageSwitcher locale={locale} />
{/* Article Content */}
<article className="relative z-10 pt-32 pb-20 px-4">
<div className="max-w-4xl mx-auto">
{/* Back to Blog Link */}
<div className="mb-8">
<Link
href={`/${locale}/blog`}
className="inline-flex items-center text-white/80 hover:text-white transition-colors duration-200"
>
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
{t.blog.backToBlog}
</Link>
</div>
{/* Article Header */}
<header className="mb-12">
<div className="mb-6">
<span
className={`${getCategoryColor(post.category)} text-white px-4 py-2 rounded-full text-sm font-medium`}
>
{getCategoryName(post.category)}
</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6 leading-tight">
{post.title}
</h1>
<div className="flex flex-wrap items-center text-white/70 text-sm space-x-6 mb-6">
<div className="flex items-center space-x-2">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
<span>{post.author}</span>
</div>
<div className="flex items-center space-x-2">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span>{post.date}</span>
</div>
<div className="flex items-center space-x-2">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{post.readTime}</span>
</div>
</div>
<p className="text-xl text-white/90 leading-relaxed">{post.excerpt}</p>
</header>
{/* Article Body */}
<div className="bg-white/10 backdrop-blur-md rounded-3xl p-8 md:p-12 border border-white/20 shadow-2xl">
<div className="prose prose-lg prose-invert max-w-none">
{post.content.split('\n\n').map((paragraph, index) => (
<p key={index} className="text-white/90 leading-relaxed mb-6 text-lg">
{paragraph}
</p>
))}
</div>
</div>
{/* Article Footer */}
<footer className="mt-12 pt-8 border-t border-white/20">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center space-y-4 md:space-y-0">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-gradient-to-br from-green-400 to-blue-500 rounded-full flex items-center justify-center">
<span className="text-white font-bold text-lg">
{post.author.charAt(0)}
</span>
</div>
<div>
<p className="text-white font-medium">{post.author}</p>
<p className="text-white/70 text-sm">
Environmental Writer
</p>
</div>
</div>
<div className="flex space-x-4">
<button className="bg-white/20 hover:bg-white/30 text-white px-6 py-2 rounded-full transition-colors duration-200">
Share
</button>
<button className="bg-green-500 hover:bg-green-600 text-white px-6 py-2 rounded-full transition-colors duration-200">
Like
</button>
</div>
</div>
</footer>
{/* Related Posts Section */}
<section className="mt-16">
<h3 className="text-2xl font-bold text-white mb-8">Related Posts</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Link href={`/${locale}/blog/post1`}>
<div className="bg-white/10 backdrop-blur-md rounded-2xl p-6 border border-white/20 hover:bg-white/20 transition-all duration-200 cursor-pointer">
<h4 className="text-white font-semibold mb-2">
Player Spotlight: Building a Virtual Eco-City
</h4>
<p className="text-white/70 text-sm">
Meet Alex, a player who created an incredible sustainable
city...
</p>
</div>
</Link>
<Link href={`/${locale}/blog/post2`}>
<div className="bg-white/10 backdrop-blur-md rounded-2xl p-6 border border-white/20 hover:bg-white/20 transition-all duration-200 cursor-pointer">
<h4 className="text-white font-semibold mb-2">
10 Amazing Facts About Ocean Conservation
</h4>
<p className="text-white/70 text-sm">
Dive into fascinating facts about our oceans...
</p>
</div>
</Link>
</div>
</section>
</div>
</article>
{/* Footer */}
<Footer locale={locale} />
</div>
);
}