Add ExperienceModal and ExperienceCard components; enhance Experience interface with additional fields
This commit is contained in:
310
src/App.tsx
310
src/App.tsx
@@ -18,6 +18,9 @@ interface Experience {
|
||||
type: "language" | "service" | "platform" | "real life experience" | "roles";
|
||||
description: string;
|
||||
image?: string;
|
||||
learned_at?: string;
|
||||
learned_from?: string;
|
||||
learned_because?: string;
|
||||
}
|
||||
|
||||
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"]) {
|
||||
switch (type) {
|
||||
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 }) {
|
||||
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">
|
||||
@@ -292,12 +473,12 @@ function App() {
|
||||
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)",
|
||||
"banana[redacted]_ (2019-2022)",
|
||||
];
|
||||
|
||||
const rotatingMessages = [
|
||||
@@ -519,7 +700,7 @@ function App() {
|
||||
About Me
|
||||
</h2>
|
||||
<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>
|
||||
</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="space-y-4 text-gray-300">
|
||||
<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 className="leading-relaxed">
|
||||
My passion lies in coding, exploring limits, and occasionally
|
||||
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.
|
||||
I focus on building projects that i need, and that don't already exist.
|
||||
</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>
|
||||
@@ -593,7 +727,7 @@ function App() {
|
||||
Hardware Stuff
|
||||
</h2>
|
||||
<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>
|
||||
</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="space-y-4 text-gray-300">
|
||||
<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">ESP32s</span>. I love interacting via Software with self-made hardware.
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
I've built my fair share of little sensor gadgets and placed them around my house. All are managed using{" "}
|
||||
<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>
|
||||
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 Arduinos because they're stupidly easy to build with & the bar to entry is extremely 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.
|
||||
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>
|
||||
@@ -626,7 +756,7 @@ function App() {
|
||||
3D Printing
|
||||
</h2>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -636,17 +766,11 @@ function App() {
|
||||
<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 remotely 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.
|
||||
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 mostly print replacement parts for stuff around the house and cool shit I find on{" "}
|
||||
<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>.
|
||||
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>
|
||||
@@ -660,7 +784,7 @@ function App() {
|
||||
My Goals
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
This is the stuff i want to learn.
|
||||
future things
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -778,6 +902,13 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedExperience && (
|
||||
<ExperienceModal
|
||||
experience={selectedExperience}
|
||||
onClose={() => setSelectedExperience(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Experience Section */}
|
||||
<section className="w-full space-y-8">
|
||||
<div className="text-center space-y-2">
|
||||
@@ -809,32 +940,11 @@ function App() {
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{items.map((exp, index) => (
|
||||
<div
|
||||
<ExperienceCard
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
experience={exp}
|
||||
onClick={() => setSelectedExperience(exp)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1119,18 +1229,20 @@ const experiences: Experience[] = [
|
||||
name: "Immich",
|
||||
type: "platform",
|
||||
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",
|
||||
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.",
|
||||
description: "Proxy solution for network management. (old proxy)",
|
||||
image: "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nginx/nginx-original.svg",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user