Skip to content

Commit 89004fb

Browse files
committed
feat: add dynamic projects system and dedicated projects page
1 parent 51f4b74 commit 89004fb

File tree

4 files changed

+149
-4
lines changed

4 files changed

+149
-4
lines changed

content/projects/bento-ui.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Bento UI Design System
3+
description: A Neobrutalist-inspired design system optimized for personal portfolios and experimental labs.
4+
link: https://github.com/Tinnci/bento-system
5+
tags: [React, CSS, Design]
6+
status: completed
7+
featured: true
8+
---
9+
The very design system powering this blog. Built with performance and accessibility in mind, while maintaining a strong visual identity.

content/projects/uefi-shell.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: UEFI Shell Prototype
3+
description: A lightweight UEFI shell environment written in Rust, focusing on system diagnostics and safe memory access.
4+
link: https://github.com/Tinnci/uefi-rs-shell
5+
tags: [Rust, UEFI, Low-level]
6+
status: active
7+
featured: true
8+
---
9+
Exploring the boundaries of what's possible in the pre-boot environment. This project provides a basic interactive shell with memory inspection tools.

src/App.tsx

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { motion, AnimatePresence } from 'framer-motion';
44
import { Github, Mail, ExternalLink, ArrowRight, ArrowLeft, Search, Moon, Sun, X } from 'lucide-react';
55
import Fuse from 'fuse.js';
66
import { getAllPosts, getPostBySlug, getFeaturedPost, getAllTags, getAllCategories, type Post } from './lib/posts';
7+
import { getAllProjects, getFeaturedProjects } from './lib/projects';
78

89
// Fix for framer-motion v12+ type issues
910
const MotionDiv = motion.div as React.FC<React.HTMLAttributes<HTMLDivElement> & {
@@ -134,10 +135,61 @@ const SearchBox = ({ posts }: { posts: Post[] }) => {
134135
);
135136
};
136137

138+
const ProjectsView = () => {
139+
const navigate = useNavigate();
140+
const projects = getAllProjects();
141+
142+
return (
143+
<MotionDiv
144+
initial={{ opacity: 0, y: 20 }}
145+
animate={{ opacity: 1, y: 0 }}
146+
exit={{ opacity: 0, y: -20 }}
147+
style={{ padding: '2rem 0' }}
148+
>
149+
<button
150+
onClick={() => navigate('/')}
151+
className="pill"
152+
style={{ marginBottom: '2rem', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.5rem' }}
153+
>
154+
<ArrowLeft size={18} /> BACK HOME
155+
</button>
156+
157+
<h1 style={{ fontSize: '3.5rem', fontWeight: 900, marginBottom: '2rem' }}>PROJECTS</h1>
158+
159+
<div className="bento-grid">
160+
{projects.map((project) => (
161+
<MotionDiv
162+
key={project.slug}
163+
whileHover={{ y: -5 }}
164+
className="bento-item span-2"
165+
>
166+
<div>
167+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
168+
<div className="pill">{project.status}</div>
169+
<a href={project.link} target="_blank" rel="noopener noreferrer" style={{ color: 'inherit' }}>
170+
<ExternalLink size={20} />
171+
</a>
172+
</div>
173+
<h2 style={{ marginTop: '1rem' }}>{project.title}</h2>
174+
<p>{project.description}</p>
175+
<div className="tag-cloud" style={{ marginTop: '1rem' }}>
176+
{project.tags.map(tag => (
177+
<span key={tag} className="pill" style={{ opacity: 0.7, fontSize: '0.7rem' }}>{tag}</span>
178+
))}
179+
</div>
180+
</div>
181+
</MotionDiv>
182+
))}
183+
</div>
184+
</MotionDiv>
185+
);
186+
};
187+
137188
const GridView = () => {
138189
const navigate = useNavigate();
139190
const posts = getAllPosts();
140191
const featuredPost = getFeaturedPost();
192+
const featuredProjects = getFeaturedProjects();
141193
const tags = getAllTags();
142194
const categories = getAllCategories();
143195
const [selectedTag, setSelectedTag] = useState<string | null>(null);
@@ -309,10 +361,27 @@ const GridView = () => {
309361
</MotionDiv>
310362

311363
{/* Projects */}
312-
<MotionDiv variants={item} className="bento-item bg-yellow">
313-
<h2 style={{ fontSize: '1.2rem' }}>Projects</h2>
314-
<p>UEFI Shell prototype in Rust.</p>
315-
<div style={{ marginTop: 'auto' }}><ExternalLink size={20} /></div>
364+
<MotionDiv
365+
variants={item}
366+
className="bento-item bg-yellow"
367+
onClick={() => navigate('/projects')}
368+
style={{ cursor: 'pointer' }}
369+
>
370+
<div>
371+
<h2 style={{ fontSize: '1.2rem' }}>Projects</h2>
372+
{featuredProjects.length > 0 ? (
373+
<div>
374+
<p style={{ fontWeight: 800 }}>{featuredProjects[0].title}</p>
375+
<p style={{ fontSize: '0.9rem' }}>{featuredProjects[0].description}</p>
376+
</div>
377+
) : (
378+
<p>Check out my latest experimental works.</p>
379+
)}
380+
</div>
381+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '1rem' }}>
382+
<span style={{ fontWeight: 900, fontSize: '0.8rem' }}>VIEW ALL</span>
383+
<ArrowRight size={20} />
384+
</div>
316385
</MotionDiv>
317386
</MotionDiv>
318387
</MotionDiv>
@@ -392,6 +461,7 @@ const App = () => {
392461
<Routes>
393462
<Route path="/" element={<GridView />} />
394463
<Route path="/post/:slug" element={<PostView />} />
464+
<Route path="/projects" element={<ProjectsView />} />
395465
</Routes>
396466
</AnimatePresence>
397467

src/lib/projects.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { marked } from 'marked';
2+
import * as yaml from 'js-yaml';
3+
4+
export interface Project {
5+
title: string;
6+
description: string;
7+
link: string;
8+
tags: string[];
9+
status: string;
10+
featured: boolean;
11+
content: string;
12+
slug: string;
13+
}
14+
15+
function parseFrontmatter(raw: string): { data: Record<string, unknown>; content: string } {
16+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
17+
if (!match) return { data: {}, content: raw };
18+
19+
try {
20+
const data = yaml.load(match[1]) as Record<string, unknown>;
21+
return { data, content: match[2] };
22+
} catch {
23+
return { data: {}, content: raw };
24+
}
25+
}
26+
27+
const projectModules = import.meta.glob('/content/projects/*.md', {
28+
query: '?raw',
29+
import: 'default',
30+
eager: true
31+
});
32+
33+
export function getAllProjects(): Project[] {
34+
const projects: Project[] = [];
35+
36+
for (const path in projectModules) {
37+
const raw = projectModules[path] as string;
38+
const { data, content } = parseFrontmatter(raw);
39+
40+
projects.push({
41+
title: (data.title as string) || 'Untitled Project',
42+
description: (data.description as string) || '',
43+
link: (data.link as string) || '#',
44+
tags: (data.tags as string[]) || [],
45+
status: (data.status as string) || 'unknown',
46+
featured: (data.featured as boolean) || false,
47+
slug: path.split('/').pop()?.replace('.md', '') || '',
48+
content: marked(content) as string,
49+
});
50+
}
51+
52+
return projects;
53+
}
54+
55+
export function getFeaturedProjects(): Project[] {
56+
return getAllProjects().filter(p => p.featured);
57+
}

0 commit comments

Comments
 (0)