Skip to content

Commit 9d93af4

Browse files
committed
feat: logs and details pages
1 parent 3dc223e commit 9d93af4

File tree

5 files changed

+366
-728
lines changed

5 files changed

+366
-728
lines changed

src/pages/LogDetailPage.jsx

Lines changed: 7 additions & 372 deletions
Original file line numberDiff line numberDiff line change
@@ -1,381 +1,16 @@
1-
import React, { useState, useEffect, useRef } from 'react';
2-
import { useParams, Link } from 'react-router-dom';
3-
import {
4-
ArrowLeft,
5-
CalendarBlank,
6-
Tag,
7-
Star,
8-
ArrowUpRight,
9-
Clock,
10-
User,
11-
Hash,
12-
} from '@phosphor-icons/react';
13-
import GenerativeArt from '../components/GenerativeArt';
14-
import Seo from '../components/Seo';
15-
import piml from 'piml';
16-
import MarkdownLink from '../components/MarkdownLink';
17-
import colors from '../config/colors';
18-
import MarkdownContent from '../components/MarkdownContent';
19-
import CustomDropdown from '../components/CustomDropdown';
1+
import React from 'react';
202
import { useVisualSettings } from '../context/VisualSettingsContext';
3+
import BrutalistLogDetailPage from './brutalist-views/BrutalistLogDetailPage';
4+
import LuxeLogDetailPage from './luxe-views/LuxeLogDetailPage';
215

226
const LogDetailPage = () => {
23-
const { category, slugId } = useParams();
24-
const [log, setLog] = useState(null);
25-
const [loading, setLoading] = useState(true);
26-
const {headerFont, setHeaderFont, bodyFont, setBodyFont, availableFonts } = useVisualSettings();
27-
const contentRef = useRef(null);
7+
const { fezcodexTheme } = useVisualSettings();
288

29-
useEffect(() => {
30-
const fetchLog = async () => {
31-
setLoading(true);
32-
try {
33-
const response = await fetch(`/logs/${category}/${category}.piml`);
34-
if (!response.ok) {
35-
setLog({ attributes: { title: 'Category not found' }, body: '' });
36-
setLoading(false);
37-
return;
38-
}
39-
const pimlText = await response.text();
40-
const data = piml.parse(pimlText);
41-
const categoryLogs = data.logs || [];
42-
const logMetadata = categoryLogs.find((item) => item.slug === slugId);
43-
44-
if (logMetadata) {
45-
try {
46-
const logContentResponse = await fetch(
47-
`/logs/${category}/${slugId}.txt`,
48-
);
49-
if (logContentResponse.ok) {
50-
const logBody = await logContentResponse.text();
51-
setLog({ attributes: logMetadata, body: logBody });
52-
} else {
53-
// If text file is missing, just use metadata
54-
setLog({ attributes: logMetadata, body: logMetadata.description || '' });
55-
}
56-
} catch (e) {
57-
setLog({ attributes: logMetadata, body: logMetadata.description || '' });
58-
}
59-
} else {
60-
setLog({ attributes: { title: 'Log not found' }, body: '' });
61-
}
62-
} catch (error) {
63-
setLog({ attributes: { title: 'Error loading log' }, body: '' });
64-
}
65-
setLoading(false);
66-
};
67-
fetchLog();
68-
}, [category, slugId]);
69-
70-
if (loading) {
71-
return (
72-
<div className="flex min-h-screen items-center justify-center bg-[#050505] text-white">
73-
<div className="flex flex-col items-center gap-4">
74-
<div className="h-px w-24 bg-white/10 relative overflow-hidden">
75-
<div className="absolute inset-0 bg-emerald-400 animate-progress origin-left"></div>
76-
</div>
77-
<span className="font-mono text-[10px] text-gray-500 uppercase tracking-widest">
78-
Accessing_Log
79-
</span>
80-
</div>
81-
</div>
82-
);
9+
if (fezcodexTheme === 'luxe') {
10+
return <LuxeLogDetailPage />;
8311
}
8412

85-
if (!log || !log.attributes.title) {
86-
return (
87-
<div className="flex min-h-screen items-center justify-center bg-[#050505] text-white font-mono uppercase">
88-
404 // Log Not Found
89-
</div>
90-
);
91-
}
92-
93-
const { attributes, body } = log;
94-
const accentColor = colors[category.toLowerCase()] || colors.primary[400];
95-
96-
const MetadataRow = ({ label, value, icon: Icon }) => {
97-
if (!value) return null;
98-
return (
99-
<div className="flex flex-col gap-1 py-4 border-b border-white/5">
100-
<span className="flex items-center gap-2 text-[10px] font-mono text-gray-500 uppercase tracking-widest">
101-
{Icon && (
102-
<Icon size={12} weight="bold" className="text-emerald-500" />
103-
)}
104-
{label}
105-
</span>
106-
<span className="text-sm text-gray-200">{value}</span>
107-
</div>
108-
);
109-
};
110-
111-
const renderStars = (rating) => {
112-
if (rating === undefined || rating === null) return null;
113-
return (
114-
<div className="flex flex-col gap-2 py-4 border-b border-white/5">
115-
<span className="text-[10px] font-mono text-gray-500 uppercase tracking-widest flex items-center gap-2">
116-
<Star size={12} weight="bold" className="text-yellow-500" />
117-
Rating
118-
</span>
119-
<div className="flex gap-1">
120-
{[...Array(5)].map((_, i) => (
121-
<Star
122-
key={i}
123-
size={16}
124-
weight="fill"
125-
className={i < rating ? 'text-yellow-500' : 'text-white/10'}
126-
/>
127-
))}
128-
<span className="ml-2 font-mono text-xs text-gray-400">
129-
({rating}/5)
130-
</span>
131-
</div>
132-
</div>
133-
);
134-
};
135-
136-
const fontMap = {
137-
'font-sans': "'Space Mono', monospace",
138-
'font-mono': "'JetBrains Mono', monospace",
139-
'font-inter': "'Inter', sans-serif",
140-
'font-arvo': "'Arvo', serif",
141-
'font-playfairDisplay': "'Playfair Display', serif",
142-
'font-syne': "'Syne', sans-serif",
143-
'font-outfit': "'Outfit', sans-serif",
144-
'font-ibm-plex-mono': "'IBM Plex Mono', monospace",
145-
'font-instr-serif': "'Instrument Serif', serif",
146-
'font-nunito': "'Nunito', sans-serif",
147-
};
148-
149-
return (
150-
<div className={`min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30`}>
151-
<Seo
152-
title={log ? `${log.attributes.title} | Fezcodex` : null}
153-
description={log ? log.body.substring(0, 150) : null}
154-
image={log?.attributes?.image}
155-
/>
156-
<style>
157-
{`
158-
.custom-prose h1, .custom-prose h2, .custom-prose h3,
159-
.custom-prose h4, .custom-prose h5, .custom-prose h6 {
160-
font-family: ${fontMap[headerFont] || fontMap['font-sans']} !important;
161-
}
162-
`}
163-
</style>
164-
{/* HERO SECTION */}
165-
<section className="relative h-[60vh] md:h-[70vh] flex flex-col justify-end overflow-hidden border-b border-white/10">
166-
<div className="absolute inset-0 z-0">
167-
<GenerativeArt
168-
seed={attributes.title}
169-
className="w-full h-full opacity-60"
170-
/>
171-
<div className="absolute inset-0 bg-gradient-to-t from-[#050505] via-[#050505]/60 to-transparent" />
172-
<div className="absolute inset-0 bg-noise opacity-[0.05] mix-blend-overlay" />
173-
</div>
174-
175-
<div className="relative z-10 mx-auto max-w-7xl w-full px-6 pb-20">
176-
<Link
177-
to="/logs"
178-
className="group mb-12 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
179-
>
180-
<ArrowLeft
181-
weight="bold"
182-
className="group-hover:-translate-x-1 transition-transform"
183-
/>
184-
<span>Archive</span>
185-
</Link>
186-
187-
<div className="flex flex-wrap items-center gap-4 mb-8">
188-
<span
189-
className="px-3 py-1 text-[10px] font-mono font-bold uppercase tracking-widest border rounded-full bg-opacity-20 backdrop-blur-md"
190-
style={{
191-
color: accentColor,
192-
borderColor: `${accentColor}44`,
193-
backgroundColor: `${accentColor}10`,
194-
}}
195-
>
196-
{category}
197-
</span>
198-
{attributes.updated && (
199-
<span className="px-3 py-1 text-[10px] font-mono font-bold uppercase tracking-widest text-rose-400 border border-rose-400/20 rounded-full bg-rose-400/5">
200-
Updated
201-
</span>
202-
)}
203-
</div>
204-
205-
<h1 className="text-5xl md:text-8xl font-black uppercase tracking-tighter leading-none mb-8 max-w-4xl">
206-
{attributes.title}
207-
</h1>
208-
209-
<div className="flex flex-wrap items-center gap-8 text-gray-400 font-mono text-[10px] uppercase tracking-[0.2em]">
210-
<div className="flex items-center gap-2">
211-
<CalendarBlank size={16} className="text-emerald-500" />
212-
<span>Published: {attributes.date}</span>
213-
</div>
214-
{attributes.slug && (
215-
<div className="flex items-center gap-2">
216-
<Hash size={16} className="text-emerald-500" />
217-
<span>ID: {attributes.slug}</span>
218-
</div>
219-
)}
220-
</div>
221-
</div>
222-
</section>
223-
224-
{/* CONTENT SECTION */}
225-
<div className="mx-auto max-w-7xl px-6 py-20 lg:py-32">
226-
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 md:gap-24">
227-
{/* Main Reading Column */}
228-
<main className="lg:col-span-8">
229-
<article
230-
ref={contentRef}
231-
className={`prose prose-xl prose-dark prose-emerald max-w-none custom-prose ${bodyFont}
232-
prose-headings:uppercase prose-headings:tracking-tighter prose-headings:font-black
233-
prose-p:text-gray-300 prose-p:leading-relaxed
234-
prose-a:text-emerald-400 prose-a:no-underline hover:prose-a:underline
235-
prose-blockquote:border-l-4 prose-blockquote:border-emerald-500 prose-blockquote:bg-white/5 prose-blockquote:py-1 prose-blockquote:px-6
236-
prose-strong:text-white prose-code:text-emerald-300 prose-code:bg-emerald-500/10 prose-code:px-1 prose-code:rounded-sm`}
237-
>
238-
<MarkdownContent
239-
content={body}
240-
components={{
241-
a: (props) => (
242-
<MarkdownLink
243-
{...props}
244-
className="text-emerald-400 hover:text-white transition-colors underline decoration-emerald-500/30 underline-offset-4"
245-
/>
246-
),
247-
}}
248-
/>
249-
</article>
250-
251-
{/* Tags Section */}
252-
{attributes.tags && attributes.tags.length > 0 && (
253-
<div className="mt-20 pt-12 border-t border-white/10">
254-
<div className="flex flex-wrap gap-2">
255-
{attributes.tags.map((tag) => (
256-
<span
257-
key={tag}
258-
className="px-3 py-1 bg-white/5 border border-white/10 rounded-full text-[10px] font-mono text-gray-400 uppercase tracking-widest hover:text-white hover:border-white transition-colors cursor-default"
259-
>
260-
#{tag}
261-
</span>
262-
))}
263-
</div>
264-
</div>
265-
)}
266-
</main>
267-
268-
{/* Sidebar Metadata */}
269-
<aside className="lg:col-span-4">
270-
<div className="sticky top-24 space-y-12">
271-
<div className="border border-white/10 p-8 bg-white/[0.02] backdrop-blur-sm rounded-sm relative overflow-hidden group">
272-
{/* Background accent line */}
273-
<div className="absolute top-0 left-0 w-1 h-0 group-hover:h-full bg-emerald-500 transition-all duration-500" />
274-
<h3 className="font-mono text-[10px] font-bold text-emerald-500 uppercase tracking-widest mb-8 flex items-center gap-2">
275-
<Tag weight="fill" />
276-
Manifest Data
277-
</h3>
278-
<div className="flex flex-col">
279-
<MetadataRow
280-
label="Category"
281-
value={attributes.category}
282-
icon={Tag}
283-
/>
284-
<MetadataRow
285-
label="Creator/Author"
286-
value={
287-
attributes.author ||
288-
attributes.director ||
289-
attributes.artist ||
290-
attributes.creator ||
291-
attributes.by
292-
}
293-
icon={User}
294-
/>
295-
<MetadataRow
296-
label="Platform/Source"
297-
value={attributes.platform || attributes.source}
298-
icon={ArrowUpRight}
299-
/>
300-
<MetadataRow
301-
label="Release Year"
302-
value={attributes.year}
303-
icon={Clock}
304-
/>
305-
<MetadataRow
306-
label="Last Updated"
307-
value={attributes.updated}
308-
icon={CalendarBlank}
309-
/>
310-
{renderStars(attributes.rating)}
311-
</div>
312-
{attributes.link && (
313-
<a
314-
href={attributes.link}
315-
target="_blank"
316-
rel="noopener noreferrer"
317-
className="mt-8 flex items-center justify-start gap-3 group/link border border-emerald-500/20 hover:border-emerald-500 p-4 transition-all"
318-
>
319-
<span className="text-xs font-mono uppercase tracking-widest text-emerald-400 group-hover/link:text-emerald-500 transition-colors">
320-
Access External Source
321-
</span>
322-
<ArrowUpRight
323-
className="text-emerald-400 transition-transform group-hover/link:translate-x-1 group-hover/link:-translate-y-1"
324-
size={20}
325-
/>
326-
</a>
327-
)}{' '}
328-
</div>
329-
330-
{/* Typography Lab */}
331-
<div className="border border-white/10 p-8 bg-white/[0.02] backdrop-blur-sm rounded-sm relative overflow-hidden group">
332-
<div className="absolute top-0 left-0 w-1 h-0 group-hover:h-full bg-emerald-500 transition-all duration-500" />
333-
<h3 className="font-mono text-[10px] font-bold text-emerald-500 uppercase tracking-widest mb-8 flex items-center gap-2">
334-
<Tag weight="fill" />
335-
Typography Lab
336-
</h3>
337-
<div className="space-y-6">
338-
<div className="space-y-2">
339-
<span className="text-[9px] font-mono text-gray-500 uppercase tracking-widest block ml-1">Header Font</span>
340-
<CustomDropdown
341-
label="Select Font"
342-
options={availableFonts.map((f) => ({
343-
label: f.name,
344-
value: f.id,
345-
}))}
346-
value={headerFont}
347-
onChange={setHeaderFont}
348-
variant="brutalist"
349-
fullWidth={true}
350-
/>
351-
</div>
352-
<div className="space-y-2">
353-
<span className="text-[9px] font-mono text-gray-500 uppercase tracking-widest block ml-1">Body Font</span>
354-
<CustomDropdown
355-
label="Select Font"
356-
options={availableFonts.map((f) => ({
357-
label: f.name,
358-
value: f.id,
359-
}))}
360-
value={bodyFont}
361-
onChange={setBodyFont}
362-
variant="brutalist"
363-
fullWidth={true}
364-
/>
365-
</div>
366-
</div>
367-
<div className="mt-8 pt-4 border-t border-white/5 text-[9px] font-mono text-gray-600 uppercase tracking-widest flex justify-between">
368-
<span>You can also change them in Settings page</span>
369-
</div>
370-
</div>
371-
372-
{/* Optional: Read More / Next Prev could go here */}
373-
</div>
374-
</aside>
375-
</div>
376-
</div>
377-
</div>
378-
);
13+
return <BrutalistLogDetailPage />;
37914
};
38015

38116
export default LogDetailPage;

0 commit comments

Comments
 (0)