1- import React , { useState , useEffect } from 'react' ;
2- import { Link } from 'react-router-dom' ;
3- import { motion , AnimatePresence } from 'framer-motion' ;
4- import PostItem from '../components/PostItem' ;
5- import GenerativeArt from '../components/GenerativeArt' ;
6- import Seo from '../components/Seo' ;
7- import { fetchAllBlogPosts } from '../utils/dataUtils' ;
8- import {
9- ArrowLeft ,
10- XCircle ,
11- Clock ,
12- Tag ,
13- BookOpen ,
14- MagnifyingGlass ,
15- Hash ,
16- } from '@phosphor-icons/react' ;
17-
18- const FILTERS = [
19- { id : 'all' , label : 'All' } ,
20- { id : 'dev' , label : 'Dev' } ,
21- { id : 'feat' , label : 'Feat' } ,
22- { id : 'rant' , label : 'Rant' } ,
23- { id : 'series' , label : 'Series' } ,
24- { id : 'gist' , label : 'Gist' } ,
25- { id : 'd&d' , label : 'D&D' } ,
26- ] ;
1+ import React from 'react' ;
2+ import { useVisualSettings } from '../context/VisualSettingsContext' ;
3+ import BrutalistBlogPage from './blog-views/BrutalistBlogPage' ;
4+ import LuxeBlogPage from './luxe-views/LuxeBlogPage' ;
275
286const BlogPage = ( ) => {
29- const [ displayItems , setDisplayItems ] = useState ( [ ] ) ;
30- const [ loading , setLoading ] = useState ( true ) ;
31- const [ activeFilter , setActiveFilter ] = useState ( 'all' ) ;
32- const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
33- const [ activePost , setActivePost ] = useState ( null ) ;
34-
35- useEffect ( ( ) => {
36- const fetchPosts = async ( ) => {
37- try {
38- const { processedPosts } = await fetchAllBlogPosts ( ) ;
39-
40- const seriesMap = new Map ( ) ;
41- const individualPosts = [ ] ;
42-
43- processedPosts . forEach ( ( post ) => {
44- if ( post . series ) {
45- if ( ! seriesMap . has ( post . series . slug ) ) {
46- seriesMap . set ( post . series . slug , {
47- title : post . series . title ,
48- slug : post . series . slug ,
49- date : post . date ,
50- updated : post . updated ,
51- image : post . series . image ,
52- isSeries : true ,
53- posts : [ ] ,
54- tags : post . tags , // Inherit tags from first post for preview if series
55- category : post . category ,
56- description : post . series . description || post . description ,
57- } ) ;
58- }
59- seriesMap . get ( post . series . slug ) . posts . push ( post ) ;
60- } else {
61- individualPosts . push ( post ) ;
62- }
63- } ) ;
64-
65- const combinedItems = [
66- ...Array . from ( seriesMap . values ( ) ) ,
67- ...individualPosts ,
68- ] ;
69- combinedItems . sort (
70- ( a , b ) =>
71- new Date ( b . updated || b . date ) - new Date ( a . updated || a . date ) ,
72- ) ;
7+ const { fezcodexTheme } = useVisualSettings ( ) ;
738
74- setDisplayItems ( combinedItems ) ;
75- if ( combinedItems . length > 0 ) setActivePost ( combinedItems [ 0 ] ) ;
76- } catch ( error ) {
77- console . error ( 'Error fetching blog data:' , error ) ;
78- } finally {
79- setLoading ( false ) ;
80- }
81- } ;
82- fetchPosts ( ) ;
83- } , [ ] ) ;
84-
85- const filteredItems = displayItems . filter ( ( item ) => {
86- const matchesFilter = ( ) => {
87- if ( activeFilter === 'all' ) return true ;
88- if ( activeFilter === 'series' ) return item . isSeries ;
89- return item . category === activeFilter && ! item . isSeries ;
90- } ;
91- const matchesSearch = ( ) => {
92- if ( ! searchQuery ) return true ;
93- const q = searchQuery . toLowerCase ( ) ;
94- return (
95- item . title ?. toLowerCase ( ) . includes ( q ) ||
96- item . slug ?. toLowerCase ( ) . includes ( q )
97- ) ;
98- } ;
99- return matchesFilter ( ) && matchesSearch ( ) ;
100- } ) ;
101-
102- const isPlaceholder = ( post ) =>
103- ! post ?. image || post . image . includes ( 'placeholder' ) ;
104-
105- if ( loading ) {
106- return (
107- < div className = "flex h-screen items-center justify-center bg-[#050505] text-white" >
108- < div className = "flex flex-col items-center gap-4" >
109- < div className = "h-px w-24 bg-white/10 relative overflow-hidden" >
110- < div className = "absolute inset-0 bg-emerald-400 animate-progress origin-left" > </ div >
111- </ div >
112- < span className = "font-mono text-[10px] text-gray-500 uppercase tracking-[0.3em]" >
113- Accessing_Intel
114- </ span >
115- </ div >
116- </ div >
117- ) ;
9+ if ( fezcodexTheme === 'luxe' ) {
10+ return < LuxeBlogPage /> ;
11811 }
11912
120- return (
121- < div className = "flex min-h-screen bg-[#050505] text-white overflow-hidden relative selection:bg-emerald-500/30" >
122- < Seo
123- title = "Archive | Fezcodex Blog"
124- description = "A curated collection of thoughts, insights, and digital rants."
125- keywords = { [ 'Fezcodex' , 'blog' , 'developer' , 'archive' ] }
126- />
127- { /* Dynamic Background (Static or Active Post Blur) */ }
128- < div className = "absolute inset-0 opacity-20 pointer-events-none z-0" >
129- { activePost &&
130- ( isPlaceholder ( activePost ) ? (
131- < GenerativeArt
132- seed = { activePost . title }
133- className = "w-full h-full filter blur-3xl"
134- />
135- ) : (
136- < img
137- src = { activePost . image }
138- alt = "bg"
139- className = "w-full h-full object-cover filter blur-3xl"
140- />
141- ) ) }
142- </ div >
143-
144- { /* LEFT PANEL: The Index */ }
145- < 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" >
146- < header className = "mb-16" >
147- < Link
148- to = "/"
149- className = "mb-8 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
150- >
151- < ArrowLeft weight = "bold" />
152- < span > Home</ span >
153- </ Link >
154- < h1 className = "text-6xl md:text-8xl font-black tracking-tighter text-white mb-4 leading-none" >
155- INTEL
156- </ h1 >
157- < p className = "text-gray-400 font-mono text-[10px] uppercase tracking-[0.2em]" >
158- { '//' } DATA_LOGS & ARCHIVED_THOUGHTS
159- </ p >
160- </ header >
161-
162- { /* Filter Bar & Search */ }
163- < div className = "mb-12 border-b border-white/10 pb-8 space-y-6" >
164- < div className = "flex flex-wrap items-center gap-2" >
165- { FILTERS . map ( ( f ) => (
166- < button
167- key = { f . id }
168- onClick = { ( ) => setActiveFilter ( f . id ) }
169- className = { `rounded-sm px-3 py-1 text-[10px] font-bold uppercase tracking-widest transition-all ${
170- activeFilter === f . id
171- ? 'bg-emerald-500 text-black shadow-lg shadow-emerald-500/20'
172- : 'bg-white/5 text-gray-500 hover:text-white hover:bg-white/10 border border-white/5'
173- } `}
174- >
175- { f . label }
176- </ button >
177- ) ) }
178- </ div >
179-
180- < div className = "relative group w-full max-w-md" >
181- < div className = "flex items-center gap-2 bg-white/5 border border-white/10 rounded-sm px-3 py-1.5 focus-within:border-emerald-500/50 focus-within:bg-white/10 transition-all" >
182- < MagnifyingGlass size = { 14 } className = "text-gray-500 group-focus-within:text-emerald-400" />
183- < input
184- type = "text"
185- value = { searchQuery }
186- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
187- placeholder = "Search Blogposts..."
188- className = "bg-transparent border-none outline-none text-[10px] font-mono uppercase tracking-widest text-white placeholder-gray-600 w-full"
189- />
190- { searchQuery && (
191- < button
192- onClick = { ( ) => setSearchQuery ( '' ) }
193- className = "text-gray-500 hover:text-red-500 transition-colors"
194- >
195- < XCircle size = { 14 } weight = "fill" />
196- </ button >
197- ) }
198- </ div >
199- </ div >
200- </ div >
201-
202- < div className = "flex flex-col pb-32" >
203- { filteredItems . map ( ( item ) => (
204- < PostItem
205- key = { item . slug }
206- { ...item }
207- isActive = { activePost ?. slug === item . slug }
208- onHover = { setActivePost }
209- />
210- ) ) }
211- { filteredItems . length === 0 && (
212- < div className = "py-12 text-center border border-dashed border-white/10 rounded-lg" >
213- < p className = "font-mono text-xs text-gray-500 uppercase tracking-widest" >
214- No_Intel_Found
215- </ p >
216- </ div >
217- ) }
218- </ div >
219-
220- < div className = "mt-auto pt-20 border-t border-white/10 text-gray-600 font-mono text-[10px] uppercase tracking-widest" >
221- Total Stored Entries: { displayItems . length }
222- </ div >
223- </ div >
224-
225- { /* RIGHT PANEL: The Stage */ }
226- < 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" >
227- < AnimatePresence mode = "wait" >
228- { activePost && (
229- < motion . div
230- key = { activePost . slug }
231- initial = { { opacity : 0 } }
232- animate = { { opacity : 1 } }
233- exit = { { opacity : 0 } }
234- transition = { { duration : 0.4 } }
235- className = "absolute inset-0"
236- >
237- { /* Art */ }
238- < div className = "absolute inset-0 z-0" >
239- < GenerativeArt
240- seed = { activePost . title }
241- className = "w-full h-full opacity-60"
242- />
243- < div className = "absolute inset-0 bg-gradient-to-t from-black via-transparent to-black/40" />
244- </ div >
245-
246- { /* Details Overlay */ }
247- < div className = "absolute bottom-0 left-0 w-full p-16 z-10 flex flex-col gap-8" >
248- < div className = "flex items-center gap-6" >
249- < div className = "flex items-center gap-2 text-emerald-400 font-mono text-[10px] tracking-widest uppercase" >
250- < Clock size = { 16 } />
251- < span >
252- { new Date (
253- activePost . updated || activePost . date ,
254- ) . toLocaleDateString ( 'en-GB' ) }
255- </ span >
256- </ div >
257- < 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" >
258- < Tag size = { 14 } />
259- < span > { activePost . category || 'Post' } </ span >
260- </ div >
261- </ div >
262-
263- < div className = "flex flex-col gap-4" >
264- < h2 className = "text-6xl md:text-7xl font-instr-serif text-white uppercase tracking-normal leading-none" >
265- { activePost . title }
266- </ h2 >
267- < p className = "text-lg text-gray-300 font-syne leading-relaxed max-w-xl" >
268- { activePost . description ||
269- 'Archived content from the digital vault. Processed and cataloged for immediate access.' }
270- </ p >
271- </ div >
272-
273- { /* Tags Section */ }
274- { activePost . tags && activePost . tags . length > 0 && (
275- < div className = "flex flex-wrap gap-2 max-w-xl" >
276- { activePost . tags . map ( ( tag ) => (
277- < span
278- key = { tag }
279- className = "inline-flex items-center gap-1 text-[9px] font-mono font-bold uppercase tracking-wider text-gray-400 bg-black/40 px-2 py-1 rounded-sm border border-white/5"
280- >
281- < Hash size = { 10 } /> { tag }
282- </ span >
283- ) ) }
284- </ div >
285- ) }
286-
287- { activePost . isSeries && (
288- < div className = "mt-4 flex flex-col gap-4" >
289- < span className = "font-mono text-[10px] text-emerald-500 font-bold tracking-widest uppercase" >
290- { '//' } SERIES_MANIFEST
291- </ span >
292- < div className = "grid grid-cols-1 gap-2" >
293- { activePost . posts ?. slice ( 0 , 3 ) . map ( ( p , i ) => (
294- < div
295- key = { p . slug }
296- className = "flex items-center gap-3 text-gray-500 font-mono text-[10px] uppercase"
297- >
298- < span > { String ( i + 1 ) . padStart ( 2 , '0' ) } </ span >
299- < span className = "h-px w-4 bg-gray-800" />
300- < span className = "truncate" > { p . title } </ span >
301- </ div >
302- ) ) }
303- </ div >
304- </ div >
305- ) }
306-
307- < motion . div
308- initial = { { opacity : 0 , y : 10 } }
309- animate = { { opacity : 1 , y : 0 } }
310- transition = { { delay : 0.2 } }
311- className = "mt-8"
312- >
313- < Link
314- to = {
315- activePost . isSeries
316- ? `/blog/series/${ activePost . slug } `
317- : `/blog/${ activePost . slug } `
318- }
319- 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-2 py-2"
320- >
321- < span className = "text-sm font-syne font-normal uppercase tracking-[0.2em]" >
322- Read Post
323- </ span >
324- < BookOpen weight = "bold" size = { 20 } />
325- </ Link >
326- </ motion . div >
327- </ div >
328- </ motion . div >
329- ) }
330- </ AnimatePresence >
331- </ div >
332- </ div >
333- ) ;
13+ return < BrutalistBlogPage /> ;
33414} ;
33515
336- export default BlogPage ;
16+ export default BlogPage ;
0 commit comments