feat: Add professional portfolio images and Hero redesign

- Add 15 optimized WebP images for portfolio (21-53KB each)
- Redesign Hero section with full-width background image
- Add SSR support for Hero component
- Update About section with new portrait image
- Update Contact page with professional headshot
- Add images to Services/Leistungen page
- Add image optimization script (sharp)
- Update translations for gallery section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 23:33:28 +01:00
co-authored by Claude Opus 4.5
parent b1ec7b4d61
commit 87c7ebc5e3
75 changed files with 10271 additions and 1314 deletions
+108 -58
View File
@@ -1,19 +1,83 @@
import { setRequestLocale } from 'next-intl/server';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import Link from 'next/link';
import { Hero, Skills, Workflow, Journey } from '@/components/about';
import { ProfilePageJsonLd, BreadcrumbJsonLd } from '@/components/seo';
import { type Locale } from '@/i18n/config';
type Props = {
params: Promise<{ locale: string }>;
};
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'pages.about.seo' });
const keywords: Record<string, string[]> = {
de: [
'Über Damjan Savić',
'KI Entwickler Köln',
'AI Developer Deutschland',
'Fullstack Entwickler',
'Lebenslauf',
'Skills',
'Erfahrung',
'Software Entwickler',
],
en: [
'About Damjan Savić',
'Remote AI Developer',
'AI Developer Europe',
'Fullstack Developer',
'AI Consultant',
'Resume',
'Skills',
'Experience',
'Software Developer',
'Hire AI Developer',
'Freelance AI Developer USA',
'Freelance AI Developer UK',
'Remote Developer',
'n8n Expert',
'Voice AI Developer',
],
sr: [
'O Damjanu Saviću',
'AI Developer Keln',
'AI Developer Nemačka',
'Fullstack Developer',
'Biografija',
'Veštine',
'Iskustvo',
'Software Developer',
],
};
const localePath = locale === 'de' ? '/about' : `/${locale}/about`;
return {
title: t('title'),
description: t('description'),
keywords: keywords[locale] || keywords.de,
alternates: {
canonical: `${BASE_URL}${localePath}`,
languages: {
'x-default': `${BASE_URL}/about`,
de: `${BASE_URL}/about`,
en: `${BASE_URL}/en/about`,
sr: `${BASE_URL}/sr/about`,
},
},
openGraph: {
title: t('title'),
description: t('description'),
url: `${BASE_URL}${localePath}`,
type: 'profile',
locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US',
alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')),
},
};
}
@@ -23,67 +87,53 @@ export default async function AboutPage({ params }: Props) {
const t = await getTranslations('pages.about');
const breadcrumbItems = [
{ name: locale === 'de' ? 'Start' : locale === 'sr' ? 'Početna' : 'Home', url: `/${locale}` },
{ name: locale === 'de' ? 'Über mich' : locale === 'sr' ? 'O meni' : 'About', url: `/${locale}/about` },
];
return (
<main className="min-h-screen py-20 px-4">
<div className="max-w-4xl mx-auto">
{/* Breadcrumb */}
<nav className="mb-8">
<Link href={`/${locale}`} className="text-zinc-400 hover:text-white transition-colors">
Home
</Link>
<span className="text-zinc-600 mx-2">/</span>
<span className="text-white">About</span>
</nav>
<div className="relative min-h-screen">
<ProfilePageJsonLd locale={locale as Locale} />
<BreadcrumbJsonLd items={breadcrumbItems} locale={locale as Locale} />
{/* Hero Section */}
<section className="mb-16">
<p className="text-zinc-400 text-sm tracking-wider mb-2">
{t('hero.aboutMe')}
</p>
<h1 className="text-4xl sm:text-5xl font-bold mb-6">
{t('hero.title')}
</h1>
<p className="text-zinc-300 text-lg leading-relaxed">
{t('hero.description')}
</p>
</section>
{/* Hero Section */}
<section className="min-h-screen relative">
<Hero />
</section>
{/* Skills Section */}
<section className="mb-16">
<h2 className="text-2xl font-bold mb-8">Core Expertise</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-zinc-800/50 rounded-lg p-6 border border-zinc-700/50">
<h3 className="text-lg font-semibold mb-2">AI & Automation</h3>
<p className="text-zinc-400 text-sm">
GPT-4, Claude API, Voice AI, n8n, Process Automation
</p>
</div>
<div className="bg-zinc-800/50 rounded-lg p-6 border border-zinc-700/50">
<h3 className="text-lg font-semibold mb-2">Fullstack Development</h3>
<p className="text-zinc-400 text-sm">
Python, TypeScript, React, Next.js, Node.js
</p>
</div>
<div className="bg-zinc-800/50 rounded-lg p-6 border border-zinc-700/50">
<h3 className="text-lg font-semibold mb-2">System Integration</h3>
<p className="text-zinc-400 text-sm">
ERP Systems, APIs, Database Design, DevOps
</p>
</div>
{/* Skills Section */}
<section id="skills" className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
{t('skills.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
{t('skills.description')}
</p>
</div>
</section>
<Skills />
</div>
</section>
{/* Contact CTA */}
<section className="text-center">
<h2 className="text-2xl font-bold mb-4">Interested in working together?</h2>
<Link
href={`/${locale}/contact`}
className="inline-block px-8 py-4 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Get in Touch
</Link>
</section>
</div>
</main>
{/* Workflow Section */}
<section className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
{t('workflow.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
{t('workflow.description')}
</p>
</div>
<Workflow />
</div>
</section>
{/* Journey Section */}
<Journey />
</div>
);
}