first commit
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useScroll, useSpring } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import type { Locale } from "@/i18n-config";
|
||||
import type { BlogPost } from "@/data/blog";
|
||||
|
||||
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
|
||||
|
||||
export default function BlogDetailClient({
|
||||
lang,
|
||||
dict,
|
||||
post,
|
||||
}: {
|
||||
lang: Locale;
|
||||
dict: any;
|
||||
post: BlogPost;
|
||||
}) {
|
||||
const { scrollYProgress } = useScroll();
|
||||
const scaleX = useSpring(scrollYProgress, {
|
||||
stiffness: 100,
|
||||
damping: 30,
|
||||
restDelta: 0.001,
|
||||
});
|
||||
|
||||
const content = post[lang] || post.en;
|
||||
const accent = "#FFE600"; // Signature yellow
|
||||
|
||||
return (
|
||||
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
|
||||
{/* Scroll indicator */}
|
||||
<motion.div
|
||||
className="fixed top-0 left-0 right-0 h-1.5 bg-[#FFE600] z-50 origin-[0%]"
|
||||
style={{ scaleX }}
|
||||
/>
|
||||
|
||||
<Header lang={lang} dict={dict.nav} />
|
||||
|
||||
<main className="pt-32 pb-24 px-6 max-w-4xl mx-auto">
|
||||
{/* Back Link */}
|
||||
<motion.div
|
||||
className="mb-8"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
>
|
||||
<Link
|
||||
href={`/${lang}/blog`}
|
||||
className="inline-flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="19" y1="12" x2="5" y2="12" />
|
||||
<polyline points="12 19 5 12 12 5" />
|
||||
</svg>
|
||||
{dict.blog.back}
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* Article Metadata */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<span
|
||||
className="font-mono text-[9px] tracking-[0.2em] uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#6A6460]"
|
||||
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
|
||||
>
|
||||
{content.category}
|
||||
</span>
|
||||
<span className="font-mono text-[9px] text-[#A0998E]">
|
||||
{post.date}
|
||||
</span>
|
||||
<span className="text-[#C8C2B8]">•</span>
|
||||
<span className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">
|
||||
{content.readingTime}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<motion.h1
|
||||
className="font-display font-black text-4xl sm:text-5xl lg:text-6xl uppercase leading-[0.95] tracking-tight mb-8"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: expo }}
|
||||
>
|
||||
{content.title}
|
||||
</motion.h1>
|
||||
|
||||
{/* Author Bio Row */}
|
||||
<motion.div
|
||||
className="flex items-center gap-4 py-6 border-y border-[#C8C2B8] mb-12"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<div className="w-10 h-10 border border-[#C8C2B8] bg-[#FFE600] flex items-center justify-center font-display font-black text-xs text-[#0A0A0A]">
|
||||
{post.author.charAt(0)}{post.author.split(" ")[1]?.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-display font-black text-[12px] uppercase text-[#0A0A0A]">
|
||||
{post.author}
|
||||
</div>
|
||||
<div className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">
|
||||
{post.authorRole}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Featured Image */}
|
||||
{post.image && (
|
||||
<motion.div
|
||||
className="border border-[#C8C2B8] p-2 bg-[#EDE8E0] mb-12 relative overflow-hidden group"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<img
|
||||
src={post.image}
|
||||
alt={content.title}
|
||||
className="w-full h-auto object-cover grayscale contrast-125 group-hover:grayscale-0 transition-all duration-700"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Article Body Content */}
|
||||
<motion.article
|
||||
className="prose prose-stone max-w-none text-[#4A4440] leading-relaxed text-[15px] space-y-6"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
{/* Render paragraph chunks dynamically */}
|
||||
{content.content.split("\n\n").map((paragraph, index) => {
|
||||
if (paragraph.startsWith("## ")) {
|
||||
return (
|
||||
<h2 key={index} className="font-display font-black text-2xl uppercase text-[#0A0A0A] pt-6 mb-2">
|
||||
{paragraph.replace("## ", "")}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
if (paragraph.startsWith("### ")) {
|
||||
return (
|
||||
<h3 key={index} className="font-display font-black text-lg uppercase text-[#0A0A0A] pt-4 mb-2">
|
||||
{paragraph.replace("### ", "")}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
if (paragraph.startsWith("> ")) {
|
||||
return (
|
||||
<blockquote key={index} className="border-l-4 border-[#FFE600] pl-6 font-display font-bold uppercase text-[13px] text-[#0A0A0A] italic my-6">
|
||||
{paragraph.replace("> ", "")}
|
||||
</blockquote>
|
||||
);
|
||||
}
|
||||
if (paragraph.startsWith("- ")) {
|
||||
return (
|
||||
<ul key={index} className="list-disc pl-6 space-y-2 font-mono text-xs text-[#6A6460]">
|
||||
{paragraph.split("\n").map((li, idx) => (
|
||||
<li key={idx}>{li.replace("- ", "")}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
if (paragraph.includes("```")) {
|
||||
const codeLines = paragraph.split("\n").filter(l => !l.includes("```"));
|
||||
const codeHeader = codeLines[0]?.startsWith("//") ? codeLines.shift() : "";
|
||||
return (
|
||||
<div key={index} className="font-mono text-xs bg-[#EDE8E0] p-5 border border-[#C8C2B8] my-6 overflow-x-auto text-[#0A0A0A]">
|
||||
{codeHeader && <div className="text-[#A0998E] pb-3 border-b border-[#C8C2B8] mb-3">{codeHeader}</div>}
|
||||
<pre className="whitespace-pre-wrap leading-relaxed">{codeLines.join("\n")}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <p key={index}>{paragraph}</p>;
|
||||
})}
|
||||
</motion.article>
|
||||
|
||||
{/* Tags Block */}
|
||||
<div className="flex flex-wrap gap-2 pt-12 mt-12 border-t border-[#C8C2B8]">
|
||||
{content.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="font-mono text-[9px] tracking-wider uppercase px-2.5 py-1.5 border border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A] transition-colors cursor-default"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer lang={lang} dict={dict} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user