Add MiniProject feature with modal and card components; enhance CSS animations
This commit is contained in:
272
src/App.tsx
272
src/App.tsx
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user