feat: modernize digital presence, team layout, and hero component parity
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 17s
Build & Deploy / 🧪 QA (push) Failing after 59s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s

This commit is contained in:
2026-05-15 11:03:01 +02:00
parent 3808339711
commit 0516dbe743
32 changed files with 824 additions and 495 deletions

View File

@@ -68,6 +68,18 @@ export function Footer({ companyInfo }: FooterProps) {
<p className="font-medium text-neutral-200 group-hover/contact:text-primary transition-colors truncate">{companyInfo.contactPhone}</p>
</a>
</div>
<div className="flex gap-4 p-4 rounded-2xl bg-white/[0.03] border border-white/[0.05] hover:bg-white/[0.05] transition-colors">
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center shrink-0">
<svg className="w-5 h-5 text-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
</div>
<div>
<p className="text-xs text-neutral-400 uppercase tracking-wider mb-1 font-heading">{locale === 'de' ? 'Öffnungszeiten' : 'Opening Hours'}</p>
<div className="text-sm font-medium text-neutral-200">
<p>{locale === 'de' ? 'Mo. Fr.' : 'Mon. Fri.'}: 07:00 17:00 {locale === 'de' ? 'Uhr' : ''}</p>
<p className="text-white/50 mt-0.5">{locale === 'de' ? 'Sa. So.' : 'Sat. Sun.'}: {locale === 'de' ? 'Geschlossen' : 'Closed'}</p>
</div>
</div>
</div>
</div>
)}
</div>
@@ -86,6 +98,7 @@ export function Footer({ companyInfo }: FooterProps) {
{[
{ label: locale === 'de' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/kompetenzen` },
{ label: locale === 'de' ? 'Über uns' : 'About us', href: `/${locale}/ueber-uns` },
{ label: locale === 'de' ? 'Unser Team' : 'Our Team', href: `/${locale}/team` },
{ label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },
{ label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/karriere` },
{ label: locale === 'de' ? 'Messen & Events' : 'Fairs & Events', href: `/${locale}/messen` },

View File

@@ -4,12 +4,13 @@ import * as React from 'react';
import { TransitionLink } from '@/components/ui/TransitionLink';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { motion, useScroll, useTransform } from 'framer-motion';
import { motion, useScroll, useTransform, AnimatePresence } from 'framer-motion';
import { LanguageSwitcher } from './LanguageSwitcher';
export interface NavLink {
label: string;
url: string;
children?: NavLink[];
}
interface HeaderProps {
@@ -71,7 +72,7 @@ export function Header({ navLinks }: HeaderProps) {
}`}
>
<div
className={`relative overflow-hidden w-full max-w-7xl mx-auto transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] flex items-center justify-between ${
className={`relative w-full max-w-7xl mx-auto transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] flex items-center justify-between ${
isSolidMode
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-6 md:px-8'
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'
@@ -114,40 +115,15 @@ export function Header({ navLinks }: HeaderProps) {
<div className="flex items-center gap-2 md:gap-4 ml-auto md:ml-0">
<nav className="relative z-10 hidden md:flex items-center gap-8 lg:gap-10">
{navLinks && navLinks.length > 0 && navLinks.map((link) => {
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
? `/${currentLocale}${link.url}`
: link.url;
const isActive = pathname === mappedUrl || (mappedUrl !== `/${currentLocale}` && pathname?.startsWith(`${mappedUrl}/`));
return (
<TransitionLink
key={link.url}
href={mappedUrl}
className={`relative font-bold text-xs lg:text-sm uppercase tracking-widest transition-colors duration-300 group py-2 select-none ${
isSolidMode
? (isActive ? 'text-primary' : 'text-text-primary hover:text-primary')
: (isActive ? 'text-white' : 'text-white/80 hover:text-white')
}`}
>
{link.label}
{/* Framer Motion Active Indicator */}
{isActive && (
<motion.div
layoutId="header-active-indicator"
className={`absolute -bottom-1 left-0 right-0 h-0.5 rounded-full ${isSolidMode ? 'bg-primary' : 'bg-white'}`}
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
/>
)}
{/* Subtle hover underline */}
<span className={`absolute -bottom-1 left-1/2 w-0 h-0.5 -translate-x-1/2 rounded-full transition-all duration-300 ease-out group-hover:w-full ${isSolidMode ? 'bg-primary/30' : 'bg-white/30'} ${isActive ? 'opacity-0' : 'opacity-100'}`} />
</TransitionLink>
);
})}
{navLinks && navLinks.length > 0 && navLinks.map((link) => (
<NavItem
key={link.url}
link={link}
currentLocale={currentLocale}
pathname={pathname}
isSolidMode={isSolidMode}
/>
))}
</nav>
{/* Right side elements (Language Switcher always visible, Contact button desktop only) */}
@@ -180,3 +156,83 @@ export function Header({ navLinks }: HeaderProps) {
);
}
function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) {
const [isHovered, setIsHovered] = React.useState(false);
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
? `/${currentLocale}${link.url}`
: link.url;
const isActive = pathname === mappedUrl || (mappedUrl !== `/${currentLocale}` && pathname?.startsWith(`${mappedUrl}/`));
return (
<div
className="relative group py-2"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<TransitionLink
href={mappedUrl}
className={`relative font-bold text-xs lg:text-sm uppercase tracking-widest transition-colors duration-300 group flex items-center gap-1.5 select-none ${
isSolidMode
? (isActive ? 'text-primary' : 'text-text-primary hover:text-primary')
: (isActive ? 'text-white' : 'text-white/80 hover:text-white')
}`}
>
{link.label}
{link.children && (
<svg className={`w-3.5 h-3.5 transition-transform duration-300 ${isHovered ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19 9l-7 7-7-7" />
</svg>
)}
{/* Framer Motion Active Indicator */}
{isActive && !link.children && (
<motion.div
layoutId="header-active-indicator"
className={`absolute -bottom-1 left-0 right-0 h-0.5 rounded-full ${isSolidMode ? 'bg-primary' : 'bg-white'}`}
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
/>
)}
{/* Subtle hover underline */}
<span className={`absolute -bottom-1 left-1/2 w-0 h-0.5 -translate-x-1/2 rounded-full transition-all duration-300 ease-out group-hover:w-full ${isSolidMode ? 'bg-primary/30' : 'bg-white/30'} ${isActive ? 'opacity-0' : 'opacity-100'}`} />
</TransitionLink>
<AnimatePresence>
{isHovered && link.children && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
className="absolute top-full left-1/2 -translate-x-1/2 pt-4 w-56"
>
<div className={`p-2 rounded-3xl shadow-[0_20px_40px_-15px_rgba(0,0,0,0.1)] border ${isSolidMode ? 'bg-white/95 border-neutral-200/60' : 'bg-[#050B14]/90 border-white/10'} backdrop-blur-xl flex flex-col gap-1`}>
{link.children.map((child) => {
const childUrl = child.url.startsWith('/') && !child.url.match(/^\/(en|de)/)
? `/${currentLocale}${child.url}`
: child.url;
const isChildActive = pathname === childUrl;
return (
<TransitionLink
key={child.url}
href={childUrl}
className={`block px-4 py-3 rounded-2xl text-sm font-medium transition-all ${
isSolidMode
? isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-600 hover:bg-neutral-100 hover:text-primary'
: isChildActive ? 'bg-white/10 text-white' : 'text-neutral-300 hover:bg-white/5 hover:text-white'
}`}
>
{child.label}
</TransitionLink>
);
})}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}