feat: Add Github Integration for project data
- Added ribbons - The icons now show github stars and forks on hover
This commit is contained in:
@@ -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<Record<string, { stars: number; forks: number }>>({});
|
||||
|
||||
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<Record<string, { stars: number; forks: number }>>((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 (
|
||||
<section className="md:col-span-2 space-y-6">
|
||||
<h2 className="font-sketch text-2xl text-[var(--text-main)] border-b-2 border-dashed border-[var(--border-main)] pb-2 flex items-center gap-2">
|
||||
<h2 className="font-sketch text-2xl text-(--text-main) border-b-2 border-dashed border-(--border-main) pb-2 flex items-center gap-2">
|
||||
<Layout className="w-5 h-5 accent-text" /> recent_projects/
|
||||
</h2>
|
||||
<div className="flex flex-col gap-6">
|
||||
{PROJECTS.map((project, idx) => (
|
||||
<article
|
||||
key={project.id}
|
||||
className={`group relative p-6 bg-[var(--bg-surface)] sketch-border-subtle cursor-pointer overflow-hidden ${idx % 2 === 0 ? 'transform rotate-[0.5deg]' : 'transform -rotate-[0.5deg]'}`}
|
||||
className={`group relative p-6 bg-(--bg-surface) sketch-border-subtle cursor-pointer overflow-visible ${idx % 2 === 0 ? 'transform rotate-[0.5deg]' : 'transform -rotate-[0.5deg]'}`}
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-[var(--border-main)] group-hover:bg-[var(--color-accent)] transition-colors duration-300 opacity-50" />
|
||||
{project.ribbon && (
|
||||
<div className="absolute -top-2 -right-10 rotate-12">
|
||||
<div className="sketch-border-subtle bg-(--color-accent) text-(--bg-base) px-6 py-1 text-xs font-sketch uppercase tracking-wide shadow-lg">
|
||||
{project.ribbon}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-(--border-main) group-hover:bg-(--color-accent) transition-colors duration-300 opacity-50" />
|
||||
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 sketch-border-subtle bg-[var(--bg-panel)] text-[var(--text-muted)] group-hover:text-[var(--color-accent)] group-hover:scale-110 transition-all transform -rotate-3">
|
||||
{project.icon}
|
||||
<div className="group/icon relative flex items-center gap-2 py-2.5 ps-2.5 pe-1.5 pr-6 sketch-border-subtle bg-(--bg-panel) text-(--text-muted) hover:text-(--color-accent) hover:scale-110 transition-all transform -rotate-3">
|
||||
<span className="flex items-center justify-center">
|
||||
{project.icon}
|
||||
</span>
|
||||
<ChevronRight className="absolute right-1.5 top-1/2 -translate-y-1/2 w-4 h-4 text-(--color-accent) opacity-90 animate-pulse transition-all drop-shadow-[0_0_16px_var(--color-accent)] group-hover/icon:opacity-0 group-hover/icon:scale-0 group-focus-within/icon:opacity-0 group-focus-within/icon:scale-0" />
|
||||
{project.github && (
|
||||
<div className="flex items-center gap-3 text-xs text-(--text-muted) overflow-hidden max-w-0 opacity-0 group-hover/icon:max-w-35 group-hover/icon:opacity-100 transition-all duration-300">
|
||||
<span className="flex items-center gap-1">
|
||||
<Star className="w-3 h-3 text-(--color-accent)" />
|
||||
{repoStats[project.id]?.stars ?? project.stars ?? '—'}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<GitFork className="w-3 h-3 text-(--color-accent)" />
|
||||
{repoStats[project.id]?.forks ?? project.forks ?? '—'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-[var(--text-main)] font-bold text-lg flex items-center gap-2 font-mono">
|
||||
<h3 className="text-(--text-main) font-bold text-lg flex items-center gap-2 font-mono">
|
||||
{project.title}
|
||||
<ExternalLink className="w-4 h-4 opacity-0 group-hover:opacity-100 transition-opacity accent-text" />
|
||||
</h3>
|
||||
<span className="font-sketch text-sm text-[var(--text-muted)] accent-text">{project.category}</span>
|
||||
<span className="font-sketch text-sm text-(--text-muted) accent-text">{project.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-[var(--text-muted)] mb-5 leading-relaxed font-mono">
|
||||
<p className="text-sm text-(--text-muted) mb-5 leading-relaxed font-mono">
|
||||
{project.desc}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tech.map((t) => (
|
||||
<span key={t} className="text-xs px-2 py-1 bg-[var(--bg-panel)] sketch-border-subtle text-[var(--text-muted)] font-mono">
|
||||
<span key={t} className="text-xs px-2 py-1 bg-(--bg-panel) sketch-border-subtle text-(--text-muted) font-mono">
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -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: <Cpu className="w-5 h-5" />,
|
||||
},
|
||||
{
|
||||
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: <Chromium className='w-5 h-5' />,
|
||||
},
|
||||
{
|
||||
id: 'omnia',
|
||||
github: 'https://github.com/sortedcord/omnia',
|
||||
title: 'Omnia',
|
||||
category: 'AI-Native Computing Systems',
|
||||
tech: ['Typescript'],
|
||||
|
||||
@@ -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: <Layout className="w-5 h-5" />,
|
||||
},
|
||||
{
|
||||
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: <Cpu className="w-5 h-5" />,
|
||||
},
|
||||
{
|
||||
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: <Chromium className='w-5 h-5' />,
|
||||
},
|
||||
{
|
||||
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: <Gamepad className="w-5 h-5" />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user