Compare commits

...

2 Commits

Author SHA1 Message Date
robre 965d2a41dd feat: enhance HeroSection layout and content with improved grid structure and added statistics
Deploy Portfolio Selfmade / deploy (push) Successful in 26s
2026-05-10 21:22:05 +02:00
robre a7e62e84fa feat: localize content to German across various components and add profile image 2026-05-10 21:18:16 +02:00
14 changed files with 314 additions and 175 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 870 KiB

@@ -38,14 +38,14 @@ export const ContactSection: React.FC = () => {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6" className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
> >
<span className="text-green-400 text-sm font-medium">GET IN TOUCH</span> <span className="text-green-400 text-sm font-medium">Kontakt aufnehmen</span>
</motion.div> </motion.div>
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4"> <h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Let's Work Together Lass uns zusammenarbeiten
</h2> </h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto"> <p className="text-gray-400 text-lg max-w-2xl mx-auto">
Have a project in mind? Let's discuss how we can bring your ideas to life Hast du ein Projekt? Lass uns besprechen, wie wir deine Idee umsetzen.
</p> </p>
</div> </div>
@@ -63,32 +63,32 @@ export const ContactSection: React.FC = () => {
<label className="block text-white font-medium mb-3">Name</label> <label className="block text-white font-medium mb-3">Name</label>
<input <input
type="text" type="text"
placeholder="Your name" placeholder="Dein Name"
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors" className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none"
/> />
</motion.div> </motion.div>
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<label className="block text-white font-medium mb-3">Email</label> <label className="block text-white font-medium mb-3">E-Mail</label>
<input <input
type="email" type="email"
placeholder="your.email@example.com" placeholder="deine.email@example.com"
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors" className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none"
/> />
</motion.div> </motion.div>
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<label className="block text-white font-medium mb-3">Message</label> <label className="block text-white font-medium mb-3">Nachricht</label>
<textarea <textarea
placeholder="Tell me about your project" placeholder="Erzähle mir von deinem Projekt"
rows={5} rows={5}
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors resize-none" className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none resize-none"
/> />
</motion.div> </motion.div>
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<Button variant="primary" size="lg" className="w-full"> <Button variant="primary" size="lg" className="w-full">
Send Message Nachricht senden
</Button> </Button>
</motion.div> </motion.div>
</motion.div> </motion.div>
@@ -102,10 +102,10 @@ export const ContactSection: React.FC = () => {
className="space-y-6" className="space-y-6"
> >
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<h3 className="text-2xl font-bold text-white mb-4">Let's Connect</h3> <h3 className="text-2xl font-bold text-white mb-4">Kontakt</h3>
<p className="text-gray-400"> <p className="text-gray-400">
I'm always open to discussing new projects, creative ideas, or opportunities Ich freue mich auf neue Projekte, kreative Ideen und spannende Kooperationen.
to be part of your vision. Feel free to reach out! Schreibe mir gerne eine Nachricht!
</p> </p>
</motion.div> </motion.div>
@@ -117,7 +117,7 @@ export const ContactSection: React.FC = () => {
<Mail className="text-green-500" size={24} /> <Mail className="text-green-500" size={24} />
</div> </div>
<div> <div>
<p className="text-gray-400 text-sm">Email</p> <p className="text-gray-400 text-sm">E-Mail</p>
<p className="text-white font-medium">robert@bretz.dev</p> <p className="text-white font-medium">robert@bretz.dev</p>
</div> </div>
</div> </div>
@@ -127,8 +127,8 @@ export const ContactSection: React.FC = () => {
<MapPin className="text-green-500" size={24} /> <MapPin className="text-green-500" size={24} />
</div> </div>
<div> <div>
<p className="text-gray-400 text-sm">Location</p> <p className="text-gray-400 text-sm">Standort</p>
<p className="text-white font-medium">Germany</p> <p className="text-white font-medium">Deutschland</p>
</div> </div>
</div> </div>
</div> </div>
@@ -136,29 +136,29 @@ export const ContactSection: React.FC = () => {
{/* Social Links */} {/* Social Links */}
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<p className="text-white font-medium mb-4">Connect with me</p> <p className="text-white font-medium mb-4">Folge mir</p>
<div className="flex gap-4"> <div className="flex gap-4">
<motion.a <motion.a
href="#" href="#"
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1, backgroundColor: 'rgba(34,197,94,0.2)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors" className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 text-green-500"
> >
<FaGithub size={24} /> <FaGithub size={24} />
</motion.a> </motion.a>
<motion.a <motion.a
href="#" href="#"
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1, backgroundColor: 'rgba(34,197,94,0.2)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors" className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 text-green-500"
> >
<FaLinkedin size={24} /> <FaLinkedin size={24} />
</motion.a> </motion.a>
<motion.a <motion.a
href="#" href="#"
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1, backgroundColor: 'rgba(34,197,94,0.2)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors" className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 text-green-500"
> >
<FaTwitter size={24} /> <FaTwitter size={24} />
</motion.a> </motion.a>
@@ -17,14 +17,14 @@ export const ExperienceSection: React.FC = () => {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6" className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
> >
<span className="text-green-400 text-sm font-medium">My Journey</span> <span className="text-green-400 text-sm font-medium">Mein Weg</span>
</motion.div> </motion.div>
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4"> <h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Professional Experience Berufserfahrung
</h2> </h2>
<p className="text-gray-400 text-lg"> <p className="text-gray-400 text-lg">
A timeline of my work experience and achievements Eine Übersicht meiner beruflichen Stationen und Erfolge
</p> </p>
</div> </div>
@@ -42,15 +42,16 @@ export const TimelineItem: React.FC<TimelineItemProps> = ({
className={`w-full md:w-5/12 ${isEven ? 'md:ml-0 md:text-right' : 'md:ml-auto'}`} className={`w-full md:w-5/12 ${isEven ? 'md:ml-0 md:text-right' : 'md:ml-auto'}`}
> >
<motion.div <motion.div
whileHover={{ scale: 1.02 }} whileHover={{ scale: 1.02, boxShadow: '0 10px 30px rgba(34,197,94,0.15)' }}
className="p-6 rounded-xl border border-green-500/30 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/60 transition-all duration-300" transition={{ type: 'spring', stiffness: 300 }}
className="p-6 rounded-xl border border-green-500/30 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm"
> >
{/* Year Badge */} {/* Year Badge */}
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="px-3 py-1 rounded-full bg-green-500/20 border border-green-500/50"> <div className="px-3 py-1 rounded-full bg-green-500/20 border border-green-500/50">
<span className="text-green-400 font-bold text-sm"> <span className="text-green-400 font-bold text-sm">
{experience.startYear} {experience.startYear}
{experience.endYear ? `-${experience.endYear}` : '-Present'} {experience.endYear ? `-${experience.endYear}` : '-Heute'}
</span> </span>
</div> </div>
</div> </div>
@@ -72,12 +73,14 @@ export const TimelineItem: React.FC<TimelineItemProps> = ({
{experience.technologies && ( {experience.technologies && (
<div className="flex flex-wrap gap-2 justify-end"> <div className="flex flex-wrap gap-2 justify-end">
{experience.technologies.map((tech) => ( {experience.technologies.map((tech) => (
<span <motion.span
key={tech} key={tech}
whileHover={{ scale: 1.05, backgroundColor: 'rgba(34,197,94,0.2)' }}
transition={{ type: 'spring', stiffness: 300 }}
className="text-xs px-2 py-1 rounded border border-green-500/30 text-green-400 bg-green-500/10" className="text-xs px-2 py-1 rounded border border-green-500/30 text-green-400 bg-green-500/10"
> >
{tech} {tech}
</span> </motion.span>
))} ))}
</div> </div>
)} )}
@@ -32,11 +32,11 @@ export const experienceData: Experience[] = [
}, },
{ {
id: '4', id: '4',
position: 'Creative Tech Consultant', position: 'Consultant für kreative Technologien',
startYear: 2018, startYear: 2018,
endYear: 2020, endYear: 2020,
description: description:
'Delivered immersive digital experiences using Unity, Blender, Godot and LaTeX documentation for creative product storytelling.', 'Entwicklung immersiver digitaler Erlebnisse mit Unity, Blender, Godot und LaTeX r kreative Produktstorytelling.',
technologies: ['Unity', 'Blender', 'Godot', 'LaTeX'], technologies: ['Unity', 'Blender', 'Godot', 'LaTeX'],
}, },
]; ];
@@ -9,8 +9,8 @@ export const HeroSection: React.FC = () => {
visible: { visible: {
opacity: 1, opacity: 1,
transition: { transition: {
staggerChildren: 0.2, staggerChildren: 0.15,
delayChildren: 0.3, delayChildren: 0.2,
}, },
}, },
}; };
@@ -20,10 +20,27 @@ export const HeroSection: React.FC = () => {
visible: { visible: {
opacity: 1, opacity: 1,
y: 0, y: 0,
transition: { duration: 0.6 },
},
};
const imageVariants = {
hidden: { opacity: 0, scale: 0.8 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.8 }, transition: { duration: 0.8 },
}, },
}; };
const borderVariants = {
hidden: { pathLength: 0 },
visible: {
pathLength: 1,
transition: { duration: 2, delay: 0.6 },
},
};
return ( return (
<section <section
id="home" id="home"
@@ -39,12 +56,16 @@ export const HeroSection: React.FC = () => {
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
className="max-w-5xl mx-auto text-center" className="max-w-7xl mx-auto w-full"
> >
{/* Grid Layout: Text left, Image right on desktop, stacked on mobile */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center">
{/* Text Content */}
<div className="text-center lg:text-left">
{/* Badge */} {/* Badge */}
<motion.div variants={itemVariants} className="mb-8"> <motion.div variants={itemVariants} className="mb-8">
<div className="inline-block px-4 py-2 border border-green-500/50 rounded-full bg-green-500/10 backdrop-blur-sm"> <div className="inline-block px-4 py-2 border border-green-500/50 rounded-full bg-green-500/10 backdrop-blur-sm">
<span className="text-green-400 text-sm font-medium flex items-center gap-2"> <span className="text-green-400 text-sm font-medium flex items-center gap-2 justify-center lg:justify-start">
<span className="w-2 h-2 bg-green-500 rounded-full"></span> <span className="w-2 h-2 bg-green-500 rounded-full"></span>
Softwareentwickler & Architektur-Enthusiast | Deutschland Softwareentwickler & Architektur-Enthusiast | Deutschland
</span> </span>
@@ -56,14 +77,14 @@ export const HeroSection: React.FC = () => {
<span className="text-white">Robert Bretz</span> <span className="text-white">Robert Bretz</span>
<br /> <br />
<span className="bg-gradient-to-r from-green-500 to-emerald-500 bg-clip-text text-transparent"> <span className="bg-gradient-to-r from-green-500 to-emerald-500 bg-clip-text text-transparent">
Crafting clean, performant digital products Saubere, performante digitale Lösungen
</span> </span>
</motion.h1> </motion.h1>
{/* Subtitle */} {/* Subtitle */}
<motion.p <motion.p
variants={itemVariants} variants={itemVariants}
className="text-gray-300 text-lg sm:text-xl max-w-2xl mx-auto mb-8" className="text-gray-300 text-lg sm:text-xl mb-8"
> >
Ich entwickle moderne Anwendungen mit React, TypeScript, Tailwind, .NET, NestJS und sauberer Architektur. Ich entwickle moderne Anwendungen mit React, TypeScript, Tailwind, .NET, NestJS und sauberer Architektur.
Mein Fokus liegt auf Performance, Vertical Slice Architecture, Docker-Deployments und skalierbaren Backend-Systemen. Mein Fokus liegt auf Performance, Vertical Slice Architecture, Docker-Deployments und skalierbaren Backend-Systemen.
@@ -72,24 +93,36 @@ export const HeroSection: React.FC = () => {
{/* Stats */} {/* Stats */}
<motion.div <motion.div
variants={itemVariants} variants={itemVariants}
className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-8 mb-12 max-w-2xl mx-auto" className="grid grid-cols-3 gap-3 sm:gap-6 mb-12"
> >
<div className="border border-green-500/30 rounded-lg p-5 bg-green-500/5 backdrop-blur-sm"> <motion.div
<div className="text-3xl font-bold text-green-500">4+</div> whileHover={{ scale: 1.05, y: -4 }}
<div className="text-gray-400 text-sm">Years Engineering</div> transition={{ type: 'spring', stiffness: 300 }}
</div> className="border border-green-500/30 rounded-lg p-4 sm:p-5 bg-green-500/5 backdrop-blur-sm"
<div className="border border-green-500/30 rounded-lg p-5 bg-green-500/5 backdrop-blur-sm"> >
<div className="text-3xl font-bold text-green-500">30+</div> <div className="text-2xl sm:text-3xl font-bold text-green-500">4+</div>
<div className="text-gray-400 text-sm">Delivered Solutions</div> <div className="text-gray-400 text-xs sm:text-sm">Jahre Erfahrung</div>
</div> </motion.div>
<div className="border border-green-500/30 rounded-lg p-5 bg-green-500/5 backdrop-blur-sm"> <motion.div
<div className="text-3xl font-bold text-green-500">99%</div> whileHover={{ scale: 1.05, y: -4 }}
<div className="text-gray-400 text-sm">Performance Focus</div> transition={{ type: 'spring', stiffness: 300 }}
</div> className="border border-green-500/30 rounded-lg p-4 sm:p-5 bg-green-500/5 backdrop-blur-sm"
>
<div className="text-2xl sm:text-3xl font-bold text-green-500">30+</div>
<div className="text-gray-400 text-xs sm:text-sm">Projekte umgesetzt</div>
</motion.div>
<motion.div
whileHover={{ scale: 1.05, y: -4 }}
transition={{ type: 'spring', stiffness: 300 }}
className="border border-green-500/30 rounded-lg p-4 sm:p-5 bg-green-500/5 backdrop-blur-sm"
>
<div className="text-2xl sm:text-3xl font-bold text-green-500">99%</div>
<div className="text-gray-400 text-xs sm:text-sm">Performance-Fokus</div>
</motion.div>
</motion.div> </motion.div>
{/* CTA Buttons */} {/* CTA Buttons */}
<motion.div variants={itemVariants} className="flex flex-col sm:flex-row gap-4 justify-center mb-12"> <motion.div variants={itemVariants} className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start mb-12">
<Button variant="primary" size="lg"> <Button variant="primary" size="lg">
Lass uns sprechen Lass uns sprechen
</Button> </Button>
@@ -97,13 +130,60 @@ export const HeroSection: React.FC = () => {
Projekte ansehen Projekte ansehen
</Button> </Button>
</motion.div> </motion.div>
</div>
{/* Profile Image - Right side on desktop, centered on mobile */}
<motion.div
variants={imageVariants}
className="flex justify-center lg:justify-end"
>
<div className="relative w-56 h-56 sm:w-72 sm:h-72 lg:w-96 lg:h-96">
{/* Animated green borders on sides */}
<svg
className="absolute inset-0 w-full h-full pointer-events-none"
viewBox="0 0 224 224"
xmlns="http://www.w3.org/2000/svg"
>
<motion.line
x1="10"
y1="0"
x2="10"
y2="224"
stroke="#22c55e"
strokeWidth="3"
variants={borderVariants}
initial="hidden"
animate="visible"
/>
<motion.line
x1="214"
y1="0"
x2="214"
y2="224"
stroke="#22c55e"
strokeWidth="3"
variants={borderVariants}
initial="hidden"
animate="visible"
/>
</svg>
{/* Profile Image */}
<img
src="/Bewerbungsfoto.png"
alt="Robert Bretz"
className="w-full h-full object-cover rounded-3xl shadow-2xl shadow-green-500/30"
/>
</div>
</motion.div>
</div>
{/* Scroll Indicator */} {/* Scroll Indicator */}
<motion.div <motion.div
variants={itemVariants} variants={itemVariants}
animate={{ y: [0, 12, 0] }} animate={{ y: [0, 12, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 2.5, repeat: Infinity, ease: 'easeInOut' }}
className="flex justify-center" className="flex justify-center mt-16"
> >
<ArrowDown className="text-green-500" size={32} /> <ArrowDown className="text-green-500" size={32} />
</motion.div> </motion.div>
@@ -1,16 +1,16 @@
import React, { useState } from 'react'; import { useState } from "react";
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from "framer-motion";
import { Menu, X } from 'lucide-react'; import { Menu, X } from "lucide-react";
import { useNavigation } from '../hooks/useNavigation'; import { useNavigation } from "../hooks/useNavigation";
import { useScrollToSection } from '../../../shared/hooks/useScrollToSection'; import { useScrollToSection } from "../../../shared/hooks/useScrollToSection";
import Button from '../../../shared/components/Button'; import Button from "../../../shared/components/Button";
const navItems = [ const navItems = [
{ id: 'home', label: 'Über mich' }, { id: "home", label: "Über mich" },
{ id: 'skills', label: 'Fähigkeiten' }, { id: "skills", label: "Fähigkeiten" },
{ id: 'projects', label: 'Projekte' }, { id: "projects", label: "Projekte" },
{ id: 'experience', label: 'Erfahrung' }, { id: "experience", label: "Erfahrung" },
{ id: 'contact', label: 'Kontakt' }, { id: "contact", label: "Kontakt" },
]; ];
export const Navigation: React.FC = () => { export const Navigation: React.FC = () => {
@@ -27,81 +27,98 @@ export const Navigation: React.FC = () => {
<motion.nav <motion.nav
initial={{ y: -120 }} initial={{ y: -120 }}
animate={{ y: 0 }} animate={{ y: 0 }}
transition={{ duration: 0.45, ease: 'easeOut' }} transition={{ duration: 0.45, ease: "easeOut" }}
className={`fixed top-0 w-full z-50 transition-all duration-300 ${ className={`fixed top-0 w-full z-50 ${
isScrolled isScrolled
? 'bg-black/90 backdrop-blur-xl border-b border-green-500/20 shadow-lg shadow-emerald-500/5' ? "bg-black/90 backdrop-blur-xl border-b border-green-500/20 shadow-lg shadow-emerald-500/5"
: 'bg-transparent' : "bg-transparent"
}`} }`}
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16"> <div className="flex justify-between items-center h-16">
{/* Logo */}
<motion.div <motion.div
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="text-2xl font-bold text-green-500 cursor-pointer" className="text-2xl font-bold text-green-500 cursor-pointer"
onClick={() => handleNavClick('home')} onClick={() => handleNavClick("home")}
> >
&lt;/&gt; Robert Bretz &lt;/&gt; Robert Bretz
</motion.div> </motion.div>
{/* Desktop Navigation */}
<div className="hidden md:flex gap-8"> <div className="hidden md:flex gap-8">
{navItems.map((item) => ( {navItems.map((item) => (
<motion.button <motion.button
key={item.id} key={item.id}
onClick={() => handleNavClick(item.id)} onClick={() => handleNavClick(item.id)}
className={`text-sm font-medium transition-colors duration-300 pb-2 border-b-2 ${ whileHover={activeSection !== item.id ? { color: '#ffffff' } : {}}
className={`text-sm font-medium pb-2 border-b-2 ${
activeSection === item.id activeSection === item.id
? 'text-green-500 border-green-500' ? "text-green-500 border-green-500"
: 'text-gray-300 border-transparent hover:text-white' : "text-gray-300 border-transparent"
}`} }`}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
> >
{item.label} {item.label}
</motion.button> </motion.button>
))} ))}
</div> </div>
{/* Desktop CTA + Mobile Toggle */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="hidden md:block"> <motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="hidden md:block"
>
<Button variant="primary" size="sm"> <Button variant="primary" size="sm">
Anfrage Anfrage
</Button> </Button>
</div> </motion.div>
<button {/* Mobile Menu Toggle */}
<motion.button
type="button" type="button"
aria-label="Open mobile menu" aria-label="Open mobile menu"
className="md:hidden p-2 rounded-full border border-green-500/20 bg-green-500/10 text-green-400 hover:text-white hover:border-green-500 transition-all" className="md:hidden p-2 rounded-full border border-green-500/20 bg-green-500/10 text-green-400"
onClick={() => setIsOpen((prev) => !prev)} onClick={() => setIsOpen((prev) => !prev)}
whileHover={{ scale: 1.1, borderColor: "#22c55e", color: "#ffffff" }}
whileTap={{ scale: 0.9 }}
> >
{isOpen ? <X size={22} /> : <Menu size={22} />} {isOpen ? <X size={22} /> : <Menu size={22} />}
</button> </motion.button>
</div> </div>
</div> </div>
</div> </div>
{/* Mobile Menu */}
<AnimatePresence> <AnimatePresence>
{isOpen && ( {isOpen && (
<motion.div <motion.div
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, y: -20 }} exit={{ opacity: 0, height: 0 }}
className="md:hidden bg-black/95 border-t border-green-500/20" transition={{ duration: 0.3, ease: "easeInOut" }}
className="md:hidden bg-black/95 border-t border-green-500/20 overflow-hidden"
> >
<div className="px-6 py-6 space-y-4"> <div className="px-6 py-6 space-y-2">
{navItems.map((item) => ( {navItems.map((item, index) => (
<button <motion.button
key={item.id} key={item.id}
onClick={() => handleNavClick(item.id)} onClick={() => handleNavClick(item.id)}
className={`w-full text-left text-lg font-medium px-4 py-3 rounded-xl transition-colors duration-300 ${ initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
whileHover={activeSection !== item.id ? { scale: 1.02, x: 4, backgroundColor: 'rgba(255,255,255,0.05)' } : { scale: 1.02, x: 4 }}
whileTap={{ scale: 0.98 }}
className={`w-full text-left text-lg font-medium px-4 py-3 rounded-xl ${
activeSection === item.id activeSection === item.id
? 'bg-green-500/15 text-green-400' ? "bg-green-500/15 text-green-400"
: 'text-gray-300 hover:bg-white/5 hover:text-white' : "text-gray-300"
}`} }`}
> >
{item.label} {item.label}
</button> </motion.button>
))} ))}
</div> </div>
</motion.div> </motion.div>
@@ -18,35 +18,43 @@ export const ProjectCard: React.FC<ProjectCardProps> = ({ project, index }) => {
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.45, delay: index * 0.08, ease: 'easeOut' }} transition={{ duration: 0.45, delay: index * 0.08, ease: 'easeOut' }}
whileHover={{ y: -8, scale: 1.01 }} whileHover={{ y: -8, scale: 1.01 }}
className="group rounded-3xl overflow-hidden border border-green-500/25 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/55 transition-all duration-300 shadow-[0_20px_60px_-40px_rgba(34,197,94,0.8)]" className="rounded-3xl overflow-hidden border border-green-500/25 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm"
> >
{/* Image */} {/* Image */}
<div className="relative h-64 overflow-hidden bg-gradient-to-br from-gray-900 to-black"> <div className="relative h-64 overflow-hidden bg-gradient-to-br from-gray-900 to-black">
<img <motion.img
src={project.image} src={project.image}
alt={project.title} alt={project.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500 ease-out" className="w-full h-full object-cover"
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.5 }}
/> />
<div className="absolute inset-0 bg-gradient-to-t from-black/85 to-transparent"></div> <div className="absolute inset-0 bg-gradient-to-t from-black/85 to-transparent"></div>
</div> </div>
{/* Content */} {/* Content */}
<div className="p-6"> <div className="p-6">
<div className="inline-flex items-center gap-2 mb-4 px-3 py-1 rounded-full bg-green-500/15 border border-green-500/25"> <motion.div
className="inline-flex items-center gap-2 mb-4 px-3 py-1 rounded-full bg-green-500/15 border border-green-500/25"
whileHover={{ scale: 1.05 }}
transition={{ type: 'spring', stiffness: 300 }}
>
<span className="text-green-400 text-xs font-semibold">{project.category}</span> <span className="text-green-400 text-xs font-semibold">{project.category}</span>
</div> </motion.div>
<h3 className="text-2xl font-bold text-white mb-3">{project.title}</h3> <h3 className="text-2xl font-bold text-white mb-3">{project.title}</h3>
<p className="text-gray-400 text-sm mb-5 line-clamp-3">{project.description}</p> <p className="text-gray-400 text-sm mb-5 line-clamp-3">{project.description}</p>
<div className="flex flex-wrap gap-2 mb-6"> <div className="flex flex-wrap gap-2 mb-6">
{project.tags.slice(0, 3).map((tag) => ( {project.tags.slice(0, 3).map((tag) => (
<span <motion.span
key={tag} key={tag}
whileHover={{ scale: 1.05, backgroundColor: 'rgba(34,197,94,0.2)' }}
transition={{ type: 'spring', stiffness: 300 }}
className="text-xs px-2 py-1 rounded-full border border-green-500/25 text-green-300 bg-white/5" className="text-xs px-2 py-1 rounded-full border border-green-500/25 text-green-300 bg-white/5"
> >
{tag} {tag}
</span> </motion.span>
))} ))}
{project.tags.length > 3 && ( {project.tags.length > 3 && (
<span className="text-xs px-2 py-1 rounded-full text-gray-400 bg-gray-900/40"> <span className="text-xs px-2 py-1 rounded-full text-gray-400 bg-gray-900/40">
@@ -61,9 +69,9 @@ export const ProjectCard: React.FC<ProjectCardProps> = ({ project, index }) => {
href={project.link} href={project.link}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05, backgroundColor: 'rgba(34,197,94,0.3)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-2xl bg-green-500/20 hover:bg-green-500/30 text-green-300 transition-colors text-sm font-medium" className="inline-flex items-center gap-2 px-4 py-2 rounded-2xl bg-green-500/20 text-green-300 text-sm font-medium"
> >
<ExternalLink size={16} /> <ExternalLink size={16} />
Live Live
@@ -74,9 +82,9 @@ export const ProjectCard: React.FC<ProjectCardProps> = ({ project, index }) => {
href={project.github} href={project.github}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05, borderColor: 'rgba(34,197,94,0.5)', color: '#ffffff' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-2xl border border-green-500/25 hover:border-green-500/50 text-gray-300 hover:text-white transition-colors text-sm font-medium" className="inline-flex items-center gap-2 px-4 py-2 rounded-2xl border border-green-500/25 text-gray-300 text-sm font-medium"
> >
<FaGithub size={16} /> <FaGithub size={16} />
Code Code
@@ -14,7 +14,7 @@ export const ProjectsSection: React.FC = () => {
const filteredProjects = projectsData.filter( const filteredProjects = projectsData.filter(
(project: Project) => (project: Project) =>
selectedCategory === 'All' || project.category === selectedCategory selectedCategory === 'Alle' || project.category === selectedCategory
); );
return ( return (
@@ -53,10 +53,19 @@ export const ProjectsSection: React.FC = () => {
onClick={() => setSelectedCategory(category)} onClick={() => setSelectedCategory(category)}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className={`px-6 py-2 rounded-full font-medium transition-all duration-300 ${ animate={{
backgroundColor:
selectedCategory === category ? 'rgba(34,197,94,1)' : 'transparent',
borderColor:
selectedCategory === category selectedCategory === category
? 'bg-green-500 text-black' ? 'rgba(34,197,94,1)'
: 'border border-green-500/30 text-gray-300 hover:border-green-500/60' : 'rgba(34,197,94,0.3)',
}}
transition={{ duration: 0.2 }}
className={`px-6 py-2 rounded-full font-medium ${
selectedCategory === category
? 'text-black'
: 'border border-green-500/30 text-gray-300'
}`} }`}
> >
{category} {category}
@@ -32,13 +32,17 @@ export const SkillCard: React.FC<SkillCardProps> = ({ skill, index }) => {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.45, delay: index * 0.08, ease: 'easeOut' }} transition={{ duration: 0.45, delay: index * 0.08, ease: 'easeOut' }}
whileHover={{ scale: 1.025, y: -6 }} whileHover={{ scale: 1.025, y: -6, boxShadow: '0 20px 60px -40px rgba(34,197,94,0.75)' }}
className="p-6 rounded-3xl border border-green-500/25 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/60 transition-all duration-300 shadow-[0_20px_60px_-40px_rgba(34,197,94,0.75)]" className="p-6 rounded-3xl border border-green-500/25 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm"
> >
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div className="p-3 rounded-2xl bg-green-500/15"> <motion.div
className="p-3 rounded-2xl bg-green-500/15"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ type: 'spring', stiffness: 300 }}
>
{IconComponent && <IconComponent className="text-green-500" size={24} />} {IconComponent && <IconComponent className="text-green-500" size={24} />}
</div> </motion.div>
<span className={`text-xs font-semibold px-3 py-1 rounded-full ${levelBg[skill.level]}`}> <span className={`text-xs font-semibold px-3 py-1 rounded-full ${levelBg[skill.level]}`}>
<span className={levelColors[skill.level]}>{skill.level}</span> <span className={levelColors[skill.level]}>{skill.level}</span>
</span> </span>
@@ -48,8 +52,8 @@ export const SkillCard: React.FC<SkillCardProps> = ({ skill, index }) => {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Experience</span> <span className="text-gray-400">Erfahrung</span>
<span className="text-green-400 font-medium">{skill.years}+ yrs</span> <span className="text-green-400 font-medium">{skill.years}+ Jahre</span>
</div> </div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden"> <div className="h-2 bg-gray-800 rounded-full overflow-hidden">
@@ -27,7 +27,7 @@ export const SkillsSection: React.FC = () => {
]; ];
const filteredSkills = skillsData.filter( const filteredSkills = skillsData.filter(
(skill) => selectedCategory === 'All' || skill.category === selectedCategory (skill) => selectedCategory === 'Alle' || skill.category === selectedCategory
); );
return ( return (
@@ -66,10 +66,19 @@ export const SkillsSection: React.FC = () => {
onClick={() => setSelectedCategory(category)} onClick={() => setSelectedCategory(category)}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className={`px-6 py-2 rounded-full font-medium transition-all duration-300 ${ animate={{
backgroundColor:
selectedCategory === category ? 'rgba(34,197,94,1)' : 'transparent',
borderColor:
selectedCategory === category selectedCategory === category
? 'bg-green-500 text-black' ? 'rgba(34,197,94,1)'
: 'border border-green-500/30 text-gray-300 hover:border-green-500/60' : 'rgba(34,197,94,0.3)',
}}
transition={{ duration: 0.2 }}
className={`px-6 py-2 rounded-full font-medium ${
selectedCategory === category
? 'text-black'
: 'border border-green-500/30 text-gray-300'
}`} }`}
> >
{category} {category}
@@ -11,8 +11,10 @@ import {
Terminal, Terminal,
Box, Box,
FileText, FileText,
Gamepad, Gamepad2,
ShieldCheck, ShieldCheck,
Smartphone,
Cpu,
} from 'lucide-react'; } from 'lucide-react';
import type { Skill } from '../../../shared/types'; import type { Skill } from '../../../shared/types';
@@ -34,11 +36,11 @@ export const iconMap = {
'Linux': Terminal, 'Linux': Terminal,
'Clean Architecture': Layers, 'Clean Architecture': Layers,
'Vertical Slice Architecture': Layers, 'Vertical Slice Architecture': Layers,
'Design Patterns': Layout, 'Design Patterns': Cpu,
'Figma': PenTool, 'Figma': PenTool,
'Unity': Gamepad, 'Unity': Gamepad2,
'Blender': Package, 'Blender': Package,
'Godot': Package, 'Godot': Gamepad2,
'LaTeX': FileText, 'LaTeX': FileText,
}; };
@@ -54,7 +56,7 @@ export const skillsData: Array<Omit<Skill, 'icon'> & { iconName: keyof typeof ic
name: 'Vue.js', name: 'Vue.js',
iconName: 'Vue.js', iconName: 'Vue.js',
level: 'Advanced', level: 'Advanced',
years: 3, years: 0.5,
category: 'Frontend', category: 'Frontend',
}, },
{ {
+4 -6
View File
@@ -16,7 +16,7 @@ const Button: React.FC<ButtonProps> = ({
size = 'md', size = 'md',
className = '', className = '',
}) => { }) => {
const baseClasses = 'font-semibold rounded-full transition-all duration-300'; const baseClasses = 'font-semibold rounded-full';
const sizeClasses = { const sizeClasses = {
sm: 'px-4 py-2 text-sm', sm: 'px-4 py-2 text-sm',
@@ -25,15 +25,13 @@ const Button: React.FC<ButtonProps> = ({
}; };
const variantClasses = { const variantClasses = {
primary: primary: 'bg-green-500 text-black',
'bg-green-500 hover:bg-green-600 text-black shadow-lg hover:shadow-green-500/50', secondary: 'border-2 border-green-500 text-green-500',
secondary:
'border-2 border-green-500 text-green-500 hover:bg-green-500/10',
}; };
return ( return (
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05, boxShadow: '0 10px 25px rgba(34,197,94,0.2)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={onClick} onClick={onClick}
className={`${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]} ${className}`} className={`${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]} ${className}`}
+18 -9
View File
@@ -27,13 +27,22 @@ const Footer: React.FC = () => {
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
> >
<h4 className="text-white font-semibold mb-4">Quick Links</h4> <h4 className="text-white font-semibold mb-4">Schnellzugriff</h4>
<ul className="space-y-2"> <ul className="space-y-2">
{['About', 'Skills', 'Projects', 'Contact'].map((link) => ( {[
<li key={link}> { label: 'Über mich', href: '#home' },
<a href={`#${link.toLowerCase()}`} className="text-gray-400 hover:text-green-400 transition-colors"> { label: 'Fähigkeiten', href: '#skills' },
{link} { label: 'Projekte', href: '#projects' },
</a> { label: 'Kontakt', href: '#contact' },
].map((link) => (
<li key={link.label}>
<motion.a
href={link.href}
whileHover={{ color: '#22c55e' }}
className="text-gray-400"
>
{link.label}
</motion.a>
</li> </li>
))} ))}
</ul> </ul>
@@ -46,9 +55,9 @@ const Footer: React.FC = () => {
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
> >
<h4 className="text-white font-semibold mb-4">Services</h4> <h4 className="text-white font-semibold mb-4">Leistungen</h4>
<ul className="space-y-2"> <ul className="space-y-2">
{['Frontend Development', 'UI/UX Design', 'Full Stack Dev'].map((service) => ( {['Frontend-Entwicklung', 'UI/UX-Design', 'Fullstack-Entwicklung'].map((service) => (
<li key={service}> <li key={service}>
<span className="text-gray-400">{service}</span> <span className="text-gray-400">{service}</span>
</li> </li>
@@ -83,7 +92,7 @@ const Footer: React.FC = () => {
© {currentYear} Robert Bretz. All rights reserved. © {currentYear} Robert Bretz. All rights reserved.
</p> </p>
<p className="text-gray-500 text-sm mt-4 md:mt-0"> <p className="text-gray-500 text-sm mt-4 md:mt-0">
Built with 💚 using React & Tailwind CSS Erstellt mit 💚 in React & Tailwind CSS
</p> </p>
</div> </div>
</div> </div>