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:
@@ -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 }}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user