Add complete bewerbung Next.js application
- Add all Next.js application files - Include components, app structure, and public assets - Remove empty bewerbung placeholder folder - Update .gitignore for Next.js project 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
export default function Contact() {
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="contact" className="relative py-20 px-4 sm:px-6 lg:px-8 bg-bg-dark text-white">
|
||||
{/* Scroll to Top Button */}
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className="absolute -top-6 left-1/2 -translate-x-1/2 w-12 h-12 bg-primary rounded-full flex items-center justify-center hover:bg-primary-dark transition-all shadow-lg group"
|
||||
aria-label="Nach oben scrollen"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 text-white group-hover:-translate-y-1 transition-transform"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M5 10l7-7m0 0l7 7m-7-7v18"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-center mb-16 relative inline-block w-full after:content-[''] after:block after:w-12 after:h-1 after:bg-primary after:mt-6 after:mx-auto">
|
||||
Jetzt Kontakt aufnehmen
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{/* Address */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-lg p-8 text-center hover:bg-white/10 transition-all hover:transform hover:scale-105">
|
||||
<div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Adresse</h3>
|
||||
<p className="text-white/80">
|
||||
Rosenstraße 4<br />
|
||||
91564 Neuendettelsau
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Phone */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-lg p-8 text-center hover:bg-white/10 transition-all hover:transform hover:scale-105">
|
||||
<div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Telefon</h3>
|
||||
<a href="tel:+4915732469960" className="text-white/80 hover:text-primary transition-colors">
|
||||
+49 157 32469960
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-lg p-8 text-center hover:bg-white/10 transition-all hover:transform hover:scale-105">
|
||||
<div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Email</h3>
|
||||
<a
|
||||
href="mailto:froehlich.sebastian@gmail.com?subject=Antwort - Ihre Bewerbung"
|
||||
className="text-white/80 hover:text-primary transition-colors break-all"
|
||||
>
|
||||
froehlich.sebastian@gmail.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
interface DownloadSectionProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
buttonText: string;
|
||||
href: string;
|
||||
backgroundImage: string;
|
||||
}
|
||||
|
||||
export default function DownloadSection({
|
||||
title,
|
||||
subtitle,
|
||||
buttonText,
|
||||
href,
|
||||
backgroundImage,
|
||||
}: DownloadSectionProps) {
|
||||
return (
|
||||
<section
|
||||
className="relative py-24 px-4 sm:px-6 lg:px-8 bg-cover bg-center bg-fixed"
|
||||
style={{ backgroundImage: `url(${backgroundImage})` }}
|
||||
>
|
||||
{/* Overlay */}
|
||||
<div className="absolute inset-0 bg-black/60" />
|
||||
|
||||
<div className="relative z-10 max-w-4xl mx-auto text-center">
|
||||
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-xl sm:text-2xl text-white/90 mb-8">
|
||||
{subtitle}
|
||||
</p>
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block px-10 py-4 bg-primary text-white font-bold text-lg rounded-md hover:bg-primary-dark transition-all transform hover:scale-105 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface Job {
|
||||
id: string;
|
||||
title: string;
|
||||
company: string;
|
||||
location: string;
|
||||
period: string;
|
||||
description: string;
|
||||
responsibilities: string[];
|
||||
}
|
||||
|
||||
const jobs: Job[] = [
|
||||
{
|
||||
id: 'job1',
|
||||
title: 'Technischer Product Owner',
|
||||
company: 'IT im Bereich Finanzen und Banken',
|
||||
location: 'Regulatory & Risk, Nürnberg',
|
||||
period: 'Juni 2021 - heute',
|
||||
description: 'Technischer Product Owner in einem Nürnberger Unternehmen, welches die komplette Banken IT steuert.',
|
||||
responsibilities: [
|
||||
'Sicherstellung des Betriebs',
|
||||
'Planung und Priorisierung der Ressourcen & Aufgaben',
|
||||
'Technische Kommunikation und Architektur',
|
||||
'Migration der Infrastruktur K8s on prem & Cloud',
|
||||
'Aufbau neuer Services',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'job2',
|
||||
title: 'Product Owner',
|
||||
company: 'Mobile Applikationen Unternehmen',
|
||||
location: 'Nürnberg',
|
||||
period: 'Februar 2019 - März 2020',
|
||||
description: 'Product Owner in einem Nürnberger Unternehmen, welches mobile Applikationen zur Digitalisierung von Prozessen herstellt.',
|
||||
responsibilities: [
|
||||
'Abklärung der Anforderungen zwischen Stakeholdern und Entwicklern',
|
||||
'Planung und Priorisierung der Ressourcen & Aufgaben',
|
||||
'Technische Kommunikation und Architektur',
|
||||
'Migration der bestehenden Infrastruktur in die MagentaCloud (Kubernetes)',
|
||||
'Weiterentwicklung des Produktes',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'job3',
|
||||
title: 'Leiter IT',
|
||||
company: 'Unternehmen im Bereich Fahrzeugtechnik',
|
||||
location: 'Georgensgmünd',
|
||||
period: 'März 2014 - Januar 2019',
|
||||
description: 'Leiter einer IT-Abteilung in einem mittelfränkischen Unternehmen aus dem Bereich Fahrzeugtechnik.',
|
||||
responsibilities: [
|
||||
'Programmierung & Weiterentwicklung der Online Shops',
|
||||
'Anbindung der Warenwirtschaft',
|
||||
'Aufbau und Entwicklung eines Intranets',
|
||||
'Architektur der Datenbanken',
|
||||
'API Schnittstellenprogrammierung',
|
||||
'Migration/Neuaufbau und Entwicklung der Linux Server',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'job4',
|
||||
title: 'Marketing Manager',
|
||||
company: 'Trustec Energy GmbH',
|
||||
location: 'Solarbranche, Mittelfranken',
|
||||
period: 'Januar 2010 - März 2014',
|
||||
description: 'Marketing Manager in einem Unternehmen der Solarbranche mit Verantwortung für die komplette digitale Präsenz und Marketing-Kommunikation.',
|
||||
responsibilities: [
|
||||
'Aufbau, Programmierung und Pflege der Firmenwebseiten (trustec-energy.eu, mhm-marketing.com) mit Joomla CMS',
|
||||
'Entwicklung und Programmierung neuer CMS-Inhaltstypen und Module',
|
||||
'E-Mail Marketing: Erstellung, Versand und Erfolgsmessung von Newslettern und Messe-Respondern',
|
||||
'Print Design: Kataloge, Preislisten, Flyer, Rollups und Messestände (InDesign, Photoshop, Illustrator)',
|
||||
'Erstellung von 3D Produkt Mock-ups und Landing Pages',
|
||||
'Web Analytics mit Google Analytics und Piwik',
|
||||
'Koordination externer Dienstleister (Grafiker, Übersetzer, Messebauer)',
|
||||
'IT-Support: Betreuung Windows Server 2008, CRM-Software und Telefonanlage',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'job5',
|
||||
title: 'Freelancer Web Development & Design',
|
||||
company: 'Selbstständig',
|
||||
location: 'Verschiedene Branchen',
|
||||
period: 'Januar 2000 - Dezember 2010',
|
||||
description: 'Über 10 Jahre selbstständig für Unternehmenskunden aus unterschiedlichen Branchen tätig, mit Fokus auf webbasierte Lösungen.',
|
||||
responsibilities: [
|
||||
'Entwicklung von Präsentations- und Buchungssystemen',
|
||||
'Programmierung von CMS-Systemen und Content-Management-Lösungen',
|
||||
'Aufbau und Entwicklung von E-Commerce Shops',
|
||||
'Webdesign und Frontend-Entwicklung (HTML, CSS, JavaScript, Flash/Actionscript)',
|
||||
'Kundenberatung und Anforderungsanalyse',
|
||||
'Print Design und Corporate Design Entwicklung',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Experience() {
|
||||
const [activeJob, setActiveJob] = useState(0);
|
||||
|
||||
return (
|
||||
<section id="experience" className="py-20 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-text text-center mb-16 relative inline-block w-full after:content-[''] after:block after:w-12 after:h-1 after:bg-primary after:mt-6 after:mx-auto">
|
||||
Berufserfahrung
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Job Tabs - Jetzt mit besserer Mobile-Darstellung */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
{jobs.map((job, index) => (
|
||||
<button
|
||||
key={job.id}
|
||||
onClick={() => setActiveJob(index)}
|
||||
className={`text-left p-4 sm:p-5 rounded-lg transition-all duration-300 ${
|
||||
activeJob === index
|
||||
? 'bg-primary text-white shadow-lg scale-105'
|
||||
: 'bg-gray-100 text-text hover:bg-gray-200 hover:shadow-md'
|
||||
}`}
|
||||
>
|
||||
<h3 className="font-bold text-sm sm:text-base lg:text-lg mb-1">
|
||||
{job.title}
|
||||
</h3>
|
||||
<p className={`text-xs sm:text-sm ${activeJob === index ? 'text-white/80' : 'text-text-light'}`}>
|
||||
{job.company}
|
||||
</p>
|
||||
<p className={`text-xs mt-1 font-semibold ${activeJob === index ? 'text-white/90' : 'text-primary'}`}>
|
||||
{job.period.split(' - ')[0]}
|
||||
</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Job Details */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-bg-light rounded-lg p-6 sm:p-8 shadow-md hover:shadow-xl transition-shadow">
|
||||
{/* Header */}
|
||||
<div className="mb-6 pb-6 border-b border-gray-200">
|
||||
<h3 className="text-xl sm:text-2xl lg:text-3xl font-bold text-text mb-3">
|
||||
{jobs[activeJob].title}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2 mb-2">
|
||||
<p className="text-base sm:text-lg font-semibold text-text-light flex items-center">
|
||||
<svg className="w-5 h-5 mr-2 text-primary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
{jobs[activeJob].company}
|
||||
</p>
|
||||
<p className="text-sm sm:text-base text-text-light flex items-center">
|
||||
<svg className="w-5 h-5 mr-2 text-primary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
{jobs[activeJob].location}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-primary font-bold text-base sm:text-lg flex items-center">
|
||||
<svg className="w-5 h-5 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{jobs[activeJob].period}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-text-light mb-6 text-sm sm:text-base leading-relaxed">
|
||||
{jobs[activeJob].description}
|
||||
</p>
|
||||
|
||||
{/* Responsibilities */}
|
||||
<h4 className="font-bold text-text mb-4 text-base sm:text-lg flex items-center">
|
||||
<svg className="w-5 h-5 mr-2 text-primary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
||||
</svg>
|
||||
Verantwortlichkeiten:
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{jobs[activeJob].responsibilities.map((resp, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="flex items-start group hover:translate-x-1 transition-transform"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 text-primary mt-0.5 mr-3 flex-shrink-0 group-hover:scale-110 transition-transform"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-text-light leading-relaxed text-sm sm:text-base">{resp}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Tags for quick tech overview */}
|
||||
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeJob === 0 && (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Kubernetes</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Cloud Migration</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Systemarchitektur</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Financial IT</span>
|
||||
</>
|
||||
)}
|
||||
{activeJob === 1 && (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Kubernetes</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Mobile Apps</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Stakeholder Management</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Digitalisierung</span>
|
||||
</>
|
||||
)}
|
||||
{activeJob === 2 && (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">E-Commerce</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Laravel</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Vue.js</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">API Development</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Team Leadership</span>
|
||||
</>
|
||||
)}
|
||||
{activeJob === 3 && (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Joomla</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">E-Mail Marketing</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Adobe Suite</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Google Analytics</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">3D Design</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Print Design</span>
|
||||
</>
|
||||
)}
|
||||
{activeJob === 4 && (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">HTML/CSS/JS</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">CMS Development</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">E-Commerce</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Flash/Actionscript</span>
|
||||
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">Webdesign</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-bg-dark text-white py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<div className="text-center md:text-left">
|
||||
<h3 className="text-xl font-bold mb-1">Sebastian Fröhlich</h3>
|
||||
<p className="text-white/60 text-sm">Full-Stack Developer, technischer PO, Team Leader</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center md:text-right">
|
||||
<p className="text-white/80 font-semibold mb-2">Nicht alles hat ein Ende...</p>
|
||||
<a
|
||||
href="mailto:froehlich.sebastian@gmail.com?subject=Antwort - Ihre Bewerbung"
|
||||
className="inline-block px-6 py-2 bg-primary text-white font-semibold rounded-lg hover:bg-primary-dark transition-colors"
|
||||
>
|
||||
Jetzt Kontakt aufnehmen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-white/10 text-center text-white/60 text-sm">
|
||||
<p>© {new Date().getFullYear()} Sebastian Fröhlich. Alle Rechte vorbehalten.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Hero() {
|
||||
const [scrollY, setScrollY] = useState(0);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setScrollY(window.scrollY);
|
||||
const handleResize = () => setIsMobile(window.innerWidth < 768);
|
||||
|
||||
handleResize(); // Initial check
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="hero"
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
{/* Background Image mit Next.js Image */}
|
||||
<div className="absolute inset-0 w-full h-full">
|
||||
<Image
|
||||
src={isMobile ? "/img/bg-ich-mobile.jpg" : "/img/bg-ich-2.png"}
|
||||
alt="Hero Background"
|
||||
fill
|
||||
priority
|
||||
quality={90}
|
||||
className="object-cover object-center"
|
||||
style={{
|
||||
transform: `translateY(${scrollY * 0.5}px)`,
|
||||
}}
|
||||
/>
|
||||
{/* Overlay für bessere Lesbarkeit */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-900/80 via-slate-800/70 to-slate-900/80" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center px-4 sm:px-6 lg:px-8">
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold text-white tracking-wider drop-shadow-2xl">
|
||||
be<span className="text-primary">Werbung</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl sm:text-2xl text-slate-300 max-w-2xl mx-auto drop-shadow-lg">
|
||||
Die Bewerberwebseite von <br />
|
||||
<em className="text-primary font-semibold">Sebastian Fröhlich</em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-bounce">
|
||||
<button
|
||||
onClick={() => document.getElementById('intro')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
className="w-12 h-12 rounded-full bg-primary/20 backdrop-blur-sm border-2 border-primary flex items-center justify-center hover:bg-primary/30 transition-all group"
|
||||
aria-label="Scroll nach unten"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 text-primary group-hover:translate-y-1 transition-transform"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function Hero() {
|
||||
const [scrollY, setScrollY] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setScrollY(window.scrollY);
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="hero"
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
{/* Background Layer 1 - Wiederholendes Muster - passt sich an Bildschirmhöhe an */}
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full"
|
||||
style={{
|
||||
backgroundImage: 'url(/img/bg-ich-2025-background.jpg)',
|
||||
backgroundRepeat: 'repeat',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundPosition: 'center',
|
||||
transform: `translateY(${scrollY * 0.3}px)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Background Layer 2 - Portrait rechts (PNG mit Transparenz) */}
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full"
|
||||
style={{
|
||||
backgroundImage: 'url(/img/bg-ich-2025-foreground.png)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundPosition: 'right center',
|
||||
transform: `translateY(${scrollY * 0.5}px)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Overlay für bessere Lesbarkeit */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-slate-900/80 via-slate-900/50 to-transparent sm:from-slate-900/70 sm:via-slate-900/40 lg:from-slate-900/60 lg:via-slate-900/20" />
|
||||
|
||||
{/* Content - Responsive Positionierung */}
|
||||
<div className="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-full sm:max-w-2xl lg:max-w-xl space-y-6 animate-fade-in">
|
||||
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-white tracking-wider drop-shadow-2xl">
|
||||
be<span className="text-primary">Werbung</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg sm:text-xl md:text-2xl text-slate-300 drop-shadow-lg">
|
||||
Die Bewerberwebseite von <br />
|
||||
<em className="text-primary font-semibold">Sebastian Fröhlich</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-bounce z-20">
|
||||
<button
|
||||
onClick={() => document.getElementById('intro')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
className="w-12 h-12 rounded-full bg-primary/20 backdrop-blur-sm border-2 border-primary flex items-center justify-center hover:bg-primary/30 transition-all group"
|
||||
aria-label="Scroll nach unten"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 text-primary group-hover:translate-y-1 transition-transform"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function Intro() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = ref.current;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (currentRef) {
|
||||
observer.observe(currentRef);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef) {
|
||||
observer.unobserve(currentRef);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const highlights = [
|
||||
{ value: '20+', label: 'Jahre Erfahrung' },
|
||||
{ value: '10+', label: 'Jahre Freelancing' },
|
||||
{ value: '9+', label: 'Mitarbeiter geführt' },
|
||||
{ value: '3', label: 'Produkte verantwortet' },
|
||||
];
|
||||
|
||||
const skills = [
|
||||
'Product Owner',
|
||||
'Technical Project Manager',
|
||||
'Laravel',
|
||||
'Vue.js',
|
||||
'PHP',
|
||||
'JavaScript',
|
||||
'MySQL',
|
||||
'MSSQL',
|
||||
'Systemarchitektur',
|
||||
'User Experience',
|
||||
'Team Leadership',
|
||||
'Compliance',
|
||||
];
|
||||
|
||||
const timeline = [
|
||||
{ year: '2021 - heute', company: 'Sopra Financial Technology GmbH', role: 'Technical Product Owner' },
|
||||
{ year: '2019 - 2020', company: 'APPsolute Mobility', role: 'Product Owner & Technical Project Manager' },
|
||||
{ year: '2014 - 2019', company: 'SCC Fahrzeugtechnik GmbH', role: 'Leiter IT Abteilung' },
|
||||
{ year: '2010 - 2014', company: 'Trustec Energy GmbH', role: 'Marketing Manager' },
|
||||
{ year: '2000 - 2010', company: 'Freelancer', role: 'Web Development & Design' },
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
id="intro"
|
||||
ref={ref}
|
||||
className={`py-20 px-4 sm:px-6 lg:px-8 bg-white transition-opacity duration-1000 ${
|
||||
isVisible ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-text mb-12 text-center relative inline-block w-full after:content-[''] after:block after:w-12 after:h-1 after:bg-primary after:mt-6 after:mx-auto">
|
||||
Über mich
|
||||
</h2>
|
||||
|
||||
{/* Highlights Box */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-12">
|
||||
{highlights.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg p-6 text-center border border-primary/20 hover:border-primary/40 transition-all hover:shadow-lg ${
|
||||
isVisible ? 'animate-fade-in' : 'opacity-0'
|
||||
}`}
|
||||
style={{ animationDelay: `${index * 100}ms` }}
|
||||
>
|
||||
<div className="text-3xl sm:text-4xl font-bold text-primary mb-2">
|
||||
{item.value}
|
||||
</div>
|
||||
<div className="text-sm text-text-light font-semibold">
|
||||
{item.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main Text */}
|
||||
<div className="max-w-4xl mx-auto space-y-6 text-text-light leading-relaxed text-base sm:text-lg mb-12">
|
||||
<p>
|
||||
Mein Name ist Sebastian Fröhlich, ich bin 47 Jahre alt und lebe zusammen mit meiner
|
||||
Familie in Neuendettelsau im Landkreis Ansbach.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Von der Diskette zur Cloud durfte ich diesen spannenden und rasanten technischen
|
||||
Wandel miterleben. Besonders die Themen{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
redundante Datenerfassung, Systemarchitektur und User Experience
|
||||
</strong>{' '}
|
||||
haben mich schon immer besonders fasziniert.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Bereits während meiner Berufsausbildung wurde mir die Verantwortung für den
|
||||
Firmenserver (Netware) übertragen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Für die A-Socks GmbH, einem Unternehmen aus der Textilbranche entwickelte ich einen{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
Shop (HTML/ Javascript/ Actionscript - Flash)
|
||||
</strong>, gestaltete die Verkaufsverpackungen und programmierte ein automatisiertes
|
||||
EAN Vergabe System.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Als Freelancer war ich über 10 Jahre für Unternehmenskunden aus unterschiedlichen
|
||||
Branchen tätig. Mein Fokus lag dabei auf webbasierten Lösungen wie z.B.{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
Präsentations- und Buchungssystemen, CMS-Systemen und Internetshops
|
||||
</strong>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Im Jahr 2010 erhielt ich die Gelegenheit meine Kompetenzen als Marketing Manager für
|
||||
die Trustec Energy GmbH in der Solarbranche einzusetzen. Nach 4 Jahren erhielt ich eine
|
||||
betriebsbedingte Kündigung und wechselte 2014 zur SCC Fahrzeugtechnik GmbH.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Als{' '}
|
||||
<strong className="text-text font-semibold">Leiter der IT Abteilung</strong> war ich
|
||||
hier fachlich und disziplinarisch für 6 Mitarbeiter und 3 Auszubildende verantwortlich.
|
||||
Darüber hinaus entwickelte und programmierte ich ein eigenes Firmen-Intranet{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
(PHP Framework: Laravel, Javascript/ Vue.js, MySql/ MSSQL)
|
||||
</strong>, wartete und migrierte die Server und sorgte für ein funktionierendes Backup-
|
||||
und Sicherheitssystem{' '}
|
||||
<strong className="text-text font-semibold">(Master-Master Replication, BashScript)</strong>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Die nächste Karrierestufe erreichte ich mit meinem Wechsel zu einem Startupunternehmen,
|
||||
wo ich als{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
Product Owner & Technical Project Manager
|
||||
</strong>{' '}
|
||||
für die Firma "APPsolute Mobility" tätig war. Leider konnte das Startup die gesetzten
|
||||
Ziele nicht erreichen, weswegen ich 1 Jahr später zu meinem jetzigen Arbeitgeber wechselte.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Bei der{' '}
|
||||
<strong className="text-text font-semibold">Sopra Financial Technology GmbH</strong> bin
|
||||
ich seit 2021 als{' '}
|
||||
<strong className="text-text font-semibold">Technical Product Owner</strong> im
|
||||
Bereich Regulatory & Risk tätig. Ich verantworte{' '}
|
||||
<strong className="text-text font-semibold">3 Fintech-Produkte</strong> im{' '}
|
||||
<strong className="text-text font-semibold">Bankenmeldewesen</strong> und stelle
|
||||
deren reibungslosen Betrieb unter strikten{' '}
|
||||
<strong className="text-text font-semibold">regulatorischen Vorgaben</strong> sicher.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Zu meinen Kernaufgaben gehören die{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
technische Projektleitung komplexer Migrationen
|
||||
</strong>{' '}
|
||||
(z.B. Kubernetes-Migration ohne Ausfallzeiten), die Steuerung{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
internationaler Teams (inkl. Nearshore)
|
||||
</strong>, Code Reviews sowie die aktive Teilnahme an{' '}
|
||||
<strong className="text-text font-semibold">Cost-Savings-Programmen</strong>.
|
||||
Darüber hinaus arbeite ich in den Bereichen{' '}
|
||||
<strong className="text-text font-semibold">
|
||||
Managed Application & Cloud Service
|
||||
</strong>, wo ich neue Produkte architektonisch begleite und bei
|
||||
Bieterverfahren meine Expertise einbringe.
|
||||
</p>
|
||||
|
||||
<p className="pt-4 border-t border-gray-200">
|
||||
Mit dieser Präsentation möchte ich die Gelegenheit nutzen, mich als motivierten und
|
||||
kompetenten Bewerber bei Ihrem Unternehmen vorzustellen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Nachfolgend können Sie sich einen Eindruck über mich und meine abgeschlossenen Projekte{' '}
|
||||
<a
|
||||
href="#portfolio"
|
||||
className="text-primary hover:text-primary-dark font-semibold underline underline-offset-4 transition-colors"
|
||||
>
|
||||
hier
|
||||
</a>{' '}
|
||||
verschaffen. Gerne überzeuge ich Sie auch in einem persönlichen Gespräch von mir.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="mb-12">
|
||||
<h3 className="text-2xl font-bold text-text mb-8 text-center">
|
||||
Karriere-Timeline
|
||||
</h3>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{timeline.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`relative pl-8 pb-8 border-l-2 border-primary/30 last:border-transparent ${
|
||||
isVisible ? 'animate-fade-in' : 'opacity-0'
|
||||
}`}
|
||||
style={{ animationDelay: `${(index + 4) * 100}ms` }}
|
||||
>
|
||||
{/* Timeline dot */}
|
||||
<div className="absolute -left-[9px] top-0 w-4 h-4 rounded-full bg-primary border-4 border-white shadow-md" />
|
||||
|
||||
<div className="bg-bg-light rounded-lg p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start mb-2">
|
||||
<h4 className="text-lg font-bold text-text">{item.role}</h4>
|
||||
<span className="text-sm font-semibold text-primary whitespace-nowrap">
|
||||
{item.year}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-text-light">{item.company}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skills Tags */}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h3 className="text-2xl font-bold text-text mb-6 text-center">
|
||||
Kernkompetenzen
|
||||
</h3>
|
||||
<div className="flex flex-wrap justify-center gap-3">
|
||||
{skills.map((skill, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`px-4 py-2 bg-primary/10 text-primary font-semibold rounded-full text-sm border border-primary/20 hover:bg-primary hover:text-white transition-all cursor-default hover:scale-105 ${
|
||||
isVisible ? 'animate-fade-in' : 'opacity-0'
|
||||
}`}
|
||||
style={{ animationDelay: `${(index + timeline.length + 4) * 50}ms` }}
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
title: string;
|
||||
category: 'web' | 'print';
|
||||
description: string;
|
||||
image: string;
|
||||
details: string[];
|
||||
link?: string;
|
||||
}
|
||||
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: 'estev',
|
||||
title: 'e-stev Radsport',
|
||||
category: 'web',
|
||||
description: '2013 für die STEV Radport GmbH in Gunzenhausen wurde die Webseite e-stev von Grund auf neu geschaffen und für ein CMS umgesetzt.',
|
||||
image: '/img/Referenz - e-stev -Web.jpg',
|
||||
details: [
|
||||
'Modernes Responsive Design',
|
||||
'CMS-Integration für einfache Pflege',
|
||||
'Produktkatalog für E-Bikes',
|
||||
'Kontaktformular mit Kartenfunktion',
|
||||
],
|
||||
link: 'https://e-stev.de',
|
||||
},
|
||||
{
|
||||
id: 'planet',
|
||||
title: 'Planet-Touristik',
|
||||
category: 'web',
|
||||
description: 'Für Planet-Touristik, das Reisebüro im Nürnberger Norden, wurde die Startseite 2014 einem Redesign unterworfen.',
|
||||
image: '/img/Referenz---Planet-Touristik---web.jpg',
|
||||
details: [
|
||||
'Redesign der Startseite',
|
||||
'Optimierung der User Experience',
|
||||
'Integration in bestehendes System',
|
||||
'Responsive Layout',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'handstaubsauger',
|
||||
title: 'Handstaubsauger24.de',
|
||||
category: 'web',
|
||||
description: 'Für handstaubsauger24.de wurde ein Magento E-Commerce Template gestaltet.',
|
||||
image: '/img/Referenz---handstaubsauger---web.jpg',
|
||||
details: [
|
||||
'Magento Theme Development',
|
||||
'E-Commerce Optimierung',
|
||||
'Produktfilter und Suche',
|
||||
'Mobile-First Ansatz',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'illy',
|
||||
title: 'Illy Caffè Ansbach',
|
||||
category: 'print',
|
||||
description: '2007 für das Illy Caffe in Ansbach entstandener Trifolder als Menükarte.',
|
||||
image: '/img/Referenz---Illy---Print.jpg',
|
||||
details: [
|
||||
'Tri-Folder Design',
|
||||
'Hochwertige Druckvorlagen',
|
||||
'Corporate Design Integration',
|
||||
'Fotografie und Layout',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sz',
|
||||
title: 'SZ Kirchbirlingen',
|
||||
category: 'print',
|
||||
description: '2008 für die SZ in Kirchbirlingen entstandener Flyer als Wurfsendung.',
|
||||
image: '/img/Referenz---SZ---Print.jpg',
|
||||
details: [
|
||||
'DIN Lang Format',
|
||||
'Wurfsendung Konzept',
|
||||
'Print Design',
|
||||
'Layout und Satz',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'planet-print',
|
||||
title: 'Planet-Touristik Flyer',
|
||||
category: 'print',
|
||||
description: '2014 wurde ein neuer Flyer für Planet Touristik geschaffen.',
|
||||
image: '/img/Referenz---Planet-Touristik---print.jpg',
|
||||
details: [
|
||||
'DIN A5 Format',
|
||||
'Verteiler und Auslage',
|
||||
'Moderne Gestaltung',
|
||||
'Print-Produktion',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Portfolio() {
|
||||
const [activeCategory, setActiveCategory] = useState<'all' | 'web' | 'print'>('all');
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());
|
||||
|
||||
const filteredProjects =
|
||||
activeCategory === 'all'
|
||||
? projects
|
||||
: projects.filter((p) => p.category === activeCategory);
|
||||
|
||||
const handleImageError = (projectId: string) => {
|
||||
setImageErrors(prev => new Set(prev).add(projectId));
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="portfolio" className="py-20 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-text text-center mb-6 relative inline-block w-full after:content-[''] after:block after:w-12 after:h-1 after:bg-primary after:mt-6 after:mx-auto">
|
||||
Portfolio
|
||||
</h2>
|
||||
|
||||
<p className="text-text-light text-center mb-12 max-w-3xl mx-auto">
|
||||
Zwischen 2000 bis 2010 war ich auch als Freelancer tätig und arbeitete mit Kunden und
|
||||
Agenturen zusammen. Eine Auswahl meiner Arbeiten aus diesem Zeitraum habe ich hier
|
||||
zusammengestellt.
|
||||
</p>
|
||||
|
||||
{/* Filter Buttons */}
|
||||
<div className="flex justify-center gap-4 mb-12">
|
||||
{(['all', 'web', 'print'] as const).map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActiveCategory(cat)}
|
||||
className={`px-6 py-3 rounded-lg font-semibold transition-all capitalize ${
|
||||
activeCategory === cat
|
||||
? 'bg-primary text-white shadow-lg'
|
||||
: 'bg-gray-100 text-text hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{cat === 'all' ? 'Alle' : cat === 'web' ? 'Web' : 'Print'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Projects Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredProjects.map((project) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className="group relative overflow-hidden rounded-lg shadow-lg hover:shadow-2xl transition-all duration-300 cursor-pointer bg-white"
|
||||
onClick={() => setSelectedProject(project)}
|
||||
>
|
||||
<div className="relative h-64 bg-gray-200 overflow-hidden">
|
||||
{!imageErrors.has(project.id) ? (
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
fill
|
||||
className="object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
onError={() => handleImageError(project.id)}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center">
|
||||
<div className="text-center p-4">
|
||||
<svg className="w-16 h-16 mx-auto mb-2 text-primary/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<p className="text-sm text-gray-500">{project.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-6">
|
||||
<h3 className="text-white font-bold text-xl mb-2">{project.title}</h3>
|
||||
<p className="text-white/90 text-sm line-clamp-2">{project.description}</p>
|
||||
<button className="mt-4 text-primary font-semibold hover:text-white transition-colors">
|
||||
Details ansehen →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-white">
|
||||
<h3 className="font-bold text-text mb-2">{project.title}</h3>
|
||||
<span className="inline-block px-3 py-1 bg-primary/10 text-primary text-xs font-semibold rounded-full">
|
||||
{project.category === 'web' ? 'Web Design' : 'Print Design'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{selectedProject && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
onClick={() => setSelectedProject(null)}
|
||||
>
|
||||
<div
|
||||
className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="sticky top-0 bg-white border-b p-4 flex justify-between items-center z-10">
|
||||
<h3 className="text-2xl font-bold text-text">{selectedProject.title}</h3>
|
||||
<button
|
||||
onClick={() => setSelectedProject(null)}
|
||||
className="text-gray-500 hover:text-text transition-colors"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div className="relative h-64 bg-gray-200 rounded-lg mb-6 overflow-hidden">
|
||||
{!imageErrors.has(selectedProject.id) ? (
|
||||
<Image
|
||||
src={selectedProject.image}
|
||||
alt={selectedProject.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
onError={() => handleImageError(selectedProject.id)}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<svg className="w-20 h-20 mx-auto mb-2 text-primary/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<p className="text-sm text-gray-500">Bild wird geladen...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-text-light mb-6">{selectedProject.description}</p>
|
||||
|
||||
<h4 className="font-bold text-text mb-3">Details:</h4>
|
||||
<ul className="space-y-2 mb-6">
|
||||
{selectedProject.details.map((detail, idx) => (
|
||||
<li key={idx} className="flex items-start">
|
||||
<svg className="w-5 h-5 text-primary mt-0.5 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-text-light">{detail}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface Skill {
|
||||
name: string;
|
||||
level: number; // 1-5
|
||||
category: string;
|
||||
}
|
||||
|
||||
const skillsData: Skill[] = [
|
||||
// Programmiersprachen
|
||||
{ name: 'PHP & FW Laravel', level: 4, category: 'Programmiersprachen' },
|
||||
{ name: 'Python & Perl', level: 4, category: 'Programmiersprachen' },
|
||||
{ name: 'JavaScript', level: 4, category: 'Programmiersprachen' },
|
||||
{ name: 'Node.js', level: 3, category: 'Programmiersprachen' },
|
||||
{ name: 'Vue.js / Next.js', level: 4, category: 'Programmiersprachen' },
|
||||
{ name: 'HTML', level: 5, category: 'Programmiersprachen' },
|
||||
{ name: 'CSS & Frameworks', level: 5, category: 'Programmiersprachen' },
|
||||
{ name: 'Sass', level: 4, category: 'Programmiersprachen' },
|
||||
|
||||
// Datenbanken
|
||||
{ name: 'SQL (MS/DB2/Oracle)', level: 4, category: 'Datenbanken' },
|
||||
{ name: 'MySQL, MariaDB, Postgres', level: 4, category: 'Datenbanken' },
|
||||
{ name: 'Redis', level: 4, category: 'Datenbanken' },
|
||||
{ name: 'MongoDB', level: 3, category: 'Datenbanken' },
|
||||
{ name: 'CouchDB', level: 3, category: 'Datenbanken' },
|
||||
{ name: 'Firebase', level: 1, category: 'Datenbanken' },
|
||||
|
||||
// DevOps
|
||||
{ name: 'Linux Bash Scripting', level: 4, category: 'DevOps' },
|
||||
{ name: 'Git / SVN', level: 4, category: 'DevOps' },
|
||||
{ name: 'Docker / Kubernetes', level: 3, category: 'DevOps' },
|
||||
{ name: 'AWS', level: 3, category: 'DevOps' },
|
||||
{ name: 'CI/CD Pipelines', level: 4, category: 'DevOps' },
|
||||
{ name: 'Draw.io Architektur', level: 4, category: 'DevOps' },
|
||||
|
||||
// Grafik & Design
|
||||
{ name: 'Adobe Suite (u.a. PS/XD)', level: 4, category: 'Grafik & Design' },
|
||||
{ name: 'Affinity', level: 4, category: 'Grafik & Design' },
|
||||
{ name: 'FinalCut', level: 4, category: 'Grafik & Design' },
|
||||
];
|
||||
|
||||
const categories = [
|
||||
'Programmiersprachen',
|
||||
'Datenbanken',
|
||||
'DevOps',
|
||||
'Grafik & Design',
|
||||
];
|
||||
|
||||
export default function Skills() {
|
||||
const [activeCategory, setActiveCategory] = useState<string>('Programmiersprachen');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = ref.current;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (currentRef) {
|
||||
observer.observe(currentRef);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef) {
|
||||
observer.unobserve(currentRef);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const filteredSkills = skillsData.filter((skill) => skill.category === activeCategory);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="skills"
|
||||
ref={ref}
|
||||
className={`py-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-bg-light to-white transition-opacity duration-1000 ${
|
||||
isVisible ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-text text-center mb-4 relative inline-block w-full">
|
||||
Technische Fähigkeiten
|
||||
</h2>
|
||||
<p className="text-center text-text-light mb-12 max-w-2xl mx-auto">
|
||||
Umfassende Expertise in modernen Technologien und Frameworks
|
||||
</p>
|
||||
|
||||
{/* Category Tabs */}
|
||||
<div className="flex flex-wrap justify-center gap-3 mb-12">
|
||||
{categories.map((category, index) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
className={`px-5 py-3 rounded-lg font-semibold text-sm transition-all duration-300 ${
|
||||
activeCategory === category
|
||||
? 'bg-primary text-white shadow-lg scale-105'
|
||||
: 'bg-white text-text-light hover:bg-gray-100 hover:text-text shadow-md'
|
||||
} ${isVisible ? 'animate-fade-in' : 'opacity-0'}`}
|
||||
style={{ animationDelay: `${index * 50}ms` }}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Skills Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredSkills.map((skill, index) => (
|
||||
<div
|
||||
key={skill.name}
|
||||
className={`bg-white rounded-lg p-6 shadow-md hover:shadow-xl transition-all duration-300 hover:-translate-y-1 ${
|
||||
isVisible ? 'animate-fade-in' : 'opacity-0'
|
||||
}`}
|
||||
style={{ animationDelay: `${(index + categories.length) * 50}ms` }}
|
||||
>
|
||||
{/* Skill Name */}
|
||||
<h3 className="font-bold text-text mb-3 text-base">{skill.name}</h3>
|
||||
|
||||
{/* Level Bar */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 bg-gray-200 rounded-full h-2.5 overflow-hidden">
|
||||
<div
|
||||
className="bg-gradient-to-r from-primary to-primary-dark h-full rounded-full transition-all duration-1000 ease-out"
|
||||
style={{
|
||||
width: isVisible ? `${(skill.level / 5) * 100}%` : '0%',
|
||||
transitionDelay: `${(index + categories.length) * 50 + 200}ms`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-bold text-primary min-w-[2rem] text-right">
|
||||
{skill.level}/5
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Level Dots */}
|
||||
<div className="flex gap-1 mt-3">
|
||||
{[1, 2, 3, 4, 5].map((dot) => (
|
||||
<div
|
||||
key={dot}
|
||||
className={`w-2 h-2 rounded-full transition-all duration-300 ${
|
||||
dot <= skill.level ? 'bg-primary' : 'bg-gray-300'
|
||||
}`}
|
||||
style={{
|
||||
transitionDelay: `${(index + categories.length) * 50 + dot * 50}ms`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Info Text */}
|
||||
<div className="mt-12 text-center">
|
||||
<p className="text-text-light text-sm">
|
||||
<strong className="text-primary">Bewertungsskala:</strong> 1 = Grundkenntnisse | 5 = Expertenwissen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface SoftSkill {
|
||||
name: string;
|
||||
level: number; // 1-5
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const softSkills: SoftSkill[] = [
|
||||
{ name: 'Organisation', level: 4, icon: '📋' },
|
||||
{ name: 'Kommunikation', level: 5, icon: '💬' },
|
||||
{ name: 'Selbstständigkeit', level: 4, icon: '🎯' },
|
||||
{ name: 'Teamfähigkeit', level: 5, icon: '🤝' },
|
||||
{ name: 'Verantwortung', level: 5, icon: '✓' },
|
||||
{ name: 'Engagement', level: 5, icon: '🚀' },
|
||||
{ name: 'Kompromissbereitschaft', level: 4, icon: '🤔' },
|
||||
{ name: 'Aufgeschlossenheit', level: 5, icon: '🌟' },
|
||||
{ name: 'Lernbereitschaft', level: 5, icon: '📚' },
|
||||
{ name: 'Zielstrebigkeit', level: 5, icon: '🎯' },
|
||||
{ name: 'Überzeugungsfähigkeit', level: 4, icon: '💡' },
|
||||
{ name: 'Lösungsorientiert', level: 5, icon: '🔧' },
|
||||
];
|
||||
|
||||
export default function SoftSkills() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = ref.current;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (currentRef) {
|
||||
observer.observe(currentRef);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef) {
|
||||
observer.unobserve(currentRef);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="soft-skills"
|
||||
ref={ref}
|
||||
className={`py-20 px-4 sm:px-6 lg:px-8 bg-white transition-opacity duration-1000 ${
|
||||
isVisible ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-text text-center mb-4 relative inline-block w-full">
|
||||
Persönliche Fähigkeiten
|
||||
</h2>
|
||||
<p className="text-center text-text-light mb-12 max-w-2xl mx-auto">
|
||||
Soft Skills, die mich als Teamplayer und Leader auszeichnen
|
||||
</p>
|
||||
|
||||
{/* Skills Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{softSkills.map((skill, index) => (
|
||||
<div
|
||||
key={skill.name}
|
||||
className={`group bg-gradient-to-br from-white to-bg-light rounded-xl p-6 shadow-md hover:shadow-2xl transition-all duration-300 hover:-translate-y-2 border border-gray-100 hover:border-primary/30 ${
|
||||
isVisible ? 'animate-fade-in' : 'opacity-0'
|
||||
}`}
|
||||
style={{ animationDelay: `${index * 50}ms` }}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="text-4xl mb-4 group-hover:scale-110 transition-transform duration-300">
|
||||
{skill.icon}
|
||||
</div>
|
||||
|
||||
{/* Skill Name */}
|
||||
<h3 className="font-bold text-text mb-3 text-base group-hover:text-primary transition-colors">
|
||||
{skill.name}
|
||||
</h3>
|
||||
|
||||
{/* Level Dots */}
|
||||
<div className="flex gap-1.5">
|
||||
{[1, 2, 3, 4, 5].map((dot) => (
|
||||
<div
|
||||
key={dot}
|
||||
className={`w-2.5 h-2.5 rounded-full transition-all duration-300 ${
|
||||
dot <= skill.level
|
||||
? 'bg-primary group-hover:scale-125'
|
||||
: 'bg-gray-300'
|
||||
}`}
|
||||
style={{
|
||||
transitionDelay: `${index * 50 + dot * 30}ms`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Hover Effect - Level Text */}
|
||||
<div className="mt-3 transition-opacity duration-300">
|
||||
<span className="text-xs text-primary font-semibold">
|
||||
{skill.level === 5 ? 'Expertenlevel' : skill.level === 4 ? 'Sehr gut' : 'Gut'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom Info */}
|
||||
<div className="mt-16 bg-gradient-to-r from-primary/5 via-primary/10 to-primary/5 rounded-xl p-8 text-center">
|
||||
<h3 className="text-xl font-bold text-text mb-4">
|
||||
Warum diese Fähigkeiten wichtig sind
|
||||
</h3>
|
||||
<p className="text-text-light leading-relaxed max-w-3xl mx-auto">
|
||||
In über 20 Jahren Berufserfahrung habe ich gelernt, dass technisches Know-how nur die
|
||||
halbe Miete ist. Erfolgreiche Projekte entstehen durch{' '}
|
||||
<strong className="text-primary">klare Kommunikation</strong>,
|
||||
<strong className="text-primary"> Teamarbeit</strong> und die Fähigkeit, auch in
|
||||
stressigen Situationen <strong className="text-primary">lösungsorientiert</strong> zu bleiben.
|
||||
Diese Soft Skills ermöglichen es mir, komplexe technische Anforderungen verständlich zu
|
||||
vermitteln und Teams erfolgreich zu führen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user