Skip to content

Commit 8e00c2e

Browse files
feat: Implement blog functionality with dedicated pages, layout, and initial post data
1 parent c5202b3 commit 8e00c2e

5 files changed

Lines changed: 389 additions & 83 deletions

File tree

apps/web/app/blog/[slug]/page.tsx

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
'use client';
2+
3+
import Link from 'next/link';
4+
import { useParams, useRouter } from 'next/navigation';
5+
import { motion } from 'framer-motion';
6+
import {
7+
ArrowLeft,
8+
Clock,
9+
Calendar,
10+
User,
11+
Share2,
12+
ChevronRight,
13+
Command,
14+
Zap,
15+
ArrowUpRight
16+
} from 'lucide-react';
17+
import { GridBackground } from '../../../components/home';
18+
import { fadeUp, staggerContainer } from '../../../components/home/motion-variants';
19+
import { GlassCard } from '../../../components/ui/GlassCard';
20+
import posts from '../../../src/data/blog-posts.json';
21+
22+
export default function BlogPostPage() {
23+
const { slug } = useParams();
24+
const router = useRouter();
25+
26+
const post = posts.find(p => p.slug === slug);
27+
28+
if (!post) {
29+
return (
30+
<main className="min-h-screen flex items-center justify-center bg-[#030303] text-white p-6">
31+
<div className="text-center">
32+
<h1 className="text-4xl font-black mb-6">SIGNAL LOST.</h1>
33+
<p className="text-zinc-500 mb-8">The requested blog entry could not be located in the protocol.</p>
34+
<Link href="/blog">
35+
<button className="px-8 py-3 rounded-xl bg-purple-600 font-bold uppercase tracking-widest text-xs hover:bg-purple-500 transition-all">
36+
Return to Log
37+
</button>
38+
</Link>
39+
</div>
40+
</main>
41+
);
42+
}
43+
44+
const relatedPosts = posts.filter(p => p.slug !== slug).slice(0, 2);
45+
46+
return (
47+
<main className="relative min-h-screen overflow-hidden bg-[#030303] text-white selection:bg-purple-500/30 font-sans">
48+
{/* ── Background Infrastructure ── */}
49+
<GridBackground />
50+
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-purple-500/[0.01] to-transparent" />
51+
52+
{/* Ambient Glows */}
53+
<div className="absolute top-0 left-0 h-full w-full pointer-events-none overflow-hidden">
54+
<div className="absolute top-[0%] left-1/2 -translate-x-1/2 h-[600px] w-full bg-[radial-gradient(circle_at_50%_0%,rgba(168,85,247,0.03),transparent_70%)] blur-[100px]" />
55+
<div className="absolute bottom-[-10%] right-[-10%] h-[700px] w-[700px] bg-indigo-600/5 blur-[180px] rounded-full" />
56+
</div>
57+
58+
<div className="relative z-10 max-w-[900px] mx-auto px-6 pt-32 pb-44">
59+
{/* Navigation */}
60+
<motion.div
61+
initial={{ opacity: 0, x: -10 }}
62+
animate={{ opacity: 1, x: 0 }}
63+
className="mb-16"
64+
>
65+
<Link href="/blog" className="flex items-center gap-2 text-zinc-500 hover:text-white transition-colors group">
66+
<div className="h-8 w-8 flex items-center justify-center rounded-lg border border-white/5 bg-white/5 group-hover:border-purple-500/50 transition-all">
67+
<ArrowLeft size={16} />
68+
</div>
69+
<span className="text-[10px] font-bold uppercase tracking-[0.2em]">Back to Log</span>
70+
</Link>
71+
</motion.div>
72+
73+
{/* Article Header */}
74+
<motion.header
75+
variants={staggerContainer}
76+
initial="hidden"
77+
animate="show"
78+
className="mb-20"
79+
>
80+
<motion.div variants={fadeUp} className="flex items-center gap-4 mb-8 text-purple-400">
81+
<div className="px-3 py-1 rounded-lg border border-purple-500/20 bg-purple-500/5 text-[10px] font-bold uppercase tracking-widest">
82+
{post.category}
83+
</div>
84+
<div className="h-1 w-1 rounded-full bg-zinc-800" />
85+
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
86+
<Clock size={12} />
87+
{post.readTime} Read
88+
</div>
89+
</motion.div>
90+
91+
<motion.h1
92+
variants={fadeUp}
93+
className="text-4xl md:text-6xl lg:text-7xl font-black italic tracking-tighter leading-[0.9] text-white mb-10"
94+
>
95+
{post.title}
96+
</motion.h1>
97+
98+
<motion.div
99+
variants={fadeUp}
100+
className="flex flex-wrap items-center gap-8 pt-8 border-t border-white/5"
101+
>
102+
<div className="flex items-center gap-3">
103+
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-purple-500/20 to-indigo-500/20 border border-white/10 flex items-center justify-center text-white font-bold">
104+
{post.author[0]}
105+
</div>
106+
<div>
107+
<p className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 mb-0.5">Reported By</p>
108+
<p className="text-xs font-bold text-white">{post.author}</p>
109+
</div>
110+
</div>
111+
112+
<div className="flex items-center gap-3">
113+
<div className="h-10 w-10 rounded-full border border-white/5 bg-white/5 flex items-center justify-center text-zinc-500">
114+
<Calendar size={18} />
115+
</div>
116+
<div>
117+
<p className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 mb-0.5">Signal Date</p>
118+
<p className="text-xs font-bold text-white">{post.date}</p>
119+
</div>
120+
</div>
121+
</motion.div>
122+
</motion.header>
123+
124+
{/* Article Content */}
125+
<motion.article
126+
initial={{ opacity: 0, y: 20 }}
127+
animate={{ opacity: 1, y: 0 }}
128+
transition={{ delay: 0.4 }}
129+
className="prose prose-invert prose-purple max-w-none
130+
prose-h3:text-2xl prose-h3:font-black prose-h3:italic prose-h3:tracking-tighter prose-h3:mt-12
131+
prose-p:text-zinc-400 prose-p:text-lg prose-p:leading-relaxed prose-p:font-medium
132+
prose-li:text-zinc-400 prose-li:text-lg
133+
pb-32 border-b border-white/5"
134+
>
135+
{post.content.split('\n').map((line, i) => {
136+
if (line.startsWith('### ')) {
137+
return <h3 key={i}>{line.replace('### ', '')}</h3>;
138+
}
139+
if (line.startsWith('# ')) {
140+
return null; // Skip title as it's in the header
141+
}
142+
if (line.trim() === '') return <br key={i} />;
143+
return <p key={i}>{line}</p>;
144+
})}
145+
</motion.article>
146+
147+
{/* Related Entries */}
148+
<section className="pt-32">
149+
<div className="flex items-center justify-between mb-12">
150+
<h2 className="text-2xl font-black italic tracking-tighter text-white">RELATED SIGNALS</h2>
151+
<div className="h-px flex-grow mx-8 bg-gradient-to-r from-white/10 to-transparent" />
152+
</div>
153+
154+
<div className="grid gap-6 md:grid-cols-2">
155+
{relatedPosts.map(rel => (
156+
<Link key={rel.slug} href={`/blog/${rel.slug}`}>
157+
<GlassCard className="p-8 border-white/5 bg-white/[0.01] hover:bg-white/[0.03] transition-all group">
158+
<p className="text-[10px] font-bold uppercase tracking-widest text-purple-400 mb-4">{rel.category}</p>
159+
<h4 className="text-xl font-bold text-white mb-4 group-hover:text-purple-400 transition-colors">{rel.title}</h4>
160+
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-zinc-600 group-hover:text-white transition-colors">
161+
Access Entry <ArrowUpRight size={12} />
162+
</div>
163+
</GlassCard>
164+
</Link>
165+
))}
166+
</div>
167+
</section>
168+
</div>
169+
170+
<footer className="py-24 text-center border-t border-white/5 bg-[#010101]">
171+
<div className="flex justify-center mb-8">
172+
<div className="px-6 py-2 rounded-2xl border border-white/5 bg-white/5 text-[10px] font-bold uppercase tracking-[0.4em] text-zinc-700">
173+
End of Record // Protocol Secure
174+
</div>
175+
</div>
176+
</footer>
177+
</main>
178+
);
179+
}

apps/web/app/blog/layout.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Metadata } from 'next';
2+
3+
export const metadata: Metadata = {
4+
title: 'The Log | FairShare – Engineering & Social Finance',
5+
description: 'Deep dives into real-time sync, group dynamics, and the future of social finance.',
6+
};
7+
8+
export default function BlogLayout({
9+
children,
10+
}: {
11+
children: React.ReactNode;
12+
}) {
13+
return <>{children}</>;
14+
}

0 commit comments

Comments
 (0)