feat(ui): finalize E-TIB modernization with footer redesign, video optimization, and TS fixes

Former-commit-id: 67ac02c8404cc66893fdf97308574701cca6000c
This commit is contained in:
2026-05-07 10:43:20 +02:00
parent f2fdea96ec
commit 5439df72ea
3712 changed files with 932332 additions and 1867 deletions

View File

@@ -1,6 +1,8 @@
import Link from 'next/link';
import Image from 'next/image';
import { useLocale } from 'next-intl';
import { ShieldCheck, Leaf, Lock, Accessibility, Zap } from 'lucide-react';
import { LanguageSwitcher } from './LanguageSwitcher';
interface CompanyInfo {
contactEmail: string;
@@ -16,49 +18,116 @@ export function Footer({ companyInfo }: FooterProps) {
const locale = useLocale();
return (
<footer className="bg-neutral-dark text-neutral-light py-16">
<div className="container grid grid-cols-1 md:grid-cols-4 gap-12">
<div className="col-span-1 md:col-span-2">
<Link href={`/${locale}`} className="relative block h-14 w-56 mb-6">
<footer className="bg-neutral-dark text-neutral-light py-16 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/10 to-transparent" />
<div className="container grid grid-cols-1 md:grid-cols-4 lg:grid-cols-12 gap-12 lg:gap-8 mb-12">
{/* Brand Column */}
<div className="col-span-1 md:col-span-2 lg:col-span-4 space-y-6">
<Link href={`/${locale}`} className="relative block h-14 w-48 mb-6 group">
<Image
src="/assets/logo-white.png"
alt="E-TIB Gruppe"
fill
className="object-contain object-left"
className="object-contain object-left transition-transform duration-500 group-hover:scale-105"
/>
</Link>
<p className="text-text-light max-w-sm mb-6">
Die Experten für Kabeltiefbau, Horizontalspülbohrungen, komplexe Querungen und umfassende Infrastrukturprojekte.
<p className="text-white/60 text-base leading-relaxed max-w-sm">
{locale === 'de'
? 'Die Experten für Kabeltiefbau, Horizontalspülbohrungen und umfassende Infrastrukturprojekte. Qualität und Zuverlässigkeit aus Guben.'
: 'Experts in cable construction, horizontal directional drilling, and comprehensive infrastructure projects. Quality and reliability from Guben.'}
</p>
{companyInfo && (
<div className="text-text-light text-sm space-y-2 mt-6 border-t border-white/10 pt-6 max-w-xs">
<p className="whitespace-pre-line">{companyInfo.address}</p>
<p>Email: <a href={`mailto:${companyInfo.contactEmail}`} className="hover:text-white">{companyInfo.contactEmail}</a></p>
<p>Tel: <a href={`tel:${companyInfo.contactPhone.replace(/\\s+/g, '')}`} className="hover:text-white">{companyInfo.contactPhone}</a></p>
<div className="text-white/70 text-sm space-y-3 mt-8 border-t border-white/10 pt-6 max-w-xs">
<p className="whitespace-pre-line font-medium">{companyInfo.address}</p>
<p>Email: <a href={`mailto:${companyInfo.contactEmail}`} className="hover:text-primary transition-colors">{companyInfo.contactEmail}</a></p>
<p>Tel: <a href={`tel:${companyInfo.contactPhone.replace(/\s+/g, '')}`} className="hover:text-primary transition-colors">{companyInfo.contactPhone}</a></p>
</div>
)}
</div>
<div>
<h4 className="font-heading font-semibold text-lg mb-4">Unternehmen</h4>
<ul className="space-y-2">
<li><Link href={`/${locale}/kompetenzen`} className="text-text-light hover:text-primary-light transition-colors">Kompetenzen</Link></li>
<li><Link href={`/${locale}/ueber-uns`} className="text-text-light hover:text-primary-light transition-colors">Über uns</Link></li>
<li><Link href={`/${locale}/karriere`} className="text-text-light hover:text-primary-light transition-colors">Karriere / Jobs</Link></li>
<li><Link href={`/${locale}/kontakt`} className="text-text-light hover:text-primary-light transition-colors">Kontakt</Link></li>
{/* Company Column */}
<div className="col-span-1 lg:col-span-3 lg:col-start-6">
<h4 className="font-heading font-bold uppercase tracking-widest text-xs mb-6 text-white/40">
{locale === 'de' ? 'Unternehmen' : 'Company'}
</h4>
<ul className="space-y-4">
<li>
<Link href={`/${locale}/kompetenzen`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Kompetenzen' : 'Competencies'}
</Link>
</li>
<li>
<Link href={`/${locale}/ueber-uns`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Über uns' : 'About us'}
</Link>
</li>
<li>
<Link href={`/${locale}/karriere`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Karriere' : 'Career'}
</Link>
</li>
<li>
<Link href={`/${locale}/messen`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Messen & Events' : 'Fairs & Events'}
</Link>
</li>
<li>
<Link href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Kontakt' : 'Contact'}
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-heading font-semibold text-lg mb-4">Rechtliches</h4>
<ul className="space-y-2">
<li><Link href={`/${locale}/impressum`} className="text-text-light hover:text-primary-light transition-colors">Impressum</Link></li>
<li><Link href={`/${locale}/datenschutz`} className="text-text-light hover:text-primary-light transition-colors">Datenschutz</Link></li>
<li><Link href={`/${locale}/${locale === 'de' ? 'agb' : 'terms'}`} className="text-text-light hover:text-primary-light transition-colors">{locale === 'de' ? 'AGB' : 'Terms'}</Link></li>
<li><button className="text-text-light hover:text-primary-light transition-colors">Cookie-Einstellungen</button></li>
{/* Legal Column */}
<div className="col-span-1 lg:col-span-3">
<h4 className="font-heading font-bold uppercase tracking-widest text-xs mb-6 text-white/40">
{locale === 'de' ? 'Rechtliches' : 'Legal'}
</h4>
<ul className="space-y-4">
<li>
<Link href={`/${locale}/impressum`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Impressum' : 'Imprint'}
</Link>
</li>
<li>
<Link href={`/${locale}/datenschutz`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'Datenschutz' : 'Privacy Policy'}
</Link>
</li>
<li>
<Link href={`/${locale}/${locale === 'de' ? 'agb' : 'terms'}`} className="text-white/70 hover:text-white hover:translate-x-1 inline-block transition-all">
{locale === 'de' ? 'AGB' : 'Terms'}
</Link>
</li>
</ul>
</div>
</div>
<div className="container mt-12 pt-8 border-t border-neutral-dark/40 border-t-white/10 text-center text-text-light text-sm">
<p>&copy; {new Date().getFullYear()} E-TIB GmbH. Alle Rechte vorbehalten.</p>
<div className="container">
<div className="pt-8 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-6 text-white/50 text-sm">
<p>&copy; {new Date().getFullYear()} E-TIB GmbH. {locale === 'de' ? 'Alle Rechte vorbehalten.' : 'All rights reserved.'}</p>
<div className="flex items-center gap-4">
<LanguageSwitcher isSolidMode={false} />
</div>
</div>
{/* Quality Badges */}
<div className="pt-8 mt-8 border-t border-white/5 flex flex-col md:flex-row justify-between items-center gap-6 text-white/30 text-xs">
<div>
<a href="https://mintel.me" target="_blank" rel="noopener noreferrer" className="hover:text-white/60 transition-colors">
Website developed by Mintel
</a>
</div>
<div className="flex flex-wrap justify-center gap-x-6 gap-y-3">
<div className="flex items-center gap-1.5"><ShieldCheck className="w-3.5 h-3.5" /><span>SSL Secured</span></div>
<div className="flex items-center gap-1.5"><Leaf className="w-3.5 h-3.5" /><span>Green Hosting</span></div>
<div className="flex items-center gap-1.5"><Lock className="w-3.5 h-3.5" /><span>DSGVO Compliant</span></div>
<div className="flex items-center gap-1.5"><Accessibility className="w-3.5 h-3.5" /><span>WCAG</span></div>
<div className="flex items-center gap-1.5"><Zap className="w-3.5 h-3.5" /><span>PageSpeed 90+</span></div>
</div>
</div>
</div>
</footer>
);

View File

@@ -5,6 +5,7 @@ import Link from 'next/link';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { motion, AnimatePresence } from 'framer-motion';
import { LanguageSwitcher } from './LanguageSwitcher';
export interface NavLink {
label: string;
@@ -18,6 +19,7 @@ interface HeaderProps {
export function Header({ navLinks }: HeaderProps) {
const [isScrolled, setIsScrolled] = React.useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const [forceSolid, setForceSolid] = React.useState(false);
const pathname = usePathname() || '/';
// Determine current locale from pathname
@@ -35,27 +37,39 @@ export function Header({ navLinks }: HeaderProps) {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Check for forced solid header (used by error pages, legal pages without hero, etc.)
const checkSolid = () => {
setForceSolid(document.body.getAttribute('data-force-solid-header') === 'true');
};
checkSolid();
// Set up observer for client-side navigation changes
const observer = new MutationObserver(checkSolid);
observer.observe(document.body, { attributes: true, attributeFilter: ['data-force-solid-header'] });
// Re-check when pathname changes
checkSolid();
return () => {
window.removeEventListener('scroll', handleScroll);
observer.disconnect();
};
}, [pathname]);
// Close mobile menu on route change
React.useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
const LanguageSwitcher = ({ mobile = false }) => (
<div className={`flex items-center gap-2 ${mobile ? 'text-xl' : 'text-sm font-bold'} ${mobile ? 'text-neutral-dark' : isScrolled ? 'text-text-primary' : 'text-neutral-light'}`}>
<Link href={getSwitchedUrl('de')} className={`transition-colors hover:text-primary ${currentLocale === 'de' ? 'text-primary' : ''}`}>DE</Link>
<span className="opacity-50">|</span>
<Link href={getSwitchedUrl('en')} className={`transition-colors hover:text-primary ${currentLocale === 'en' ? 'text-primary' : ''}`}>EN</Link>
</div>
);
const isSolidMode = isScrolled || forceSolid;
return (
<>
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out ${
isScrolled
isSolidMode
? 'bg-neutral-light/90 backdrop-blur-md shadow-sm py-4'
: 'bg-transparent py-6'
}`}
@@ -63,7 +77,7 @@ export function Header({ navLinks }: HeaderProps) {
<div className="container flex items-center justify-between">
<Link href={`/${currentLocale}`} className="relative flex items-center h-12 w-48 z-50">
<Image
src={(isScrolled || isMobileMenuOpen) ? "/assets/logo.png" : "/assets/logo-white.png"}
src={(isSolidMode || isMobileMenuOpen) ? "/assets/logo.png" : "/assets/logo-white.png"}
alt="E-TIB Gruppe"
fill
className="object-contain object-left"
@@ -83,7 +97,7 @@ export function Header({ navLinks }: HeaderProps) {
key={link.url}
href={mappedUrl}
className={`font-semibold text-sm uppercase tracking-widest transition-all duration-300 ${
isScrolled
isSolidMode
? 'text-text-primary hover:text-primary'
: 'text-neutral-light hover:text-primary-light'
}`}
@@ -92,8 +106,8 @@ export function Header({ navLinks }: HeaderProps) {
</Link>
);
})}
<div className="h-4 w-px bg-white/20 mx-2" />
<LanguageSwitcher />
<div className={`h-4 w-px mx-2 transition-colors duration-300 ${isSolidMode ? 'bg-neutral-300' : 'bg-white/20'}`} />
<LanguageSwitcher isSolidMode={isSolidMode} />
<Link
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
className="rounded-xl bg-primary hover:bg-primary-dark text-neutral-light px-8 py-3 font-bold text-sm uppercase tracking-wider transition-all shadow-xl hover:shadow-primary/20 hover:-translate-y-0.5 active:translate-y-0"
@@ -108,7 +122,7 @@ export function Header({ navLinks }: HeaderProps) {
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Toggle menu"
>
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={(isScrolled || isMobileMenuOpen) ? "currentColor" : "white"} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={(isSolidMode || isMobileMenuOpen) ? "currentColor" : "white"} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
{isMobileMenuOpen ? (
<>
<line x1="18" y1="6" x2="6" y2="18"></line>

View File

@@ -0,0 +1,84 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
interface LanguageSwitcherProps {
mobile?: boolean;
isSolidMode?: boolean;
}
export function LanguageSwitcher({ mobile = false, isSolidMode = false }: LanguageSwitcherProps) {
const pathname = usePathname() || '/';
const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';
const getSwitchedUrl = (newLocale: string) => {
const pathWithoutLocale = pathname.replace(/^\/(en|de)/, '');
return `/${newLocale}${pathWithoutLocale === '' ? '' : pathWithoutLocale}`;
};
const locales = [
{ code: 'de', label: 'DE', fullLabel: 'Deutsch', flag: '🇩🇪' },
{ code: 'en', label: 'EN', fullLabel: 'English', flag: '🇬🇧' },
] as const;
if (mobile) {
return (
<div className="flex flex-col gap-3">
{locales.map((loc) => (
<Link
key={loc.code}
href={getSwitchedUrl(loc.code)}
className={`flex items-center gap-3 px-5 py-3 rounded-xl text-lg font-semibold transition-all duration-300 ${
currentLocale === loc.code
? 'bg-primary/10 text-primary border border-primary/20'
: 'text-text-secondary hover:bg-neutral-50 border border-transparent'
}`}
>
<span className="text-2xl" aria-hidden="true">{loc.flag}</span>
<span>{loc.fullLabel}</span>
{currentLocale === loc.code && (
<svg className="w-5 h-5 ml-auto text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
)}
</Link>
))}
</div>
);
}
// Desktop styles dependent on isSolidMode
const containerClass = isSolidMode
? 'bg-neutral-100 border-neutral-200'
: 'bg-white/10 backdrop-blur-sm border-white/10';
return (
<div className={`flex items-center rounded-full p-0.5 border ${containerClass} transition-colors duration-300`}>
{locales.map((loc) => {
const isActive = currentLocale === loc.code;
let linkClass = '';
if (isActive) {
linkClass = 'bg-white text-primary shadow-sm';
} else {
linkClass = isSolidMode
? 'text-text-secondary hover:text-primary'
: 'text-white/70 hover:text-white';
}
return (
<Link
key={loc.code}
href={getSwitchedUrl(loc.code)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-bold uppercase tracking-wider transition-all duration-300 ${linkClass}`}
aria-label={`Switch to ${loc.fullLabel}`}
>
<span className="text-sm" aria-hidden="true">{loc.flag}</span>
<span>{loc.label}</span>
</Link>
);
})}
</div>
);
}