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:
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { Globe } from 'lucide-react';
|
||||
|
||||
const LanguageSwitcher = () => {
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const languages = [
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'de', name: 'Deutsch' },
|
||||
{ code: 'sr', name: 'Srpski' },
|
||||
];
|
||||
|
||||
const currentLanguage = languages.find(lang => lang.code === locale)?.name || 'Language';
|
||||
|
||||
const handleLanguageChange = (newLocale: string) => {
|
||||
// Replace the locale in the pathname
|
||||
const pathWithoutLocale = pathname.replace(/^\/(de|en|sr)/, '');
|
||||
const newPath = `/${newLocale}${pathWithoutLocale || ''}`;
|
||||
router.push(newPath);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative inline-block" ref={dropdownRef}>
|
||||
<button
|
||||
className="flex items-center space-x-2 text-zinc-400 hover:text-white
|
||||
px-4 py-2 rounded-full transition-all duration-200
|
||||
hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800
|
||||
hover:scale-105 active:scale-95"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Select language"
|
||||
>
|
||||
<Globe className="h-5 w-5" aria-hidden="true" />
|
||||
<span className="text-sm">{currentLanguage}</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={`absolute ${isMobile ? 'bottom-full mb-2' : 'top-full mt-2'} right-0 w-48 rounded-lg overflow-hidden
|
||||
border border-zinc-800 bg-zinc-900/95 backdrop-blur-sm shadow-lg
|
||||
transition-all duration-200 origin-top
|
||||
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
|
||||
role="listbox"
|
||||
aria-label="Languages"
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<div className="py-1">
|
||||
{languages.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
|
||||
hover:bg-zinc-800/50
|
||||
${locale === lang.code
|
||||
? 'bg-zinc-800 text-white'
|
||||
: 'text-zinc-400 hover:text-white'
|
||||
}`}
|
||||
onClick={() => handleLanguageChange(lang.code)}
|
||||
role="option"
|
||||
aria-selected={locale === lang.code}
|
||||
>
|
||||
{lang.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
Reference in New Issue
Block a user