Skip to content

Commit 5b20345

Browse files
committed
feat: new series pages
1 parent 7b377a7 commit 5b20345

File tree

3 files changed

+338
-193
lines changed

3 files changed

+338
-193
lines changed

src/pages/SeriesPage.jsx

Lines changed: 8 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,201 +1,16 @@
1-
import React, { useState, useEffect } from 'react';
2-
import { useParams, Link } from 'react-router-dom';
3-
import { motion, AnimatePresence } from 'framer-motion';
4-
import PostItem from '../components/PostItem';
5-
import { ArrowLeft, Clock, Tag, BookOpen } from '@phosphor-icons/react';
6-
import Seo from '../components/Seo';
7-
import { fetchAllBlogPosts } from '../utils/dataUtils';
8-
import GenerativeArt from '../components/GenerativeArt';
1+
import React from 'react';
2+
import { useVisualSettings } from '../context/VisualSettingsContext';
3+
import BrutalistSeriesPage from './brutalist-views/BrutalistSeriesPage';
4+
import LuxeSeriesPage from './luxe-views/LuxeSeriesPage';
95

106
const SeriesPage = () => {
11-
const { seriesSlug } = useParams();
12-
const [seriesPosts, setSeriesPosts] = useState([]);
13-
const [seriesTitle, setSeriesTitle] = useState('');
14-
const [loading, setLoading] = useState(true);
15-
const [activePost, setActivePost] = useState(null);
7+
const { fezcodexTheme } = useVisualSettings();
168

17-
useEffect(() => {
18-
const fetchSeriesPosts = async () => {
19-
try {
20-
const { processedPosts } = await fetchAllBlogPosts();
21-
22-
const filteredPosts = processedPosts
23-
.filter((post) => post.series && post.series.slug === seriesSlug)
24-
.sort((a, b) => {
25-
const dateA = new Date(a.updated || a.date);
26-
const dateB = new Date(b.updated || b.date);
27-
return dateB - dateA;
28-
});
29-
30-
if (filteredPosts.length > 0) {
31-
setSeriesPosts(filteredPosts);
32-
setSeriesTitle(filteredPosts[0].series.title);
33-
setActivePost(filteredPosts[0]);
34-
} else {
35-
setSeriesPosts([]);
36-
setSeriesTitle('Series Not Found');
37-
}
38-
} catch (error) {
39-
console.error('Error fetching series posts:', error);
40-
setSeriesPosts([]);
41-
setSeriesTitle('Error');
42-
} finally {
43-
setLoading(false);
44-
}
45-
};
46-
47-
fetchSeriesPosts();
48-
}, [seriesSlug]);
49-
50-
const isPlaceholder = (post) =>
51-
!post?.image || post.image.includes('placeholder');
52-
53-
if (loading) {
54-
return (
55-
<div className="flex h-screen items-center justify-center bg-[#050505] text-white">
56-
<div className="flex flex-col items-center gap-4">
57-
<div className="h-px w-24 bg-white/10 relative overflow-hidden">
58-
<div className="absolute inset-0 bg-emerald-400 animate-progress origin-left"></div>
59-
</div>
60-
<span className="font-mono text-[10px] text-gray-500 uppercase tracking-[0.3em]">
61-
Accessing_Series_Data
62-
</span>
63-
</div>
64-
</div>
65-
);
9+
if (fezcodexTheme === 'luxe') {
10+
return <LuxeSeriesPage />;
6611
}
6712

68-
return (
69-
<div className="flex min-h-screen bg-[#050505] text-white overflow-hidden relative selection:bg-emerald-500/30">
70-
<Seo
71-
title={`${seriesTitle} | Fezcodex Series`}
72-
description={`Explore the sequential entries in the "${seriesTitle}" series.`}
73-
keywords={['Fezcodex', 'blog', 'series', seriesTitle]}
74-
/>
75-
{/* Dynamic Background (Static or Active Post Blur) */}
76-
<div className="absolute inset-0 opacity-20 pointer-events-none z-0">
77-
{activePost &&
78-
(isPlaceholder(activePost) ? (
79-
<GenerativeArt
80-
seed={activePost.title}
81-
className="w-full h-full filter blur-3xl"
82-
/>
83-
) : (
84-
<img
85-
src={activePost.image}
86-
alt="bg"
87-
className="w-full h-full object-cover filter blur-3xl"
88-
/>
89-
))}
90-
</div>
91-
92-
{/* LEFT PANEL: The Index */}
93-
<div className="w-full 4xl:pr-[50vw] relative z-10 flex flex-col min-h-screen py-24 px-6 md:pl-20 overflow-y-auto overflow-x-hidden no-scrollbar transition-all duration-300">
94-
<header className="mb-16">
95-
<Link
96-
to="/blog"
97-
className="mb-8 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
98-
>
99-
<ArrowLeft weight="bold" />
100-
<span>Archive</span>
101-
</Link>
102-
<h1 className="text-6xl md:text-8xl font-black tracking-tighter text-white mb-4 leading-none uppercase">
103-
SERIES
104-
</h1>
105-
<p className="text-gray-400 font-mono text-[10px] uppercase tracking-[0.2em]">
106-
{'//'} SEQUENTIAL_ENTRIES: {seriesTitle.toUpperCase()}
107-
</p>
108-
</header>
109-
110-
<div className="flex flex-col pb-32">
111-
{seriesPosts.map((post) => (
112-
<PostItem
113-
key={post.slug}
114-
{...post}
115-
slug={`series/${seriesSlug}/${post.slug}`}
116-
isActive={activePost?.slug === post.slug}
117-
onHover={setActivePost}
118-
isSeries={false}
119-
/>
120-
))}
121-
</div>
122-
123-
<div className="mt-auto pt-20 border-t border-white/10 text-gray-600 font-mono text-[10px] uppercase tracking-widest">
124-
Stored_Episodes: {seriesPosts.length}
125-
</div>
126-
</div>
127-
128-
{/* RIGHT PANEL: The Stage */}
129-
<div className="hidden 4xl:block fixed right-0 top-0 h-screen w-1/2 bg-neutral-900 overflow-hidden border-l border-white/10 z-20">
130-
<AnimatePresence mode="wait">
131-
{activePost && (
132-
<motion.div
133-
key={activePost.slug}
134-
initial={{ opacity: 0 }}
135-
animate={{ opacity: 1 }}
136-
exit={{ opacity: 0 }}
137-
transition={{ duration: 0.4 }}
138-
className="absolute inset-0"
139-
>
140-
{/* Art */}
141-
<div className="absolute inset-0 z-0">
142-
<GenerativeArt
143-
seed={activePost.title}
144-
className="w-full h-full opacity-60"
145-
/>
146-
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-black/40" />
147-
</div>
148-
149-
{/* Details Overlay */}
150-
<div className="absolute bottom-0 left-0 w-full p-16 z-10 flex flex-col gap-8">
151-
<div className="flex items-center gap-6">
152-
<div className="flex items-center gap-2 text-emerald-400 font-mono text-[10px] tracking-widest uppercase">
153-
<Clock size={16} />
154-
<span>
155-
{new Date(
156-
activePost.updated || activePost.date,
157-
).toLocaleDateString('en-GB')}
158-
</span>
159-
</div>
160-
<div className="flex items-center gap-2 text-white font-mono text-[10px] tracking-widest uppercase bg-white/10 px-2 py-1 border border-white/10 rounded-sm">
161-
<Tag size={14} />
162-
<span>{activePost.category || 'Episode'}</span>
163-
</div>
164-
</div>
165-
166-
<div className="flex flex-col gap-4">
167-
<h2 className="text-4xl font-black text-white uppercase tracking-tighter leading-none">
168-
{activePost.title}
169-
</h2>
170-
<p className="text-lg text-gray-300 font-light leading-relaxed max-w-xl">
171-
{activePost.description ||
172-
'Part of a sequential data stream. Analysis and implementation logs curated for technical review.'}
173-
</p>
174-
</div>
175-
176-
<motion.div
177-
initial={{ opacity: 0, y: 10 }}
178-
animate={{ opacity: 1, y: 0 }}
179-
transition={{ delay: 0.2 }}
180-
className="mt-8"
181-
>
182-
<Link
183-
to={`/blog/series/${seriesSlug}/${activePost.slug}`}
184-
className="inline-flex items-center gap-4 text-white border-b-2 border-emerald-500 pb-2 hover:bg-emerald-500 hover:text-black transition-all px-1"
185-
>
186-
<span className="text-sm font-black uppercase tracking-[0.2em]">
187-
Access_Episode
188-
</span>
189-
<BookOpen weight="bold" size={20} />
190-
</Link>
191-
</motion.div>
192-
</div>
193-
</motion.div>
194-
)}
195-
</AnimatePresence>
196-
</div>
197-
</div>
198-
);
13+
return <BrutalistSeriesPage />;
19914
};
20015

20116
export default SeriesPage;

0 commit comments

Comments
 (0)