Add ExperienceModal and ExperienceCard components; enhance Experience interface with additional fields

This commit is contained in:
Space-Banane
2026-01-07 21:44:21 +01:00
parent 910c3987d5
commit 455dc8f0a2

View File

@@ -18,6 +18,9 @@ interface Experience {
type: "language" | "service" | "platform" | "real life experience" | "roles"; type: "language" | "service" | "platform" | "real life experience" | "roles";
description: string; description: string;
image?: string; image?: string;
learned_at?: string;
learned_from?: string;
learned_because?: string;
} }
function MiniProjectModal({ function MiniProjectModal({
@@ -138,6 +141,117 @@ function MiniProjectModal({
); );
} }
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"]) { function getTypeIcon(type: Experience["type"]) {
switch (type) { switch (type) {
case "language": case "language":
@@ -221,6 +335,73 @@ function MiniProjectCard({
); );
} }
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 }) { 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">
@@ -292,12 +473,12 @@ function App() {
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 [selectedMiniProject, setSelectedMiniProject] = useState<MiniProject | null>(null);
const [selectedExperience, setSelectedExperience] = useState<Experience | null>(null);
const oldUsernames = [ const oldUsernames = [
"getspaced (ingame)", "getspaced (ingame)",
"Space² (alternative)", "Space² (alternative)",
"Space-Banane (2022-2024)", "Space-Banane (2022-2024)",
"banana[redacted]_ (2019-2022)",
]; ];
const rotatingMessages = [ const rotatingMessages = [
@@ -519,7 +700,7 @@ function App() {
About Me About Me
</h2> </h2>
<p className="text-gray-400 max-w-2xl mx-auto"> <p className="text-gray-400 max-w-2xl mx-auto">
A bit about who I am and what I do What i do and who i am.
</p> </p>
</div> </div>
@@ -527,60 +708,13 @@ function App() {
<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="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"> <div className="space-y-4 text-gray-300">
<p className="leading-relaxed"> <p className="leading-relaxed">
I'm a Software Developer from Germany, located near Frankfurt. I'm a Developer from Germany. I love doing full-stack web
development, tinkering with hardware, and 3D printing cool
stuff.
</p> </p>
<p className="leading-relaxed"> <p className="leading-relaxed">
My passion lies in coding, exploring limits, and occasionally I focus on building projects that i need, and that don't already exist.
reinventing the wheel just for the fun of it. While I love
open-source, I'm not afraid to build custom solutions when
existing ones don't fit.
</p> </p>
<p className="leading-relaxed">
I actively contribute to open-source projects, fixing the little
things that make a big difference.
</p>
</div>
</div>
</div>
</section>
{/* AI-Assisted Coding 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">
AI-Assisted Coding
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
How i use AI.
</p>
</div>
<div className="max-w-3xl mx-auto space-y-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="space-y-4 text-gray-300">
<p className="leading-relaxed">
I write most of my code myself, especially when it comes to logic, backend architecture,
authentication systems, database communication, and project setup. These core aspects are
where I believe hands-on coding is essential for quality and understanding.
</p>
<p className="leading-relaxed">
However, for UI development, I love using{" "}
<span className="text-purple-400 font-semibold">Claude Sonnet 4.5</span> to
help speed up the process and explore different design approaches. It's particularly helpful for
styling, layout, and creating polished user interfaces.
</p>
<p className="leading-relaxed">
I also embrace tools like{" "}
<span className="text-blue-400 font-semibold">GitHub Copilot's autocompletion</span>,
which enhances my productivity by suggesting code snippets and reducing repetitive typing.
It's like having a coding assistant that understands context.
</p>
<div className="mt-6 p-4 rounded-xl bg-white/5 border border-white/10">
<p className="text-sm text-gray-400 italic text-center">
"Ai wont replace shit, it'll either help us or kill us"
</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -593,7 +727,7 @@ function App() {
Hardware Stuff Hardware Stuff
</h2> </h2>
<p className="text-gray-400 max-w-2xl mx-auto"> <p className="text-gray-400 max-w-2xl mx-auto">
The lion is concerned with a LED connected to 12V The lion is concerned with a 5v rated LED connected to 12V
</p> </p>
</div> </div>
@@ -601,18 +735,14 @@ function App() {
<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="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"> <div className="space-y-4 text-gray-300">
<p className="leading-relaxed"> <p className="leading-relaxed">
When I'm not breaking my code, you'll find me breaking{" "} 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">Arduinos</span> and{" "}
<span className="text-orange-400 font-semibold">ESP32s</span>. I love interacting via Software with self-made hardware. <span className="text-orange-400 font-semibold">ESP32s</span>. I love interacting via Software with self-made hardware.
</p> </p>
<p className="leading-relaxed"> <p className="leading-relaxed">
I've built my fair share of little sensor gadgets and placed them around my house. All are managed using{" "} I've built some Smarthome sensors myself, including a few little bluetooth proxys that also serve as temperature sensors.</p>
<span className="text-blue-400 font-semibold">ESPHome</span> via{" "}
<span className="text-blue-400 font-semibold">Home Assistant</span>. Which means, no cloud dependencies and full control over my smart home setup.
</p>
<p className="leading-relaxed"> <p className="leading-relaxed">
I love using Arduinos because they're stupidly easy to build with & the bar to entry is extremely low with a stupid amount of tutorials, 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.
but the possibilities are endless. First it was the blinking LEDs, now it's servo control via bluetooth across the room.
</p> </p>
</div> </div>
</div> </div>
@@ -626,7 +756,7 @@ function App() {
3D Printing 3D Printing
</h2> </h2>
<p className="text-gray-400 max-w-2xl mx-auto"> <p className="text-gray-400 max-w-2xl mx-auto">
I turn Plastic on a Spool into Plastic in a Shape. Plastic on a Spool -{">"} Plasic in a Shape
</p> </p>
</div> </div>
@@ -636,17 +766,11 @@ function App() {
<p className="leading-relaxed"> <p className="leading-relaxed">
I run a modded Creality Ender 3 V1 with a{" "} I run a modded Creality Ender 3 V1 with a{" "}
<span className="text-cyan-400 font-semibold">BLTouch</span> for auto bed leveling, <span className="text-cyan-400 font-semibold">BLTouch</span> for auto bed leveling,
all managed remotely through <span className="text-cyan-400 font-semibold">OctoPrint</span>. all managed through <span className="text-cyan-400 font-semibold">OctoPrint</span>.
I slice my prints using <span className="text-cyan-400 font-semibold">OrcaSlicer</span> because Cura misses a few options. 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>
<p className="leading-relaxed"> <p className="leading-relaxed">
I mostly print replacement parts for stuff around the house and cool shit I find on{" "} 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.
<span className="text-cyan-400 font-semibold">Printables</span>. For custom designs, I use{" "}
<span className="text-cyan-400 font-semibold">Fusion 360</span> to model my own parts when I need something specific.
</p>
<p className="leading-relaxed">
I'm not a fan of Bamboo Lab. Their choices, pricing, and quality just don't. <br />
One day, I'd love to own a <span className="text-green-400 font-semibold">Creality K2 Pro</span>.
</p> </p>
</div> </div>
</div> </div>
@@ -660,7 +784,7 @@ function App() {
My Goals My Goals
</h2> </h2>
<p className="text-gray-400 max-w-2xl mx-auto"> <p className="text-gray-400 max-w-2xl mx-auto">
This is the stuff i want to learn. future things
</p> </p>
</div> </div>
@@ -778,6 +902,13 @@ function App() {
/> />
)} )}
{selectedExperience && (
<ExperienceModal
experience={selectedExperience}
onClose={() => setSelectedExperience(null)}
/>
)}
{/* Experience Section */} {/* Experience Section */}
<section className="w-full space-y-8"> <section className="w-full space-y-8">
<div className="text-center space-y-2"> <div className="text-center space-y-2">
@@ -809,32 +940,11 @@ function App() {
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{items.map((exp, index) => ( {items.map((exp, index) => (
<div <ExperienceCard
key={index} key={index}
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 hover:border-purple-500/50 transition-all duration-200 hover:bg-white/10" experience={exp}
> onClick={() => setSelectedExperience(exp)}
<div className={`text-2xl p-2 rounded-lg ${getTypeColor(exp.type)} flex items-center justify-center shrink-0 ${exp.image ? 'w-10 h-10' : ''}`}> />
{exp.image ? (
<img
src={exp.image}
alt={exp.name}
className="w-full h-full object-contain"
/>
) : (
getTypeIcon(exp.type)
)}
</div>
<div className="flex-1 min-w-0">
<h4 className="text-base font-semibold text-white group-hover:text-purple-400 transition-colors">
{exp.name}
</h4>
{exp.description && (
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">
{exp.description}
</p>
)}
</div>
</div>
))} ))}
</div> </div>
</div> </div>
@@ -1119,18 +1229,20 @@ const experiences: Experience[] = [
name: "Immich", name: "Immich",
type: "platform", type: "platform",
description: "Self-hosted photo and video management solution.", description: "Self-hosted photo and video management solution.",
image: "https://avatars.githubusercontent.com/u/109746326?v=4" image: "https://avatars.githubusercontent.com/u/109746326?v=4",
learned_because: "I hate OneDrive"
}, },
{ {
name: "Pangolin", name: "Pangolin",
type: "platform", type: "platform",
description: "Proxy solution for network management.", description: "Proxy solution for network management.",
image: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pangolin.svg", 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", name: "Nginx",
type: "platform", type: "platform",
description: "Proxy solution for network management.", description: "Proxy solution for network management. (old proxy)",
image: "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nginx/nginx-original.svg", image: "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nginx/nginx-original.svg",
}, },
{ {