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:
2025-12-18 09:11:47 +01:00
co-authored by Claude Sonnet 4.5
parent 571c381718
commit 30a380cae8
48 changed files with 9038 additions and 20 deletions
+255
View File
@@ -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>
);
}