Files
my-portfolio/src/App.tsx
2026-01-16 21:53:38 +01:00

1451 lines
53 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
interface MiniProject {
title: string;
description: string;
image?: string;
github?: string;
reproduction?: string;
why: string;
note?: {
color: string;
content: string;
};
}
interface Experience {
name: string;
type: "language" | "service" | "platform" | "real life experience" | "roles";
description: string;
image?: string;
learned_at?: string;
learned_from?: string;
learned_because?: 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 ExperienceModal({
experience,
onClose,
}: {
experience: Experience;
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-2xl 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>
{/* Content */}
<div className="p-8 space-y-6">
<div className="flex items-center gap-4">
<div
className={`text-4xl p-3 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 ${experience.image ? "w-16 h-16" : ""}`}
>
{experience.image ? (
<img
src={experience.image}
alt={experience.name}
className="w-full h-full object-contain"
/>
) : (
getTypeIcon(experience.type)
)}
</div>
<div>
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
{experience.name}
</h2>
<p className="text-sm text-gray-400 mt-1">
{getTypeIcon(experience.type)}{" "}
{experience.type.charAt(0).toUpperCase() +
experience.type.slice(1)}
</p>
</div>
</div>
<div className="space-y-4">
{experience.description && (
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
Description
</h3>
<p className="text-gray-300 leading-relaxed">
{experience.description}
</p>
</div>
)}
{experience.learned_at && (
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
📅 When I Learned It
</h3>
<p className="text-gray-300 leading-relaxed">
{experience.learned_at}
</p>
</div>
)}
{experience.learned_from && (
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
👨🏫 How I Learned It
</h3>
<p className="text-gray-300 leading-relaxed">
{experience.learned_from}
</p>
</div>
)}
{experience.learned_because && (
<div>
<h3 className="text-lg font-semibold text-purple-400 mb-2">
💡 Why I Learned It
</h3>
<p className="text-gray-300 leading-relaxed">
{experience.learned_because}
</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}
function getTypeIcon(type: Experience["type"]) {
switch (type) {
case "language":
return "💻";
case "service":
return "☁️";
case "platform":
return "🚀";
case "real life experience":
return "🌍";
case "roles":
return "👔";
default:
return "📦";
}
}
function getTypeColor(type: Experience["type"]) {
switch (type) {
case "language":
return "bg-blue-500/20";
case "service":
return "bg-purple-500/20";
case "platform":
return "bg-green-500/20";
case "real life experience":
return "bg-orange-500/20";
case "roles":
return "bg-pink-500/20";
default:
return "bg-gray-500/20";
}
}
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 ExperienceCard({
experience,
onClick,
}: {
experience: Experience;
onClick?: () => void;
}) {
const isClickable = !!(
experience.learned_at ||
experience.learned_from ||
experience.learned_because
);
return (
<div
onClick={isClickable ? onClick : undefined}
className={`group flex items-center gap-3 p-3 rounded-lg bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 transition-all duration-200 ${
isClickable
? "cursor-pointer hover:border-purple-500/50 hover:bg-white/10 hover:-translate-y-0.5"
: ""
}`}
>
<div
className={`text-2xl p-2 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 relative ${
experience.image ? "w-10 h-10" : ""
}`}
>
{experience.image ? (
<img
src={experience.image}
alt={experience.name}
className="w-full h-full object-contain"
/>
) : (
getTypeIcon(experience.type)
)}
{isClickable && (
<div className="absolute -top-1 -right-1 w-3 h-3 bg-purple-500 rounded-full border-2 border-black animate-pulse" />
)}
</div>
<div className="flex-1 min-w-0">
<h4
className={`text-base font-semibold text-white transition-colors ${
isClickable ? "group-hover:text-purple-400" : ""
}`}
>
{experience.name}
</h4>
{experience.description && (
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">
{experience.description}
</p>
)}
</div>
{isClickable && (
<div className="text-purple-400 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<svg
className="w-4 h-4"
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 }) {
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">
{project.image && (
<div className="mb-6 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center relative">
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<img
src={project.image}
alt={project.name}
className={`object-contain ${project.rounded === false ? "w-full h-auto max-h-24 rounded-lg" : "h-24 w-24 rounded-full"} shadow-lg 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.name}
</h3>
<p className="text-gray-400 text-sm flex-grow mb-6 leading-relaxed">
{project.description}
</p>
<div className="flex gap-3 mt-auto">
<a
href={project.link + "?utm_source=portfolio&ref=space"}
target="_blank"
rel="noreferrer"
className="flex-1 py-2 px-4 rounded-lg bg-white/10 hover:bg-white/20 text-white text-sm font-medium text-center transition-colors flex items-center justify-center gap-2"
>
Visit
<svg
className="w-3 h-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
{project.open_source && (
<a
href={project.open_source.link}
target="_blank"
rel="noreferrer"
className="p-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition-colors border border-white/5"
title="View Source"
>
<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>
</a>
)}
</div>
</div>
);
}
function App() {
const [status, setStatus] = useState("");
const [borderStatus, setBorderStatus] = useState("border-gray-700");
const [glowColor, setGlowColor] = useState("rgba(55, 65, 81, 0.5)");
const [statusMessage, setStatusMessage] = useState("Breaking SHSF");
const [messageIndex, setMessageIndex] = useState(0);
const [displayMessage, setDisplayMessage] = useState("");
const [isScrambling, setIsScrambling] = useState(false);
const [showOldNames, setShowOldNames] = useState(false);
const [selectedMiniProject, setSelectedMiniProject] =
useState<MiniProject | null>(null);
const [selectedExperience, setSelectedExperience] =
useState<Experience | null>(null);
const oldUsernames = [
"getspaced (ingame)",
"Space² (alternative)",
"Space-Banane (2022-2024)",
];
const rotatingMessages = [
"Bricking Esp32's 🧱",
"Buy me a White Redbull⁉",
"Testing in production 🧪",
"Shipping bug fixes 📦",
"Not learning Rust 🦀",
"Pushing before Copilot's Review ✅",
"Breaking SHSF 🚧",
"No debugger attached 🐞",
"Asking ChatGPT for help 🤖",
"Refactoring for the 10th time 🔄",
"Writing documentation 🥹",
"Removing Comments 🧹",
"Collecting Spotify hours 🎵",
];
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:',.<>?/`~" +
"🧱⁉️🧪📦🦀✅🚧🐞🤖🔄🥹🧹🎵";
useEffect(() => {
if (status === "online") {
setBorderStatus("border-green-500");
setGlowColor("rgba(34, 197, 94, 0.5)");
setStatusMessage("Reachable");
} else if (status === "offline") {
setBorderStatus("border-gray-500");
setGlowColor("rgba(107, 114, 128, 0.5)");
setStatusMessage("Probably Away");
} else if (status === "dnd") {
setBorderStatus("border-red-600");
setGlowColor("rgba(220, 38, 38, 0.5)");
setStatusMessage("Not Reachable");
} else if (status === "idle") {
setBorderStatus("border-yellow-500");
setGlowColor("rgba(234, 179, 8, 0.5)");
setStatusMessage("Doing anything but work");
} else {
setBorderStatus("border-gray-700");
setGlowColor("rgba(55, 65, 81, 0.5)");
setStatusMessage("Breaking SHSF");
}
}, [status]);
useEffect(() => {
fetch(
"https://shsf-api.reversed.dev/api/exec/6/c084ec4a-1b20-491e-ab2e-67c5fa8881e6",
)
.then((res) => res.json())
.then((data) => {
setStatus(data.status);
})
.catch((err) => {
console.error(err);
setStatus("offline");
});
}, []);
// Scramble effect
useEffect(() => {
const targetMessage = rotatingMessages[messageIndex];
if (isScrambling) {
let iteration = 0;
const scrambleInterval = setInterval(() => {
setDisplayMessage(
targetMessage
.split("")
.map((char, index) => {
if (index < iteration) {
return targetMessage[index];
}
if (char === " " || /[\u{1F000}-\u{1F9FF}]/u.test(char)) {
return char;
}
return characters[Math.floor(Math.random() * characters.length)];
})
.join(""),
);
if (iteration >= targetMessage.length) {
clearInterval(scrambleInterval);
setIsScrambling(false);
}
iteration += 1 / 3;
}, 30);
return () => clearInterval(scrambleInterval);
} else {
setDisplayMessage(targetMessage);
}
}, [messageIndex, isScrambling]);
// Rotating message effect
useEffect(() => {
const messageInterval = setInterval(() => {
setIsScrambling(true);
setMessageIndex((prev) => (prev + 1) % rotatingMessages.length);
}, 7000);
return () => {
clearInterval(messageInterval);
};
}, []);
return (
<div className="relative min-h-screen bg-black text-white selection:bg-purple-500/30">
{/* Background Grid */}
<div className="fixed inset-0 z-0 pointer-events-none">
<div
className="absolute inset-0 blur-sm"
style={{
backgroundImage:
"linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px)",
backgroundSize: "30px 30px",
maskImage:
"radial-gradient(circle at center, black 40%, transparent 100%)",
}}
/>
</div>
<main className="relative z-10 flex flex-col items-center px-6 py-20 mx-auto max-w-5xl gap-24">
{/* Hero Section */}
<section className="flex flex-col items-center text-center space-y-8 animate-fade-in-down">
<div className="relative group">
<div
className={`absolute -inset-1 rounded-full blur opacity-75 transition duration-500`}
style={{ backgroundColor: glowColor }}
></div>
<div
className={`relative w-48 h-48 rounded-full border-4 transition-colors duration-500 overflow-hidden ${borderStatus} bg-black`}
>
<img
src="https://cdn.reversed.dev/pictures/20250405_120402.png"
alt="Space"
className="w-full h-full object-cover scale-110 transition-transform duration-700 group-hover:scale-125"
/>
</div>
{/* Rotating Message Bubble (Left Side) */}
<div className="absolute -left-4 top-4 -translate-x-full">
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-sm border border-purple-500/30 rounded-2xl px-4 py-3 shadow-lg">
{/* Arrow pointing to profile */}
<div className="absolute right-0 top-1/2 translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/30"></div>
<div className="absolute right-0 top-1/2 translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/20"></div>
<p className="text-sm text-gray-200 whitespace-nowrap font-medium font-mono">
{displayMessage || rotatingMessages[0]}
</p>
</div>
</div>
{/* Status Bubble (Right Side) */}
<div className="absolute -right-4 top-8 translate-x-full">
<div className="relative bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/20 rounded-2xl px-4 py-3 shadow-lg">
{/* Arrow pointing to profile */}
<div className="absolute left-0 top-1/2 -translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/20"></div>
<div className="absolute left-0 top-1/2 -translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/10"></div>
<p className="text-sm text-gray-300 whitespace-nowrap">
Currently{" "}
<span className="text-white font-semibold">
{statusMessage}
</span>
<br />
on Discord
</p>
</div>
</div>
</div>
<div className="space-y-4 max-w-2xl">
<h1 className="text-6xl md:text-7xl font-extrabold">
Hey, I'm{" "}
<span
className="relative inline-block text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-purple-500 to-pink-500 cursor-help px-2"
style={{ marginLeft: "0.15em", marginRight: "0.15em" }}
onMouseEnter={() => setShowOldNames(true)}
onMouseLeave={() => setShowOldNames(false)}
>
Space²
{showOldNames && (
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-4 z-50 animate-fade-in flex justify-center w-full">
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-md border border-purple-500/30 rounded-xl px-6 py-4 shadow-2xl min-w-[220px] max-w-xs">
<div className="absolute left-1/2 -translate-x-1/2 -top-2 w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-8 border-b-purple-500/30"></div>
<p className="text-xs text-gray-400 mb-2 font-medium whitespace-nowrap text-center">
Also known as:
</p>
<ul className="flex flex-col items-center gap-1.5">
{oldUsernames.map((name, index) => (
<li
key={index}
className="text-sm text-gray-200 font-mono hover:text-purple-400 transition-colors whitespace-nowrap text-center"
>
{name}
</li>
))}
</ul>
</div>
</div>
)}
</span>
</h1>
<p className="text-2xl text-gray-400 font-light">
A{" "}
<span className="line-through decoration-purple-500/50 decoration-2">
Self-proclaimed
</span>{" "}
Developer breaking things to see how they&nbsp;work.
</p>
</div>
</section>
{/* About Section */}
<section className="w-full space-y-8">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
About Me
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
What i do and who i am.
</p>
</div>
<div className="max-w-3xl mx-auto">
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
<div className="space-y-4 text-gray-300">
<p className="leading-relaxed">
I'm a Developer from Germany. I love doing full-stack web
development, tinkering with hardware, and 3D printing cool
stuff.
</p>
<p className="leading-relaxed">
I focus on building projects that i need, and that don't
already exist.
</p>
</div>
</div>
</div>
</section>
{/* Hardware Mods & Builds Section */}
<section className="w-full space-y-8">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 to-red-500">
Hardware Stuff
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
The lion is concerned with a 5v rated LED connected to 12V
</p>
</div>
<div className="max-w-3xl mx-auto">
<div className="p-6 rounded-2xl bg-gradient-to-br from-orange-500/10 to-red-500/5 backdrop-blur-sm border border-orange-500/20">
<div className="space-y-4 text-gray-300">
<p className="leading-relaxed">
When im not breaking Typescript im usually playing with{" "}
<span className="text-orange-400 font-semibold">
Arduinos
</span>{" "}
and{" "}
<span className="text-orange-400 font-semibold">ESP32s</span>.
I love interacting via Software with self-made hardware.
</p>
<p className="leading-relaxed">
I've built some Smarthome sensors myself, including a few
little bluetooth proxys that also serve as temperature
sensors.
</p>
<p className="leading-relaxed">
I love using ESP32s because they're stupidly easy to build
with & the bar to entry is stupidly low with a stupid amount
of tutorials.
</p>
</div>
</div>
</div>
</section>
{/* 3D Printing Section */}
<section className="w-full space-y-8">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-blue-500">
3D Printing
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
Plastic on a Spool -{">"} Plasic in a Shape
</p>
</div>
<div className="max-w-3xl mx-auto">
<div className="p-6 rounded-2xl bg-gradient-to-br from-cyan-500/10 to-blue-500/5 backdrop-blur-sm border border-cyan-500/20">
<div className="space-y-4 text-gray-300">
<p className="leading-relaxed">
I run a modded Creality Ender 3 V1 with a{" "}
<span className="text-cyan-400 font-semibold">BLTouch</span>{" "}
for auto bed leveling, all managed through{" "}
<span className="text-cyan-400 font-semibold">OctoPrint</span>
. I slice my stuff using{" "}
<span className="text-cyan-400 font-semibold">
OrcaSlicer
</span>{" "}
because Cura's UI gives me a flashbang everytime i open it.
</p>
<p className="leading-relaxed">
I would lie if i said i print useful stuff. Most of the stuff
i print is for organising stuff and a few fun prints here and
there.
</p>
</div>
</div>
</div>
</section>
{/* Goals Section */}
<section className="w-full space-y-8">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
My Goals
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">future things</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-blue-500/20 text-3xl">🚀</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">
Not Just Semi-Fullstack
</h3>
<p className="text-gray-400 leading-relaxed">
I want to work more with Serverless Architectures and Cloud
Services to build scalable applications.
</p>
</div>
</div>
</div>
<div className="p-6 rounded-2xl bg-gradient-to-br from-purple-500/10 to-pink-500/5 backdrop-blur-sm border border-purple-500/20">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-purple-500/20 text-3xl">
💻
</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">
Contribute More to Open Source
</h3>
<p className="text-gray-400 leading-relaxed">
I want to commit more to Open-Source Projects. That's it...
</p>
</div>
</div>
</div>
<div className="p-6 rounded-2xl bg-gradient-to-br from-green-500/10 to-blue-500/5 backdrop-blur-sm border border-green-500/20">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-green-500/20 text-3xl">
</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">
Expand on Existing Projects
</h3>
<p className="text-gray-400 leading-relaxed">
I want to make SHSF more stable and usable.
</p>
</div>
</div>
</div>
<div className="p-6 rounded-2xl bg-gradient-to-br from-yellow-500/10 to-orange-500/5 backdrop-blur-sm border border-yellow-500/20">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-yellow-500/20 text-3xl">
🦀
</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">
Explore the Unknown
</h3>
<p className="text-gray-400 leading-relaxed">Rust?👀</p>
</div>
</div>
</div>
</div>
</section>
{/* 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-purple-400 via-pink-500 to-orange-400">
Favourite Projects
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
Personal Faviorite? Uhhhhh....
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{projects.map((project, index) => (
<ProjectCard key={index} project={project} />
))}
</div>
</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)}
/>
)}
{selectedExperience && (
<ExperienceModal
experience={selectedExperience}
onClose={() => setSelectedExperience(null)}
/>
)}
{/* Experience Section */}
<section className="w-full space-y-8">
<div className="text-center space-y-2">
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 via-red-500 to-pink-500">
Experience
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
Technologies, platforms, and roles I've worked with
</p>
</div>
<div className="max-w-4xl mx-auto space-y-6">
{Object.entries(
experiences.reduce(
(acc, exp) => {
if (!acc[exp.type]) acc[exp.type] = [];
acc[exp.type].push(exp);
return acc;
},
{} as Record<string, Experience[]>,
),
).map(([type, items]) => (
<div key={type} className="space-y-3">
<h3
className={`text-lg font-semibold flex items-center gap-2 ${
type === "language"
? "text-blue-400"
: type === "service"
? "text-purple-400"
: type === "platform"
? "text-green-400"
: type === "real life experience"
? "text-orange-400"
: type === "roles"
? "text-pink-400"
: "text-gray-400"
}`}
>
{getTypeIcon(type as Experience["type"])}{" "}
{type.charAt(0).toUpperCase() + type.slice(1)}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{items.map((exp, index) => (
<ExperienceCard
key={index}
experience={exp}
onClick={() => setSelectedExperience(exp)}
/>
))}
</div>
</div>
))}
</div>
<p className="text-center text-gray-400 mt-4">
And a bunch of stuff i probably also forgot...
</p>
</section>
{/* Contact Section */}
<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">
Let's Connect
</h2>
<div className="p-8 rounded-3xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 border border-blue-500/20 backdrop-blur-md">
<p className="text-gray-300 mb-8 text-lg">
Got anything on your mind? Shoot me a DM or Email.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="mailto:space@reversed.dev"
className="px-8 py-3 rounded-full bg-gradient-to-r from-white to-gray-100 text-black font-bold hover:from-gray-100 hover:to-white transition-all shadow-lg shadow-white/20 hover:shadow-white/30"
>
Send Email
</a>
<a
href="https://discord.com/users/456443941169004545"
target="_blank"
rel="noreferrer"
className="px-8 py-3 rounded-full bg-gradient-to-r from-[#5865F2] to-[#4752C4] text-white font-bold hover:from-[#4752C4] hover:to-[#5865F2] transition-all shadow-lg shadow-[#5865F2]/30 hover:shadow-[#5865F2]/50"
>
Discord
</a>
</div>
</div>
</section>
{/* Footer */}
<footer className="w-full border-t border-white/10 pt-4">
<div className="text-center space-y-3">
<div className="flex items-center justify-center gap-2">
<img
src="http://shsf.reversed.dev/SHSF%20SMALL%20TRANSPARENT.png"
alt="SHSF Logo"
className="w-6 h-6"
/>
<p className="text-gray-400 text-sm">
Partly powered by{" "}
<a
href="https://github.com/Space-Banane/shsf"
target="_blank"
rel="noreferrer"
className="text-purple-400 hover:text-purple-300 transition-colors"
>
SHSF
</a>
</p>
</div>
<p className="text-gray-400 text-sm">Love from Space </p>
<p className="text-gray-500 text-xs">
Designed with Copilot &{" "}
<a
href="https://gradienty.codes/"
target="_blank"
rel="noreferrer"
className="text-purple-400 hover:text-purple-300 transition-colors underline"
>
gradienty.codes
</a>
, Tailwind and React
</p>
</div>
</footer>
</main>
</div>
);
}
interface Project {
name: string;
description: string;
image?: string;
link: string;
open_source: false | { link: string };
rounded?: boolean;
}
const projects: Project[] = [
{
name: "BetterNews",
description: "A news feed where you submit the news.",
image: "https://betternews.app/assets/icon.png",
open_source: false,
link: "https://betternews.app",
},
{
name: "SHSF",
description: 'Self-hostable "Cloud Functions" on your own hardware.',
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",
},
{
name: "Thoughtful",
description: "A minimalistic and privacy-focused idea-taking app.",
link: "https://github.com/Space-Banane/thoughtful",
open_source: { link: "https://github.com/Space-Banane/thoughtful" },
image:
"https://em-content.zobj.net/source/microsoft-3D-fluent/433/thinking-face_1f914.png",
},
{
name: "3D Print Card",
description: "Showcase Your 3D Prints Beautifully",
link: "https://card.peakprinting.top",
image: "https://card.peakprinting.top/icon.png",
open_source: { link: "https://github.com/Space-Banane/imsoprintingit" },
},
{
name: "Whatsapp-Chat-Analyzer",
description:
"Analyze your Whatsapp chats with ease. Get insights, stats and more.",
image: "https://cdn.reversed.dev/pictures/wca.png",
open_source: { link: "https://github.com/Space-Banane/whatsapp-stats" },
link: "https://whatstat.reversed.dev",
}
];
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",
},
{
title: "Lego Set to STL",
description: "Convert Lego set instructions to STL files for 3D printing.",
why: "A friend of mine searched for a website that does something like this, so i made one.",
github: "https://github.com/Space-Banane/lego-to-stl",
image: "https://shx.reversed.dev/u/44nekH.png",
},
{
title: "Thoughtful Extention for Raycast",
description: "Quickly add ideas to Thoughtful from Raycast.",
why: "I use Raycast a lot, so having a quick way to add ideas to Thoughtful is super convenient.",
github: "https://gitea.reversed.dev/space/thoughtful-extention",
note: {
color: "blue",
content: "Requires Thoughtful & Raycast to use lol",
},
image: "https://shx.reversed.dev/u/bCOaZt.png"
},
{
title: "Big File Generator",
description:
"Generate large files filled with random data for testing purposes.",
why: "SMB performance test, i did NOT have a big file lying around.",
github: "https://gitea.reversed.dev/space/big-file-gen",
image:
"https://em-content.zobj.net/source/microsoft-3D-fluent/433/file-folder_1f4c1.png",
},
{
title: "Active Directory Profile Picture Management",
description: "Manage and update Active Directory profile pictures easily.",
why: "I wanted a simple way to manage profile pictures in Active Directory. (home lab stuff)",
github: "https://github.com/Space-Banane/ADPPM",
image:
"https://shx.reversed.dev/u/RSqlwN.png",
note: {
color: "red",
content:
"Work in Progress + Requires Active Directory Environment + Requires Go to be installed",
},
},
{
title: "QrCode Generator",
description: "Vue.JS based QR Code Generator with a bit of customization.",
why: "I wanted a simple and customizable QR code generator.",
github: "https://github.com/reversed-dev/qr-code-gen",
image: "https://cdn.reversed.dev/pictures/qrcode.jpeg",
},
];
const experiences: Experience[] = [
{
name: "TypeScript",
type: "language",
description:
"My go-to language for building scalable web applications with type safety.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/typescript/typescript-original.svg",
learned_because: "I HATE JavaScript, this makes it minimally better",
},
{
name: "JavaScript",
type: "language",
description: "The foundation of web development.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/javascript/javascript-original.svg",
},
{
name: "Python",
type: "language",
description:
"Used for automation scripts, data processing, and backend services.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-original.svg",
},
{
name: "Go",
type: "language",
description: "For building fast and efficient backend services.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/go/go-original.svg",
},
{
name: "Lua",
type: "language",
description: "Scripting and embedded applications.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/lua/lua-original.svg",
learned_from: "Youtube & Docs",
learned_because: "Used in some Robots in a Minecraft mod called CC:Tweaked",
},
{
name: "React",
type: "platform",
description:
"Building interactive user interfaces with modern React patterns and hooks.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg",
},
{
name: "Vue.js",
type: "platform",
description: "Creating reactive web applications with Vue's elegant API.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg",
},
{
name: "Docker",
type: "service",
description:
"Containerizing applications for consistent deployment across environments.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/docker/docker-original.svg",
},
{
name: "PostgreSQL",
type: "service",
description: "Reliable relational database for complex data storage needs.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/postgresql/postgresql-original.svg",
},
{
name: "Redis",
type: "service",
description: "High-performance caching and data structure store.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/redis/redis-original.svg",
},
{
name: "MariaDB",
type: "service",
description: "Open-source relational database management system.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mariadb/mariadb-original.svg",
},
{
name: "Proxy Software",
type: "service",
description:
"Configuring and managing reverse proxies for web applications.",
},
{
name: "Home Assistant",
type: "platform",
description:
"Building smart home automations and integrating various IoT devices.",
image:
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/home-assistant.png",
},
{
name: "ESPHome",
type: "platform",
description:
"Programming ESP32 and ESP8266 microcontrollers for custom smart home sensors.",
image: "https://esphome.io/_images/logo.svg",
},
{
name: "Troubleshooting Hardware",
type: "real life experience",
description:
"Diagnosing and fixing hardware issues, from bricked microcontrollers to server problems.",
},
{
name: "Node.js",
type: "platform",
description: "Building backend services and APIs with Express and Fastify.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-original.svg",
},
{
name: "Immich",
type: "platform",
description: "Self-hosted photo and video management solution.",
image: "https://avatars.githubusercontent.com/u/109746326?v=4",
learned_because: "I hate OneDrive",
},
{
name: "Pangolin",
type: "platform",
description: "Proxy solution for network management.",
image:
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pangolin.svg",
learned_because:
"My old proxy did NOT work super well with authentication.",
},
{
name: "Nginx",
type: "platform",
description: "Proxy solution for network management. (old proxy)",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nginx/nginx-original.svg",
},
{
name: "Minecraft Servers",
type: "platform",
description: "Hosting and managing multiplayer game servers.",
image:
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/minecraft.png",
},
{
name: "Gitea",
type: "platform",
description: "Self-hosted Git service for version control.",
image:
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/gitea.png",
},
{
name: "Serverless Functions",
type: "platform",
description: "Building scalable cloud functions and microservices. (SHSF)",
image:
"https://cdn.reversed.dev/pictures/shsf/SHSF%20SMALL%20TRANSPARENT.png",
},
{
name: "Cloud Infrastructure",
type: "platform",
description:
"Managing and deploying applications on cloud platforms. (GCP, AWS, DigitalOcean)",
},
{
name: "Git",
type: "service",
description:
"Version control for collaborative development and open-source contributions.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/git/git-original.svg",
},
{
name: "ShareX",
type: "platform",
description:
"Custom uploaders and automation workflows for screenshots and file sharing.",
image:
"https://upload.wikimedia.org/wikipedia/commons/d/d1/ShareX_Logo.png",
},
{
name: "Zipline",
type: "platform",
description: "ShareX companion for self-hosted file uploads.",
image: "https://media.sys.truenas.net/apps/zipline/icons/icon.png",
},
{
name: "Tailwind CSS",
type: "platform",
description: "Utility-first CSS framework for rapid UI development.",
image:
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Tailwind_CSS_Logo.svg/2560px-Tailwind_CSS_Logo.svg.png",
},
{
name: "Proxmox VE",
type: "platform",
description:
"Virtualization management platform for running VMs and containers.",
image:
"https://media.printables.com/media/prints/543748/images/4374616_04ed4585-3662-4652-a84d-04621cb9f709/thumbs/inside/1280x960/png/proxmox.webp",
},
{
name: "MongoDB",
type: "service",
description: "NoSQL database for flexible document-based data storage.",
image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mongodb/mongodb-original.svg",
learned_because: "I didn't wanna mess around with schemas for a project.",
learned_at: "09.01.2026",
},
{
name: "Insomnia",
type: "platform",
description: "REST API client for testing and debugging HTTP requests.",
image:
"https://s3.amazonaws.com/s3.roaringapps.com/assets/icons/1561251841927-Insomnia.png",
learned_because: "Postman is bloatware",
},
];
export default App;