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,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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user