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:
+56
-20
@@ -2,8 +2,10 @@ import { notFound } from 'next/navigation';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages, setRequestLocale } from 'next-intl/server';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { locales, type Locale } from '@/i18n/config';
|
||||
import { locales, localeMetadata, seoKeywords, type Locale } from '@/i18n/config';
|
||||
import type { Metadata } from 'next';
|
||||
import { Header, Footer, GlobalBackground } from '@/components/layout';
|
||||
import { PersonJsonLd, ProfessionalServiceJsonLd, OrganizationJsonLd, WebSiteJsonLd } from '@/components/seo';
|
||||
import '../globals.css';
|
||||
|
||||
const inter = Inter({
|
||||
@@ -23,43 +25,57 @@ export function generateStaticParams() {
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const currentLocale = locale as Locale;
|
||||
const meta = localeMetadata[currentLocale] || localeMetadata.de;
|
||||
const keywords = seoKeywords[currentLocale] || seoKeywords.de;
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const titles: Record<Locale, string> = {
|
||||
de: 'Damjan Savic | AI & Automation Specialist aus Koeln',
|
||||
en: 'Damjan Savic | AI & Automation Specialist from Cologne',
|
||||
sr: 'Damjan Savic | AI & Automation Specialist iz Kelna',
|
||||
de: 'Damjan Savić | AI & Automation Specialist aus Köln',
|
||||
en: 'Damjan Savić | AI & Automation Specialist from Cologne',
|
||||
sr: 'Damjan Savić | AI & Automation Specialist iz Kelna',
|
||||
};
|
||||
|
||||
const descriptions: Record<Locale, string> = {
|
||||
de: 'Entwicklung von KI-Agenten und Automatisierungsloesungen. Von Voice-AI-Plattformen bis zu autonomen Web-Agenten.',
|
||||
de: 'Entwicklung von KI-Agenten und Automatisierungslösungen. Von Voice-AI-Plattformen bis zu autonomen Web-Agenten.',
|
||||
en: 'Building AI agents and automation solutions. From voice AI platforms to autonomous web agents.',
|
||||
sr: 'Razvoj AI agenata i resenja za automatizaciju. Od voice AI platformi do autonomnih web agenata.',
|
||||
};
|
||||
|
||||
// Generate alternates using localeMetadata
|
||||
const languageAlternates: Record<string, string> = {
|
||||
'x-default': `${BASE_URL}/de`,
|
||||
};
|
||||
locales.forEach((loc) => {
|
||||
languageAlternates[loc] = `${BASE_URL}/${loc}`;
|
||||
});
|
||||
|
||||
// Generate OG alternate locales
|
||||
const allOgLocales = locales.map((loc) => localeMetadata[loc].ogLocale);
|
||||
const alternateOgLocales = allOgLocales.filter((l) => l !== meta.ogLocale);
|
||||
|
||||
return {
|
||||
title: {
|
||||
template: '%s | Damjan Savic',
|
||||
default: titles[locale as Locale] || titles.de,
|
||||
template: '%s | Damjan Savić',
|
||||
default: titles[currentLocale] || titles.de,
|
||||
},
|
||||
description: descriptions[locale as Locale] || descriptions.de,
|
||||
description: descriptions[currentLocale] || descriptions.de,
|
||||
keywords: keywords,
|
||||
alternates: {
|
||||
canonical: `/${locale}`,
|
||||
languages: {
|
||||
de: '/de',
|
||||
en: '/en',
|
||||
sr: '/sr',
|
||||
},
|
||||
canonical: `${BASE_URL}/${locale}`,
|
||||
languages: languageAlternates,
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US',
|
||||
siteName: 'Damjan Savic',
|
||||
locale: meta.ogLocale,
|
||||
alternateLocale: alternateOgLocales,
|
||||
siteName: 'Damjan Savić',
|
||||
images: [
|
||||
{
|
||||
url: '/images/og-image.jpg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Damjan Savic - AI & Automation Specialist',
|
||||
alt: locale === 'de' ? 'Damjan Savić - KI & Automatisierung Spezialist' : locale === 'sr' ? 'Damjan Savić - AI & Automatizacija Specijalista' : 'Damjan Savić - AI & Automation Specialist',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -82,10 +98,30 @@ export default async function LocaleLayout({ children, params }: Props) {
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={locale} className={inter.variable}>
|
||||
<body className="bg-zinc-900 text-zinc-50 antialiased">
|
||||
<html lang={locale} className={`${inter.variable} dark`}>
|
||||
<head>
|
||||
{/* Preconnect für bessere Performance */}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link rel="dns-prefetch" href="https://mxadgucxhmstlzsbgmoz.supabase.co" />
|
||||
|
||||
{/* Theme Color für mobile Browser */}
|
||||
<meta name="theme-color" content="#18181b" />
|
||||
|
||||
{/* JSON-LD Structured Data */}
|
||||
<PersonJsonLd locale={locale as Locale} />
|
||||
<ProfessionalServiceJsonLd locale={locale as Locale} />
|
||||
<OrganizationJsonLd locale={locale as Locale} />
|
||||
<WebSiteJsonLd locale={locale as Locale} />
|
||||
</head>
|
||||
<body className="min-h-screen bg-zinc-900 text-zinc-50 antialiased">
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
<div className="min-h-screen flex flex-col relative">
|
||||
<GlobalBackground />
|
||||
<Header />
|
||||
<main className="flex-1 pt-16 relative z-10">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user