Add MiniProject feature with modal and card components; enhance CSS animations

This commit is contained in:
Space-Banane
2026-01-03 18:28:17 +01:00
parent 4a1f11b4fa
commit afdae3e32f
2 changed files with 292 additions and 10 deletions

View File

@@ -1,5 +1,185 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
interface MiniProject {
title: string;
description: string;
image?: string;
github?: string;
reproduction?: string;
why: string;
note?: {
color: string;
content: string;
};
}
function MiniProjectModal({
project,
onClose,
}: {
project: MiniProject;
onClose: () => void;
}) {
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"
onClick={onClose}
>
<div
className="relative max-w-3xl w-full max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-purple-500/30 rounded-2xl shadow-2xl animate-scale-in"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
{/* Image */}
{project.image && (
<div className="w-full h-64 overflow-hidden bg-black/40 rounded-t-2xl">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover"
/>
</div>
)}
{/* Content */}
<div className="p-8 space-y-6">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
{project.title}
</h2>
{project.note && (
<div
className="p-4 rounded-lg border-l-4 bg-black/20 backdrop-blur-sm"
style={{
borderColor: project.note.color,
backgroundColor: `${project.note.color}15`,
}}
>
<p
className="text-sm font-medium"
style={{ color: project.note.color }}
>
{project.note.content}
</p>
</div>
)}
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
Description
</h3>
<p className="text-gray-300 leading-relaxed">
{project.description}
</p>
</div>
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
Why I Made This
</h3>
<p className="text-gray-300 leading-relaxed">{project.why}</p>
</div>
{project.reproduction && (
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
How to Reproduce
</h3>
<pre className="text-sm text-gray-300 bg-black/40 p-4 rounded-lg overflow-x-auto border border-white/10">
<code>{project.reproduction}</code>
</pre>
</div>
)}
{project.github && (
<a
href={project.github}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-gradient-to-r from-purple-500 to-pink-500 text-white font-semibold hover:from-purple-600 hover:to-pink-600 transition-all shadow-lg shadow-purple-500/30 hover:shadow-purple-500/50"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
View on GitHub
</a>
)}
</div>
</div>
</div>
</div>
);
}
function MiniProjectCard({
project,
onClick,
}: {
project: MiniProject;
onClick: () => void;
}) {
return (
<div
onClick={onClick}
className="group cursor-pointer relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 hover:border-purple-500/50 transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl hover:shadow-purple-500/20"
>
{project.image && (
<div className="mb-4 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
</div>
)}
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
{project.title}
</h3>
<p className="text-gray-400 text-sm line-clamp-2 mb-4">
{project.description}
</p>
<div className="flex items-center text-purple-400 text-sm font-medium mt-auto">
Click to learn more
<svg
className="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</div>
</div>
);
}
function ProjectCard({ project }: { project: Project }) { function ProjectCard({ project }: { project: Project }) {
return ( return (
<div className="group relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/10 hover:border-purple-500/30 transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-purple-500/10"> <div className="group relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/10 hover:border-purple-500/30 transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-purple-500/10">
@@ -70,6 +250,7 @@ function App() {
const [displayMessage, setDisplayMessage] = useState(""); const [displayMessage, setDisplayMessage] = useState("");
const [isScrambling, setIsScrambling] = useState(false); const [isScrambling, setIsScrambling] = useState(false);
const [showOldNames, setShowOldNames] = useState(false); const [showOldNames, setShowOldNames] = useState(false);
const [selectedMiniProject, setSelectedMiniProject] = useState<MiniProject | null>(null);
const oldUsernames = [ const oldUsernames = [
"getspaced (ingame)", "getspaced (ingame)",
@@ -527,6 +708,37 @@ function App() {
</div> </div>
</section> </section>
{/* Mini Projects Section */}
<section className="w-full space-y-12">
<div className="text-center space-y-2">
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500">
Mini Projects
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
Small experiments and fun projects I've built along the way
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{miniProjects.map((project, index) => (
<MiniProjectCard
key={index}
project={project}
onClick={() => setSelectedMiniProject(project)}
/>
))}
</div>
</section>
{selectedMiniProject && (
<MiniProjectModal
project={selectedMiniProject}
onClose={() => setSelectedMiniProject(null)}
/>
)}
{/* Contact Section */} {/* Contact Section */}
<section className="w-full max-w-2xl text-center space-y-4 pb-8"> <section className="w-full max-w-2xl text-center space-y-4 pb-8">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500"> <h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500">
@@ -615,7 +827,7 @@ const projects: Project[] = [
}, },
{ {
name: "SHSF", name: "SHSF",
description: 'Self-hostable "Cloud Functions" for your own hardware.', description: 'Self-hostable "Cloud Functions" on your own hardware.',
link: "https://github.com/Space-Banane/shsf", link: "https://github.com/Space-Banane/shsf",
open_source: { link: "https://github.com/Space-Banane/shsf" }, open_source: { link: "https://github.com/Space-Banane/shsf" },
image: "https://cdn.reversed.dev/pictures/shsf/SHSF.png", image: "https://cdn.reversed.dev/pictures/shsf/SHSF.png",
@@ -641,15 +853,57 @@ const projects: Project[] = [
image: "https://cdn.reversed.dev/pictures/qrcode.jpeg", image: "https://cdn.reversed.dev/pictures/qrcode.jpeg",
open_source: { link: "https://github.com/reversed-dev/qr-code-gen" }, open_source: { link: "https://github.com/reversed-dev/qr-code-gen" },
link: "https://qr.reversed.dev", link: "https://qr.reversed.dev",
},
{
name: "Nvidia-SMI Server",
description: "A simple server to expose Nvidia-SMI data via HTTP.",
image: "https://www.nvidia.com/content/nvidiaGDC/us/en_US/about-nvidia/legal-info/logo-brand-usage/_jcr_content/root/responsivegrid/nv_container_392921705/nv_container_412055486/nv_image.coreimg.100.630.png/1703060329095/nvidia-logo-horz.png",
open_source: { link: "https://github.com/Space-Banane/nvidia-smi-server" },
link: "https://github.com/Space-Banane/nvidia-smi-server",
rounded: false,
} }
]; ];
const miniProjects: MiniProject[] = [
{
title: "Discord Status to Website",
description: "Display your Discord status on your personal website.",
why: "I wanted to show my Discord status here on my portfolio.",
github: "https://github.com/Space-Banane/shsf-discord-status",
note: {
color: "red",
content: "Requires SHSF to run",
},
image: "https://github.com/Space-Banane/shsf-discord-status/blob/main/Discord%20Status%20Image.png?raw=true"
},
{
title: "Mirror GoXLR to Discord",
description: "Muting your Mic Channel mutes you on Discord.",
why: "When i mute myself on my GoXLR, my friends don't know that, now they do.",
github: "https://github.com/Space-Banane/mirror-goxlr-to-discord",
note: {
color: "yellow",
content: "Experimental - This might interfere with some other Hotkeys you set, or Applications that listen to those Hotkeys"
},
image: "https://github.com/Space-Banane/mirror-goxlr-to-discord/blob/main/Discord%20x%20GoXLR.png?raw=true"
},
{
title: "Octo-Activity",
description: "Show your 3D Printer activity as Discord Status.",
why: "I wanted to show off my 3D Printer activity on Discord.",
github: "https://github.com/Space-Banane/discord-octo-activity",
image: "https://github.com/Space-Banane/discord-octo-activity/blob/main/Discord%20Octo%20Activity.png?raw=true",
},
{
title: "Fishing Rod Automation Raft",
description: "Automate fishing in Raft with this simple script.",
why: "Fishing while Shitting is kinda difficult.",
github: "https://github.com/Space-Banane/raft-fishing",
note: {
color: "yellow",
content: "Might interfere with other Applications that use Mouse/Keyboard input. In case of trust issues, move the mouse to the bottom right corner to stop the script."
},
image: "https://github.com/Space-Banane/raft-fishing/blob/main/Raft%20Fishing%20Guide%20-%20cover.jpg?raw=true"
},
{
title: "Nvidia-SMI Server",
description: "A simple server to expose Nvidia-SMI data via HTTP.",
why: "I wanted to monitor my GPU usage remotely without installing additional software.",
github: "https://github.com/Space-Banane/nvidia-smi-server",
image: "https://github.com/Space-Banane/nvidia-smi-server/blob/main/Nvidia%20-%20SMI.png?raw=true"
},
];
export default App; export default App;

View File

@@ -1 +1,29 @@
@import "tailwindcss"; @import "tailwindcss";
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}
.animate-scale-in {
animation: scale-in 0.3s ease-out;
}