diff --git a/src/components/ProjectsSection.tsx b/src/components/ProjectsSection.tsx index 68d4d24..6bc6069 100644 --- a/src/components/ProjectsSection.tsx +++ b/src/components/ProjectsSection.tsx @@ -1,42 +1,116 @@ -import { ExternalLink, Layout } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { ChevronRight, ExternalLink, GitFork, Layout, Star } from 'lucide-react'; import { PROJECTS } from '../data/contentData'; export function ProjectsSection() { + const [repoStats, setRepoStats] = useState>({}); + + useEffect(() => { + let isMounted = true; + + const fetchStats = async () => { + const entries = PROJECTS.filter(project => project.github).map(async project => { + try { + const url = new URL(project.github!); + const [owner, repo] = url.pathname.replace(/^\//, '').split('/'); + + if (!owner || !repo) { + return null; + } + + const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`); + if (!response.ok) { + return null; + } + const data = await response.json(); + return { + id: project.id, + stars: Number(data.stargazers_count ?? 0), + forks: Number(data.forks_count ?? 0), + }; + } catch { + return null; + } + }); + + const results = await Promise.all(entries); + if (!isMounted) return; + + const nextStats = results.reduce>((acc, item) => { + if (item) { + acc[item.id] = { stars: item.stars, forks: item.forks }; + } + return acc; + }, {}); + + if (Object.keys(nextStats).length > 0) { + setRepoStats(nextStats); + } + }; + + fetchStats(); + + return () => { + isMounted = false; + }; + }, []); + return (
-

+

recent_projects/

{PROJECTS.map((project, idx) => (
-
+ {project.ribbon && ( +
+
+ {project.ribbon} +
+
+ )} +
-
- {project.icon} +
+ + {project.icon} + + + {project.github && ( +
+ + + {repoStats[project.id]?.stars ?? project.stars ?? '—'} + + + + {repoStats[project.id]?.forks ?? project.forks ?? '—'} + +
+ )}
-

+

{project.title}

- {project.category} + {project.category}
- -

+

{project.desc}

{project.tech.map((t) => ( - + {t} ))} diff --git a/src/data/content.tsx b/src/data/content.tsx index cf58f8c..5e49335 100644 --- a/src/data/content.tsx +++ b/src/data/content.tsx @@ -1,8 +1,9 @@ -import { Cpu, Gamepad, Layout } from 'lucide-react'; +import { Chromium, Cpu, Gamepad, Layout } from 'lucide-react'; export const PROJECTS = [ { id: 'Krohnkite', + github: 'https://github.com/sortedcord/krohnkite-i3', title: 'Krohnkite i3', category: 'Window Management', tech: ['Typescript', 'Kwin API', 'QML'], @@ -11,14 +12,25 @@ export const PROJECTS = [ }, { id: 'leenim', + github: 'https://github.com/sortedcord/leenim', title: 'Leenim', category: 'Desktop Application Development', tech: ['React', 'Rust', 'Tauri', 'Python'], desc: 'A Cross platform Video Editor style application for creating mathematical illustrations with Manim.', icon: , }, + { + id: 'gippity-pruner', + github: 'https://github.com/sortedcord/gippity-pruner', + title: 'Gippity Pruner', + category: 'Browser Extension for Chromium & Gecko', + tech: ['JavaScript'], + desc: 'Improve ChatGPT performance in long conversations, without losing your context!', + icon: , + }, { id: 'omnia', + github: 'https://github.com/sortedcord/omnia', title: 'Omnia', category: 'AI-Native Computing Systems', tech: ['Typescript'], diff --git a/src/data/contentData.tsx b/src/data/contentData.tsx index 7280c0d..22bbf84 100644 --- a/src/data/contentData.tsx +++ b/src/data/contentData.tsx @@ -1,11 +1,15 @@ import type { ReactElement } from 'react'; -import { Cpu, Gamepad, Layout } from 'lucide-react'; +import { Cpu, Gamepad, Layout, Chromium } from 'lucide-react'; export type Project = { id: string; + github?: string; + stars?: number; + forks?: number; title: string; category: string; tech: string[]; + ribbon: string | null; desc: string; icon: ReactElement; }; @@ -18,26 +22,42 @@ export type LabNote = { export const PROJECTS: Project[] = [ { id: 'Krohnkite', + github: 'https://github.com/sortedcord/krohnkite-i3', title: 'Krohnkite i3', category: 'Window Management', tech: ['Typescript', 'Kwin API', 'QML'], + ribbon: null, desc: 'A modified stripped down version of Krohnkite that implements i3-style tiling in KDE Plasma (Wayland).', icon: , }, { id: 'leenim', + github: 'https://github.com/sortedcord/leenim', title: 'Leenim', category: 'Desktop Application Development', tech: ['React', 'Rust', 'Tauri', 'Python'], desc: 'A Cross platform Video Editor style application for creating mathematical illustrations with Manim.', + ribbon: null, icon: , }, + { + id: 'gippity-pruner', + github: 'https://github.com/sortedcord/gippity-pruner', + title: 'Gippity Pruner', + category: 'Browser Extension', + tech: ['JavaScript'], + desc: 'Cross browser extension that introduces lazy loading in ChatGPTs UI, improving performance for long coversations without context or memory loss.', + ribbon: "+1k Users!", + icon: , + }, { id: 'omnia', + github: 'https://github.com/sortedcord/omnia', title: 'Omnia', category: 'AI-Native Computing Systems', tech: ['Typescript'], desc: 'A game engine specification that defines an AI driven, intent-based architecture for building determinist-first interactive worlds.', + ribbon: null, icon: , }, ]; diff --git a/src/theme.css b/src/theme.css index 3826ba5..24f3c5e 100644 --- a/src/theme.css +++ b/src/theme.css @@ -114,6 +114,30 @@ } } +@keyframes project-shake { + + 0%, + 100% { + transform: rotate(-3deg) translateY(0); + } + + 20% { + transform: rotate(1deg) translateY(-1px); + } + + 40% { + transform: rotate(-1deg) translateY(1px); + } + + 60% { + transform: rotate(1.5deg) translateY(-2px); + } + + 80% { + transform: rotate(-1.5deg) translateY(1px); + } +} + @media (prefers-reduced-motion: reduce) { .dot-grid::before { animation: none;