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,46 @@
|
||||
'use client';
|
||||
|
||||
import { Phone, Mail, ArrowRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
|
||||
export function ContactInfo() {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('footer.sections.contact');
|
||||
|
||||
return (
|
||||
<div className="pb-6 px-6 pt-4 bg-zinc-800/30 border border-zinc-800 rounded-lg mx-4 mt-2">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-white tracking-wider uppercase">
|
||||
{t('title')}
|
||||
</h3>
|
||||
|
||||
<a
|
||||
href="tel:+491756950979"
|
||||
className="flex items-center space-x-3 text-sm text-zinc-400 hover:text-white transition-colors duration-200 group"
|
||||
>
|
||||
<Phone className="h-4 w-4 text-zinc-400 group-hover:text-white transition-colors duration-200" />
|
||||
<span>+49 175 695 0979</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="mailto:info@damjan-savic.com"
|
||||
className="flex items-center space-x-3 text-sm text-zinc-400 hover:text-white transition-colors duration-200 group"
|
||||
>
|
||||
<Mail className="h-4 w-4 text-zinc-400 group-hover:text-white transition-colors duration-200" />
|
||||
<span>{t('email')}</span>
|
||||
</a>
|
||||
|
||||
<Link
|
||||
href={`/${locale}/contact`}
|
||||
className="flex items-center justify-between mt-6 px-4 py-2 bg-zinc-800/50
|
||||
hover:bg-zinc-800 rounded-full text-sm text-zinc-400 hover:text-white
|
||||
transition-all duration-200 group border border-zinc-800 hover:border-zinc-700"
|
||||
>
|
||||
<span className="tracking-wide">Kontakt aufnehmen</span>
|
||||
<ArrowRight className="h-4 w-4 transform group-hover:translate-x-1 transition-transform duration-200" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import Link from 'next/link';
|
||||
import { getLocale, getTranslations } from 'next-intl/server';
|
||||
import { Mail, Linkedin, Github, MapPin } from 'lucide-react';
|
||||
|
||||
const Footer = async () => {
|
||||
const locale = await getLocale();
|
||||
const t = await getTranslations('footer');
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="relative bg-zinc-800/50 backdrop-blur-sm border-t border-zinc-700/30 pt-8 pb-4 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex flex-col space-y-8">
|
||||
{/* Contact Section */}
|
||||
<div>
|
||||
<h3 className="text-white text-base font-semibold mb-3">
|
||||
{t('sections.contact.title')}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<a
|
||||
href={`mailto:${t('sections.contact.email')}`}
|
||||
className="group flex items-center gap-2 text-zinc-400 hover:text-white transition-colors text-sm w-fit"
|
||||
>
|
||||
<Mail className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors flex-shrink-0" />
|
||||
<span>{t('sections.contact.email')}</span>
|
||||
</a>
|
||||
<div className="flex items-center gap-2 text-zinc-400 text-sm w-fit">
|
||||
<MapPin className="h-4 w-4 text-zinc-500 flex-shrink-0" />
|
||||
<span>{t('sections.contact.location')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Section */}
|
||||
<div>
|
||||
<h3 className="text-white text-base font-semibold mb-3 text-left">
|
||||
{t('sections.navigation.title')}
|
||||
</h3>
|
||||
<nav className="flex flex-col space-y-2">
|
||||
<Link
|
||||
href={`/${locale}/portfolio`}
|
||||
className="text-zinc-400 hover:text-white transition-colors text-sm w-fit"
|
||||
>
|
||||
{t('sections.navigation.links.portfolio')}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/blog`}
|
||||
className="text-zinc-400 hover:text-white transition-colors text-sm w-fit"
|
||||
>
|
||||
{t('sections.navigation.links.blog')}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/about`}
|
||||
className="text-zinc-400 hover:text-white transition-colors text-sm w-fit"
|
||||
>
|
||||
{t('sections.navigation.links.about')}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/contact`}
|
||||
className="text-zinc-400 hover:text-white transition-colors text-sm w-fit"
|
||||
>
|
||||
{t('sections.navigation.links.contact')}
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Social Media Section */}
|
||||
<div>
|
||||
<h3 className="text-white text-base font-semibold mb-3 text-left">
|
||||
{t('sections.social.title')}
|
||||
</h3>
|
||||
<div className="flex space-x-4">
|
||||
<a
|
||||
href="https://www.linkedin.com/in/damjan-savić-720288127/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group text-zinc-400 hover:text-white transition-colors"
|
||||
aria-label="LinkedIn"
|
||||
>
|
||||
<Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/damjan1996"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group text-zinc-400 hover:text-white transition-colors"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<Github className="transform transition-transform group-hover:scale-110" size={20} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legal Section */}
|
||||
<div className="border-t border-zinc-700/30 pt-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<p className="text-zinc-400 text-xs text-left">
|
||||
© {currentYear} Damjan Savić. {t('legal.copyright')}
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<Link
|
||||
href={`/${locale}/privacy`}
|
||||
className="text-zinc-400 hover:text-white text-xs transition-colors"
|
||||
>
|
||||
{t('legal.links.privacy')}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/imprint`}
|
||||
className="text-zinc-400 hover:text-white text-xs transition-colors"
|
||||
>
|
||||
{t('legal.links.imprint')}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/terms`}
|
||||
className="text-zinc-400 hover:text-white text-xs transition-colors"
|
||||
>
|
||||
{t('legal.links.terms')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -0,0 +1,112 @@
|
||||
const GlobalBackground = () => {
|
||||
return (
|
||||
<>
|
||||
{/* Background layer */}
|
||||
<div
|
||||
className="fixed inset-0 bg-zinc-900"
|
||||
style={{ zIndex: -2 }}
|
||||
/>
|
||||
|
||||
{/* Gradient overlay */}
|
||||
<div
|
||||
className="fixed inset-0"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
background: 'linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 50%, #1e1e1e 100%)',
|
||||
opacity: 0.9
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Animated lines */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none"
|
||||
style={{ zIndex: -1 }}
|
||||
>
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="none"
|
||||
style={{ opacity: 0.3 }}
|
||||
>
|
||||
<line
|
||||
x1="0%"
|
||||
y1="20%"
|
||||
x2="100%"
|
||||
y2="20%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.2"
|
||||
>
|
||||
<animate
|
||||
attributeName="y1"
|
||||
values="20%;80%;20%"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="y2"
|
||||
values="20%;80%;20%"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
|
||||
<line
|
||||
x1="0%"
|
||||
y1="50%"
|
||||
x2="100%"
|
||||
y2="50%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.15"
|
||||
>
|
||||
<animate
|
||||
attributeName="x1"
|
||||
values="0%;100%;0%"
|
||||
dur="15s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
|
||||
<line
|
||||
x1="20%"
|
||||
y1="0%"
|
||||
x2="20%"
|
||||
y2="100%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.1"
|
||||
>
|
||||
<animate
|
||||
attributeName="x1"
|
||||
values="20%;80%;20%"
|
||||
dur="12s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="x2"
|
||||
values="20%;80%;20%"
|
||||
dur="12s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Grid overlay */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '50px 50px',
|
||||
opacity: 0.5
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalBackground;
|
||||
@@ -0,0 +1,210 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslations, useLocale } from 'next-intl';
|
||||
import {
|
||||
Menu,
|
||||
X,
|
||||
Home,
|
||||
User,
|
||||
Briefcase,
|
||||
BookOpen,
|
||||
Phone,
|
||||
Cog,
|
||||
} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { NavLink } from './NavLink';
|
||||
import LanguageSwitcher from './LanguageSwitcher';
|
||||
import { ContactInfo } from './ContactInfo';
|
||||
|
||||
const Header = () => {
|
||||
const t = useTranslations('navigation');
|
||||
const locale = useLocale();
|
||||
const pathname = usePathname();
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
// Close menu on route change
|
||||
useEffect(() => {
|
||||
setIsMenuOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Scroll handler
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 0);
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// Prevent body scroll when menu is open
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}, [isMenuOpen]);
|
||||
|
||||
const navigationLinks = [
|
||||
{
|
||||
path: `/${locale}`,
|
||||
label: t('main.home'),
|
||||
icon: <Home className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
path: `/${locale}/about`,
|
||||
label: t('main.about'),
|
||||
icon: <User className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
path: `/${locale}/leistungen`,
|
||||
label: t('main.services'),
|
||||
icon: <Cog className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
path: `/${locale}/portfolio`,
|
||||
label: t('main.portfolio'),
|
||||
icon: <Briefcase className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
path: `/${locale}/blog`,
|
||||
label: t('main.blog'),
|
||||
icon: <BookOpen className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
path: `/${locale}/contact`,
|
||||
label: t('main.contact'),
|
||||
icon: <Phone className="h-5 w-5" />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Navigation */}
|
||||
<nav
|
||||
className={`
|
||||
fixed w-full z-40 transition-all duration-300
|
||||
bg-zinc-800/50 backdrop-blur-sm border-b border-zinc-700/30
|
||||
${scrolled
|
||||
? 'bg-zinc-800/70 backdrop-blur-md shadow-lg shadow-zinc-900/30'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
role="navigation"
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo */}
|
||||
<Link href={`/${locale}`} className="flex items-center space-x-2 group shrink-0">
|
||||
<Image
|
||||
src="/header-logo.svg"
|
||||
alt="Damjan Savić Logo"
|
||||
className="h-8 w-auto transition-transform duration-200 hover:scale-105"
|
||||
width={120}
|
||||
height={32}
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
{navigationLinks.map(({ path, label, icon }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
href={path}
|
||||
icon={icon}
|
||||
label={label}
|
||||
className="text-sm"
|
||||
/>
|
||||
))}
|
||||
<div className="ml-4 text-zinc-400 pl-2 border-l border-zinc-700">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="md:hidden relative z-50 p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors active:scale-95"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="mobile-menu"
|
||||
aria-label={isMenuOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu Backdrop */}
|
||||
<div
|
||||
className={`fixed inset-0 bg-black/50 md:hidden z-30 transition-opacity duration-200 ${
|
||||
isMenuOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
||||
}`}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Mobile Sidebar */}
|
||||
<div
|
||||
id="mobile-menu"
|
||||
className={`fixed right-0 top-0 h-full w-80 bg-zinc-900/95 backdrop-blur-md shadow-xl md:hidden z-50 border-l border-zinc-800 transition-transform duration-300 ease-out ${
|
||||
isMenuOpen ? 'translate-x-0' : 'translate-x-full'
|
||||
}`}
|
||||
>
|
||||
{/* Sidebar Header */}
|
||||
<div className="flex items-center justify-between px-4 h-16 border-b border-zinc-800">
|
||||
<span className="text-white font-semibold">Menu</span>
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
aria-label="Close sidebar"
|
||||
className="p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors active:scale-95"
|
||||
>
|
||||
<X className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Scrollable Sidebar Content */}
|
||||
<div className="h-[calc(100vh-4rem)] overflow-y-auto">
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Contact Info */}
|
||||
<ContactInfo />
|
||||
|
||||
{/* Navigation Links */}
|
||||
<div className="py-4 px-4">
|
||||
{navigationLinks.map(({ path, label, icon }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
href={path}
|
||||
icon={icon}
|
||||
label={label}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="flex items-center w-full mb-1"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Language Switcher */}
|
||||
<div className="px-4 py-3 border-t border-zinc-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-zinc-400">
|
||||
Sprache ändern
|
||||
</span>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface NavLinkProps {
|
||||
href: string;
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function NavLink({ href, icon, label, onClick, className }: NavLinkProps) {
|
||||
const pathname = usePathname();
|
||||
// Check if path matches (accounting for locale prefix)
|
||||
const pathWithoutLocale = pathname.replace(/^\/(de|en|sr)/, '');
|
||||
const hrefWithoutLocale = href.replace(/^\/(de|en|sr)/, '');
|
||||
const isActive = pathWithoutLocale === hrefWithoutLocale ||
|
||||
(hrefWithoutLocale === '' && pathWithoutLocale === '');
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
className={`
|
||||
relative flex items-center gap-2 rounded-full py-2 px-4
|
||||
transition-all duration-200 ease-in-out
|
||||
${isActive ? 'text-white bg-zinc-700/60' : 'text-zinc-400 hover:text-white hover:bg-zinc-800/30'}
|
||||
${className || ''}
|
||||
`}
|
||||
>
|
||||
{icon}
|
||||
<span className="text-sm tracking-wide">{label}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as Header } from './Header';
|
||||
export { default as Footer } from './Footer';
|
||||
export { default as GlobalBackground } from './GlobalBackground';
|
||||
export { default as LanguageSwitcher } from './LanguageSwitcher';
|
||||
export { NavLink } from './NavLink';
|
||||
export { ContactInfo } from './ContactInfo';
|
||||
Reference in New Issue
Block a user