1437 lines
53 KiB
TypeScript
1437 lines
53 KiB
TypeScript
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 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",
|
||
},
|
||
{
|
||
name: "QrCode Generator",
|
||
description: "Vue.JS based QR Code Generator with a bit of customization.",
|
||
image: "https://cdn.reversed.dev/pictures/qrcode.jpeg",
|
||
open_source: { link: "https://github.com/reversed-dev/qr-code-gen" },
|
||
link: "https://qr.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://zippy.trumplovesgiving.top/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",
|
||
},
|
||
},
|
||
{
|
||
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",
|
||
},
|
||
];
|
||
|
||
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;
|