feat(ui): finalize E-TIB modernization with footer redesign, video optimization, and TS fixes
Former-commit-id: 67ac02c8404cc66893fdf97308574701cca6000c
This commit is contained in:
@@ -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>© {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>© {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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
84
components/layout/LanguageSwitcher.tsx
Normal file
84
components/layout/LanguageSwitcher.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user