Performance: PageSpeed Optimierungen implementiert

- LCP-Bild (Portrait) mit fetchpriority="high" und Preload optimiert
- Logo SVG mit expliziten width/height Attributen versehen (CLS)
- Touch-Targets in Experience Navigation von 5px auf 24px vergrößert
- Kontrast für Firmenname/Zeitraum verbessert (zinc-300/zinc-400)
- PWA Service Worker mit script-defer für nicht-blockierendes Laden
- Responsive Bilder: srcset mit 400w/800w/1200w + WebP-Format
- Neues Build-Script für Bildoptimierung (scripts/optimize-images.js)
- Original-Bilder in source-images/ verschoben (gitignored)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-05 21:14:29 +01:00
co-authored by Claude Opus 4.5
parent bda8e34355
commit 588d6e1f17
84 changed files with 166 additions and 23 deletions
+2
View File
@@ -156,6 +156,8 @@ const Layout = ({ children }: LayoutProps) => {
src="/header-logo.svg"
alt="Damjan Savić Logo"
className="h-8 w-auto"
width={120}
height={32}
whileHover={{ scale: 1.05 }}
transition={{ type: "spring", stiffness: 400, damping: 10 }}
/>
+21 -14
View File
@@ -117,10 +117,10 @@ const Experience = () => {
<h3 className="text-lg md:text-xl font-semibold text-white mb-1">
{exp.role}
</h3>
<h4 className="text-base md:text-lg text-zinc-400 mb-2">
<h4 className="text-base md:text-lg text-zinc-300 mb-2">
{exp.company}
</h4>
<p className="text-sm text-zinc-500 mb-6">
<p className="text-sm text-zinc-400 mb-6">
{exp.period}
</p>
<ul className="space-y-3 md:space-y-4">
@@ -158,25 +158,32 @@ const Experience = () => {
>
</button>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{[...Array(totalPages)].map((_, index) => (
<button
key={index}
onClick={() => setCurrentPage(index)}
className={`rounded-full transition-all duration-300 ${
index === currentPage
? 'bg-white'
: 'bg-zinc-600 hover:bg-zinc-400'
}`}
className="relative flex items-center justify-center transition-all duration-300"
style={{
width: '5px',
height: '5px',
minWidth: '5px',
minHeight: '5px',
padding: '0'
width: '24px',
height: '24px',
minWidth: '24px',
minHeight: '24px'
}}
aria-label={t('pages.home.experience.navigation.page', { page: index + 1 })}
/>
>
<span
className={`rounded-full transition-all duration-300 ${
index === currentPage
? 'bg-white'
: 'bg-zinc-600 hover:bg-zinc-400'
}`}
style={{
width: '8px',
height: '8px'
}}
/>
</button>
))}
</div>
<button
+3
View File
@@ -76,6 +76,9 @@ const Hero = () => {
alt={t('pages.home.hero.image.alt')}
className="w-full h-full object-cover"
loading="eager"
fetchPriority="high"
width={288}
height={288}
style={{ transform: 'scale(1.01)' }}
/>
</motion.div>
@@ -30,6 +30,8 @@ const PortfolioCard: React.FC<PortfolioCardProps> = ({ project }) => {
};
const imagePath = `/images/projects/${project.slug}/cover.jpg`;
const imagePathWebP = `/images/projects/${project.slug}/cover.webp`;
const srcSet = `/images/projects/${project.slug}/cover-400w.jpg 400w, /images/projects/${project.slug}/cover-800w.jpg 800w, /images/projects/${project.slug}/cover-1200w.jpg 1200w`;
// Verwende technologies anstelle von tags wenn verfügbar
const displayTags = project.technologies || project.tags;
@@ -60,14 +62,22 @@ const PortfolioCard: React.FC<PortfolioCardProps> = ({ project }) => {
>
{/* Image Container */}
<div className="aspect-[16/9] w-full relative overflow-hidden bg-zinc-900">
<img
src={imagePath}
alt={project.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-700
group-hover:scale-110"
loading="lazy"
style={{ transformOrigin: 'center center' }}
/>
<picture>
<source srcSet={imagePathWebP} type="image/webp" />
<img
src={imagePath}
srcSet={srcSet}
sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1200px"
alt={project.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-700
group-hover:scale-110"
loading="lazy"
width={1200}
height={675}
decoding="async"
style={{ transformOrigin: 'center center' }}
/>
</picture>
</div>
{/* Content - with relative positioning to overlap the gradient */}