Compare commits
5 Commits
cbb7855804
...
baec7cc94a
| Author | SHA1 | Date | |
|---|---|---|---|
| baec7cc94a | |||
| b43b1c4314 | |||
| 615757963c | |||
| 0094871358 | |||
| 8fef3b6c7f |
@@ -51,38 +51,40 @@ export default async function BlogIndex({ params: { locale } }: BlogIndexProps)
|
||||
return (
|
||||
<div className="bg-neutral-light min-h-screen">
|
||||
{/* Hero Section - Immersive Magazine Feel */}
|
||||
<section className="relative h-[50vh] md:h-[70vh] min-h-[400px] md:min-h-[600px] flex items-center overflow-hidden bg-primary-dark">
|
||||
{featuredPost && featuredPost.frontmatter.featuredImage && (
|
||||
<>
|
||||
<img
|
||||
src={featuredPost.frontmatter.featuredImage}
|
||||
alt={featuredPost.frontmatter.title}
|
||||
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-4xl animate-slide-up">
|
||||
<Badge variant="saturated" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
|
||||
{featuredPost && (
|
||||
<>
|
||||
<h1 className="text-3xl md:text-6xl font-extrabold text-white mb-4 md:mb-8 leading-[1.1] line-clamp-3 md:line-clamp-none">
|
||||
{featuredPost.frontmatter.title}
|
||||
</h1>
|
||||
<p className="text-base md:text-xl text-white/80 mb-6 md:mb-10 line-clamp-2 md:line-clamp-2 max-w-2xl">
|
||||
{featuredPost.frontmatter.excerpt}
|
||||
</p>
|
||||
<Button href={`/${locale}/blog/${featuredPost.slug}`} variant="accent" size="lg" className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl">
|
||||
{t('readFullArticle')}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
<Reveal>
|
||||
<section className="relative h-[50vh] md:h-[70vh] min-h-[400px] md:min-h-[600px] flex items-center overflow-hidden bg-primary-dark">
|
||||
{featuredPost && featuredPost.frontmatter.featuredImage && (
|
||||
<>
|
||||
<img
|
||||
src={featuredPost.frontmatter.featuredImage}
|
||||
alt={featuredPost.frontmatter.title}
|
||||
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-4xl animate-slide-up">
|
||||
<Badge variant="saturated" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
|
||||
{featuredPost && (
|
||||
<>
|
||||
<h1 className="text-3xl md:text-6xl font-extrabold text-white mb-4 md:mb-8 leading-[1.1] line-clamp-3 md:line-clamp-none">
|
||||
{featuredPost.frontmatter.title}
|
||||
</h1>
|
||||
<p className="text-base md:text-xl text-white/80 mb-6 md:mb-10 line-clamp-2 md:line-clamp-2 max-w-2xl">
|
||||
{featuredPost.frontmatter.excerpt}
|
||||
</p>
|
||||
<Button href={`/${locale}/blog/${featuredPost.slug}`} variant="accent" size="lg" className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl">
|
||||
{t('readFullArticle')}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
|
||||
<Section className="bg-neutral-light py-12 md:py-28">
|
||||
<Container>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import ContactForm from '@/components/ContactForm';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import Reveal from '@/components/Reveal';
|
||||
import { Container, Heading, Section } from '@/components/ui';
|
||||
import { Metadata } from 'next';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Suspense } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const LeafletMap = dynamic(() => import('@/components/LeafletMap'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
@@ -27,30 +27,48 @@ export async function generateMetadata({ params: { locale } }: ContactPageProps)
|
||||
title: t('title'),
|
||||
description: t('subtitle'),
|
||||
alternates: {
|
||||
canonical: `/${locale}/contact`,
|
||||
canonical: `https://klz-cables.com/${locale}/contact`,
|
||||
languages: {
|
||||
'de': '/de/contact',
|
||||
'en': '/en/contact',
|
||||
'x-default': '/en/contact',
|
||||
'de-DE': '/de/contact',
|
||||
'en-US': '/en/contact',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: `${t('title')} | KLZ Cables`,
|
||||
description: t('subtitle'),
|
||||
url: `https://klz-cables.com/${locale}/contact`,
|
||||
siteName: 'KLZ Cables',
|
||||
images: [
|
||||
{
|
||||
url: 'https://klz-cables.com/logo.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'KLZ Cables Contact',
|
||||
},
|
||||
],
|
||||
locale: `${locale.toUpperCase()}_DE`,
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: `${t('title')} | KLZ Cables`,
|
||||
description: t('subtitle'),
|
||||
images: ['https://klz-cables.com/logo.png'],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function ContactPage() {
|
||||
const t = useTranslations('Contact');
|
||||
const locale = t('locale') || 'en'; // Fallback if needed, but usually passed via params
|
||||
export async function generateStaticParams() {
|
||||
return [{ locale: 'de' }, { locale: 'en' }];
|
||||
}
|
||||
|
||||
export default async function ContactPage({ params }: ContactPageProps) {
|
||||
const { locale } = params;
|
||||
const t = await getTranslations({ locale, namespace: 'Contact' });
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-neutral-light">
|
||||
<JsonLd
|
||||
@@ -110,21 +128,23 @@ export default function ContactPage() {
|
||||
}}
|
||||
/>
|
||||
{/* Hero Section */}
|
||||
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
|
||||
</div>
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-4xl animate-slide-up">
|
||||
<Heading level={1} subtitle={t('heroSubtitle')} className="text-white mb-4 md:mb-6">
|
||||
<span className="text-white">{t('title')}</span>
|
||||
</Heading>
|
||||
<p className="text-lg md:text-xl text-white/70 leading-relaxed max-w-2xl">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
<Reveal>
|
||||
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-4xl">
|
||||
<Heading level={1} subtitle={t('heroSubtitle')} className="text-white mb-4 md:mb-6">
|
||||
<span className="text-white">{t('title')}</span>
|
||||
</Heading>
|
||||
<p className="text-lg md:text-xl text-white/70 leading-relaxed max-w-2xl">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
|
||||
<Section className="bg-neutral-light -mt-8 md:-mt-20 relative z-20 py-12 md:py-28">
|
||||
<Container>
|
||||
@@ -194,7 +214,9 @@ export default function ContactPage() {
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="lg:col-span-7">
|
||||
<ContactForm />
|
||||
<Suspense fallback={<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl"></div>}>
|
||||
<ContactForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -202,11 +224,15 @@ export default function ContactPage() {
|
||||
|
||||
{/* Map Section */}
|
||||
<section className="h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
|
||||
<LeafletMap
|
||||
address={t('info.address')}
|
||||
lat={48.8164}
|
||||
lng={9.4168}
|
||||
/>
|
||||
<Suspense fallback={<div className="h-full w-full bg-neutral-medium animate-pulse flex items-center justify-center">
|
||||
<div className="text-primary font-medium">Loading Map...</div>
|
||||
</div>}>
|
||||
<LeafletMap
|
||||
address={t('info.address')}
|
||||
lat={47.8407}
|
||||
lng={11.1421}
|
||||
/>
|
||||
</Suspense>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -166,25 +166,27 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
</Section>
|
||||
|
||||
{/* Technical Support CTA */}
|
||||
<Section className="bg-white py-12 md:py-28">
|
||||
<Container>
|
||||
<div className="bg-primary-dark rounded-[32px] md:rounded-[64px] p-6 md:p-20 lg:p-24 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-1/2 h-full bg-accent/5 -skew-x-12 translate-x-1/4" />
|
||||
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-6 md:gap-12">
|
||||
<div className="max-w-2xl text-center lg:text-left">
|
||||
<h2 className="text-2xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-8 tracking-tight">{t('cta.title')}</h2>
|
||||
<p className="text-base md:text-xl text-white/70 leading-relaxed">
|
||||
{t('cta.description')}
|
||||
</p>
|
||||
<Reveal>
|
||||
<Section className="bg-white py-12 md:py-28">
|
||||
<Container>
|
||||
<div className="bg-primary-dark rounded-[32px] md:rounded-[64px] p-6 md:p-20 lg:p-24 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-1/2 h-full bg-accent/5 -skew-x-12 translate-x-1/4" />
|
||||
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-6 md:gap-12">
|
||||
<div className="max-w-2xl text-center lg:text-left">
|
||||
<h2 className="text-2xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-8 tracking-tight">{t('cta.title')}</h2>
|
||||
<p className="text-base md:text-xl text-white/70 leading-relaxed">
|
||||
{t('cta.description')}
|
||||
</p>
|
||||
</div>
|
||||
<Button href={`/${params.locale}/contact`} variant="accent" size="lg" className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl">
|
||||
{t('cta.button')}
|
||||
<span className="ml-4 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Button href={`/${params.locale}/contact`} variant="accent" size="lg" className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl">
|
||||
{t('cta.button')}
|
||||
<span className="ml-4 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</Reveal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import { getBreadcrumbSchema, SITE_URL } from '@/lib/schema';
|
||||
import { Section, Container, Heading, Badge, Button } from '@/components/ui';
|
||||
import Image from 'next/image';
|
||||
import Lightbox from '@/components/Lightbox';
|
||||
import Reveal from '@/components/Reveal';
|
||||
import Gallery from '@/components/team/Gallery';
|
||||
|
||||
interface TeamPageProps {
|
||||
params: {
|
||||
@@ -42,19 +39,8 @@ export async function generateMetadata({ params: { locale } }: TeamPageProps): P
|
||||
};
|
||||
}
|
||||
|
||||
export default function TeamPage() {
|
||||
const t = useTranslations('Team');
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = React.useState(false);
|
||||
const [lightboxIndex, setLightboxIndex] = React.useState(0);
|
||||
|
||||
const teamGalleryImages = [
|
||||
'/uploads/2024/12/DSC07539-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07460-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07469-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07433-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07387-Large-600x400.webp'
|
||||
];
|
||||
export default async function TeamPage({ params: { locale } }: TeamPageProps) {
|
||||
const t = await getTranslations({ locale, namespace: 'Team' });
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-neutral-light">
|
||||
@@ -99,28 +85,30 @@ export default function TeamPage() {
|
||||
}}
|
||||
/>
|
||||
{/* Hero Section */}
|
||||
<section className="relative flex items-center justify-center overflow-hidden bg-primary-dark pt-32 pb-24 md:pt-[14%] md:pb-[12%]">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07655-Large.webp"
|
||||
alt="KLZ Team"
|
||||
fill
|
||||
className="object-cover scale-105 animate-slow-zoom opacity-30 md:opacity-40"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
|
||||
</div>
|
||||
|
||||
<Container className="relative z-10 text-center text-white max-w-5xl animate-slide-up">
|
||||
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
|
||||
<h1 className="text-3xl md:text-6xl lg:text-7xl font-extrabold tracking-tight leading-[1.1] mb-4 md:mb-8">
|
||||
{t('hero.subtitle')}
|
||||
</h1>
|
||||
<p className="text-lg md:text-2xl text-white/70 font-medium italic">
|
||||
{t('hero.title')}
|
||||
</p>
|
||||
</Container>
|
||||
</section>
|
||||
<Reveal>
|
||||
<section className="relative flex items-center justify-center overflow-hidden bg-primary-dark pt-32 pb-24 md:pt-[14%] md:pb-[12%]">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07655-Large.webp"
|
||||
alt="KLZ Team"
|
||||
fill
|
||||
className="object-cover scale-105 animate-slow-zoom opacity-30 md:opacity-40"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
|
||||
</div>
|
||||
|
||||
<Container className="relative z-10 text-center text-white max-w-5xl">
|
||||
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
|
||||
<h1 className="text-3xl md:text-6xl lg:text-7xl font-extrabold tracking-tight leading-[1.1] mb-4 md:mb-8">
|
||||
{t('hero.subtitle')}
|
||||
</h1>
|
||||
<p className="text-lg md:text-2xl text-white/70 font-medium italic">
|
||||
{t('hero.title')}
|
||||
</p>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
|
||||
{/* Michael Bodemer Section - Sticky Narrative Split Layout */}
|
||||
<section className="relative bg-white overflow-hidden">
|
||||
@@ -152,73 +140,75 @@ export default function TeamPage() {
|
||||
</Button>
|
||||
</div>
|
||||
</Reveal>
|
||||
<div className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1 lg:order-2">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07768-Large.webp"
|
||||
alt={t('michael.name')}
|
||||
fill
|
||||
<Reveal className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1 lg:order-2">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07768-Large.webp"
|
||||
alt={t('michael.name')}
|
||||
fill
|
||||
className="object-cover scale-105 hover:scale-100 transition-transform duration-1000"
|
||||
sizes="(max-width: 1024px) 100vw, 50vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-primary-dark/60 lg:bg-gradient-to-r lg:from-primary-dark/20 to-transparent" />
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Legacy Section - Immersive Background */}
|
||||
<section className="relative py-16 md:py-48 bg-primary-dark text-white overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/1694273920124-copy.webp"
|
||||
alt={t('legacy.subtitle')}
|
||||
fill
|
||||
className="object-cover opacity-20 md:opacity-30 scale-110 animate-slow-zoom"
|
||||
sizes="100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary-dark/60 mix-blend-multiply" />
|
||||
</div>
|
||||
<Container className="relative z-10">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16 items-center">
|
||||
<div className="lg:col-span-6">
|
||||
<Heading level={2} subtitle={t('legacy.subtitle')} className="text-white mb-6 md:mb-10">
|
||||
<span className="text-white">{t('legacy.title')}</span>
|
||||
</Heading>
|
||||
<div className="space-y-4 md:space-y-8 text-base md:text-2xl text-white/80 leading-relaxed font-medium">
|
||||
<p className="border-l-4 border-accent pl-5 md:pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl md:rounded-r-2xl">
|
||||
{t('legacy.p1')}
|
||||
</p>
|
||||
<p className="pl-6 md:pl-9 line-clamp-3 md:line-clamp-none">
|
||||
{t('legacy.p2')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:col-span-6 grid grid-cols-2 md:grid-cols-2 gap-3 md:gap-6">
|
||||
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
|
||||
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.expertise')}</div>
|
||||
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.expertiseDesc')}</div>
|
||||
</div>
|
||||
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
|
||||
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.network')}</div>
|
||||
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.networkDesc')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Reveal>
|
||||
<section className="relative py-16 md:py-48 bg-primary-dark text-white overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/1694273920124-copy.webp"
|
||||
alt={t('legacy.subtitle')}
|
||||
fill
|
||||
className="object-cover opacity-20 md:opacity-30 scale-110 animate-slow-zoom"
|
||||
sizes="100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary-dark/60 mix-blend-multiply" />
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
<Container className="relative z-10">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16 items-center">
|
||||
<div className="lg:col-span-6">
|
||||
<Heading level={2} subtitle={t('legacy.subtitle')} className="text-white mb-6 md:mb-10">
|
||||
<span className="text-white">{t('legacy.title')}</span>
|
||||
</Heading>
|
||||
<div className="space-y-4 md:space-y-8 text-base md:text-2xl text-white/80 leading-relaxed font-medium">
|
||||
<p className="border-l-4 border-accent pl-5 md:pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl md:rounded-r-2xl">
|
||||
{t('legacy.p1')}
|
||||
</p>
|
||||
<p className="pl-6 md:pl-9 line-clamp-3 md:line-clamp-none">
|
||||
{t('legacy.p2')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:col-span-6 grid grid-cols-2 md:grid-cols-2 gap-3 md:gap-6">
|
||||
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
|
||||
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.expertise')}</div>
|
||||
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.expertiseDesc')}</div>
|
||||
</div>
|
||||
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
|
||||
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.network')}</div>
|
||||
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.networkDesc')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</Reveal>
|
||||
|
||||
{/* Klaus Mintel Section - Reversed Split Layout */}
|
||||
<section className="relative bg-white overflow-hidden">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<div className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07963-Large.webp"
|
||||
alt={t('klaus.name')}
|
||||
fill
|
||||
<Reveal className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07963-Large.webp"
|
||||
alt={t('klaus.name')}
|
||||
fill
|
||||
className="object-cover scale-105 hover:scale-100 transition-transform duration-1000"
|
||||
sizes="(max-width: 1024px) 100vw, 50vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-white/60 lg:bg-gradient-to-l lg:from-primary-dark/20 to-transparent" />
|
||||
</div>
|
||||
</Reveal>
|
||||
<Reveal className="w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center bg-neutral-light text-saturated relative order-2">
|
||||
<div className="absolute top-0 left-0 w-32 h-full bg-saturated/5 skew-x-12 -translate-x-1/2" />
|
||||
<div className="relative z-10">
|
||||
@@ -287,37 +277,9 @@ export default function TeamPage() {
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Gallery Section - Premium Treatment */}
|
||||
<Section className="bg-primary-dark py-16 md:py-32">
|
||||
<Container>
|
||||
<Heading level={2} subtitle={t('gallery.subtitle')} align="center" className="text-white mb-12 md:mb-20">
|
||||
<span className="text-white">{t('gallery.title')}</span>
|
||||
</Heading>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-6">
|
||||
{teamGalleryImages.map((src, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
setLightboxIndex(idx);
|
||||
setLightboxOpen(true);
|
||||
}}
|
||||
className="relative aspect-[3/4] rounded-2xl md:rounded-[32px] overflow-hidden group shadow-2xl cursor-pointer"
|
||||
>
|
||||
<Image src={src} alt={t('gallery.title')} fill className="object-cover transition-transform duration-1000 group-hover:scale-110" />
|
||||
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-transparent transition-all duration-500" />
|
||||
<div className="absolute inset-0 border-0 group-hover:border-[8px] md:group-hover:border-[12px] border-white/10 transition-all duration-500 pointer-events-none" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<Lightbox
|
||||
isOpen={lightboxOpen}
|
||||
images={teamGalleryImages}
|
||||
initialIndex={lightboxIndex}
|
||||
onClose={() => setLightboxOpen(false)}
|
||||
/>
|
||||
</Section>
|
||||
<Reveal>
|
||||
<Gallery />
|
||||
</Reveal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from './ui';
|
||||
@@ -70,79 +71,169 @@ export default function Header() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={headerClass}>
|
||||
<motion.header
|
||||
className={headerClass}
|
||||
initial={{ y: -100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
>
|
||||
<div className="container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl flex items-center justify-between">
|
||||
<Link href={`/${currentLocale}`} className="flex-shrink-0 group touch-target">
|
||||
<Image
|
||||
src={logoSrc}
|
||||
alt={t('home')}
|
||||
width={120}
|
||||
height={120}
|
||||
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
<motion.div
|
||||
className="flex-shrink-0 group touch-target"
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
|
||||
>
|
||||
<Link href={`/${currentLocale}`}>
|
||||
<Image
|
||||
src={logoSrc}
|
||||
alt={t('home')}
|
||||
width={120}
|
||||
height={120}
|
||||
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex items-center gap-4 md:gap-12">
|
||||
<nav className="hidden lg:flex items-center space-x-10">
|
||||
{menuItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
|
||||
className={cn(
|
||||
textColorClass,
|
||||
"hover:text-accent font-bold transition-all text-base md:text-lg tracking-tight relative group"
|
||||
)}
|
||||
<motion.div
|
||||
className="flex items-center gap-4 md:gap-12"
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={{
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.08,
|
||||
delayChildren: 0.3
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<motion.nav
|
||||
className="hidden lg:flex items-center space-x-10"
|
||||
variants={navVariants}
|
||||
>
|
||||
{menuItems.map((item, idx) => (
|
||||
<motion.div
|
||||
key={item.href}
|
||||
variants={navLinkVariants}
|
||||
custom={idx}
|
||||
>
|
||||
{item.label}
|
||||
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-300 group-hover:w-full rounded-full" />
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
|
||||
className={cn(
|
||||
textColorClass,
|
||||
"hover:text-accent font-bold transition-all text-base md:text-lg tracking-tight relative group inline-block"
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-300 group-hover:w-full rounded-full" />
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</nav>
|
||||
</motion.nav>
|
||||
|
||||
<div className={cn("hidden lg:flex items-center space-x-8", textColorClass)}>
|
||||
<div className="flex items-center space-x-4 text-xs md:text-sm font-extrabold tracking-widest uppercase">
|
||||
<Link
|
||||
href={getPathForLocale('en')}
|
||||
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
EN
|
||||
</Link>
|
||||
<div className="w-px h-4 bg-current opacity-20" />
|
||||
<Link
|
||||
href={getPathForLocale('de')}
|
||||
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
DE
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
href={`/${currentLocale}/contact`}
|
||||
variant="white"
|
||||
size="md"
|
||||
className="px-8 shadow-xl"
|
||||
<motion.div
|
||||
className={cn("hidden lg:flex items-center space-x-8", textColorClass)}
|
||||
variants={headerRightVariants}
|
||||
>
|
||||
<motion.div
|
||||
className="flex items-center space-x-4 text-xs md:text-sm font-extrabold tracking-widest uppercase"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.6 }}
|
||||
>
|
||||
{t('contact')}
|
||||
</Button>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.65 }}
|
||||
>
|
||||
<Link
|
||||
href={getPathForLocale('en')}
|
||||
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
EN
|
||||
</Link>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="w-px h-4 bg-current opacity-20"
|
||||
initial={{ scaleY: 0 }}
|
||||
animate={{ scaleY: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.7 }}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.75 }}
|
||||
>
|
||||
<Link
|
||||
href={getPathForLocale('de')}
|
||||
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
DE
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.6, type: "spring", stiffness: 400, delay: 0.7 }}
|
||||
>
|
||||
<Button
|
||||
href={`/${currentLocale}/contact`}
|
||||
variant="white"
|
||||
size="md"
|
||||
className="px-8 shadow-xl"
|
||||
>
|
||||
{t('contact')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
<motion.button
|
||||
className={cn("lg:hidden touch-target p-2 rounded-xl bg-white/10 border border-white/20 z-50", textColorClass)}
|
||||
aria-label={t('toggleMenu')}
|
||||
initial={{ scale: 0.8, opacity: 0, rotate: -180 }}
|
||||
animate={{ scale: 1, opacity: 1, rotate: 0 }}
|
||||
transition={{ duration: 0.6, type: "spring", stiffness: 300, damping: 20, delay: 0.5 }}
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
>
|
||||
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<motion.svg
|
||||
className="w-7 h-7"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0.6 }}
|
||||
>
|
||||
{isMobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
<motion.path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.7 }}
|
||||
/>
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
<motion.path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.7 }}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</motion.svg>
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Overlay */}
|
||||
@@ -150,58 +241,157 @@ export default function Header() {
|
||||
"fixed inset-0 bg-primary z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col",
|
||||
isMobileMenuOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-full pointer-events-none"
|
||||
)}>
|
||||
<div className="flex-grow flex flex-col justify-center items-center p-8 space-y-8">
|
||||
<motion.div
|
||||
className="flex-grow flex flex-col justify-center items-center p-8 space-y-8"
|
||||
initial="closed"
|
||||
animate={isMobileMenuOpen ? "open" : "closed"}
|
||||
variants={{
|
||||
open: {
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
delayChildren: 0.2
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{menuItems.map((item, idx) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
|
||||
className={cn(
|
||||
"text-2xl md:text-3xl font-extrabold text-white hover:text-accent transition-all transform",
|
||||
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"
|
||||
)}
|
||||
style={{ transitionDelay: `${idx * 100}ms` }}
|
||||
<motion.div
|
||||
key={item.href}
|
||||
variants={{
|
||||
closed: { opacity: 0, y: 50, scale: 0.9 },
|
||||
open: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: "easeOut",
|
||||
delay: idx * 0.08
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
|
||||
className="text-2xl md:text-3xl font-extrabold text-white hover:text-accent transition-all transform block py-4"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
<div className={cn(
|
||||
"pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8 transition-all duration-500 delay-500",
|
||||
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"
|
||||
)}>
|
||||
<div className="flex items-center space-x-8 text-lg md:text-xl font-extrabold tracking-widest uppercase text-white">
|
||||
<Link
|
||||
href={getPathForLocale('en')}
|
||||
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
EN
|
||||
</Link>
|
||||
<div className="w-px h-6 bg-white/20" />
|
||||
<Link
|
||||
href={getPathForLocale('de')}
|
||||
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
DE
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
href={`/${currentLocale}/contact`}
|
||||
variant="accent"
|
||||
size="lg"
|
||||
className="w-full max-w-xs py-6 text-lg md:text-xl shadow-2xl"
|
||||
<motion.div
|
||||
className="pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isMobileMenuOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
||||
transition={{ duration: 0.5, delay: 0.8 }}
|
||||
>
|
||||
<motion.div
|
||||
className="flex items-center space-x-8 text-lg md:text-xl font-extrabold tracking-widest uppercase text-white"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.9 }}
|
||||
>
|
||||
{t('contact')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3, delay: 1.0 }}
|
||||
>
|
||||
<Link
|
||||
href={getPathForLocale('en')}
|
||||
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
EN
|
||||
</Link>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="w-px h-6 bg-white/20"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.4, delay: 1.05 }}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3, delay: 1.1 }}
|
||||
>
|
||||
<Link
|
||||
href={getPathForLocale('de')}
|
||||
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
|
||||
>
|
||||
DE
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0, y: 20 }}
|
||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 20, delay: 1.2 }}
|
||||
>
|
||||
<Button
|
||||
href={`/${currentLocale}/contact`}
|
||||
variant="accent"
|
||||
size="lg"
|
||||
className="w-full max-w-xs py-6 text-lg md:text-xl shadow-2xl"
|
||||
>
|
||||
{t('contact')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Bottom Branding */}
|
||||
<div className="p-12 flex justify-center opacity-20">
|
||||
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
|
||||
</div>
|
||||
<motion.div
|
||||
className="p-12 flex justify-center opacity-20"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={isMobileMenuOpen ? { opacity: 0.2, scale: 1 } : { opacity: 0, scale: 0.8 }}
|
||||
transition={{ duration: 0.5, delay: 1.4 }}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0.5 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: "spring", stiffness: 300, delay: 1.5 }}
|
||||
>
|
||||
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</header>
|
||||
</motion.header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const navVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.06,
|
||||
delayChildren: 0.1
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const navLinkVariants = {
|
||||
hidden: { opacity: 0, y: 20, scale: 0.9 },
|
||||
visible: (idx: number) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: [0.25, 0.46, 0.45, 0.94],
|
||||
delay: idx * 0.06
|
||||
}
|
||||
})
|
||||
} as const;
|
||||
|
||||
const headerRightVariants = {
|
||||
hidden: { opacity: 0, x: 30 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: { duration: 0.6, ease: "easeOut" }
|
||||
}
|
||||
} as const;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Container, Button, Section, Heading } from '@/components/ui';
|
||||
import Scribble from '@/components/Scribble';
|
||||
@@ -9,40 +12,158 @@ export default function Hero() {
|
||||
|
||||
return (
|
||||
<Section className="relative min-h-[85vh] md:h-[90vh] flex items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
||||
<HeroIllustration />
|
||||
<motion.div
|
||||
className="absolute inset-0 z-0"
|
||||
initial={{ opacity: 0, scale: 0.95, filter: 'blur(20px)' }}
|
||||
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
|
||||
transition={{ duration: 2.2, ease: 'easeOut', delay: 0.05 }}
|
||||
>
|
||||
<HeroIllustration />
|
||||
</motion.div>
|
||||
|
||||
<Container className="relative z-10 text-left text-white w-full">
|
||||
<div className="max-w-5xl animate-slide-up">
|
||||
<Heading level={1} className="mb-6 md:mb-8 max-w-[12ch] md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl">
|
||||
{t.rich('title', {
|
||||
green: (chunks) => (
|
||||
<span className="relative inline-block">
|
||||
<span className="relative z-10 text-accent italic">{chunks}</span>
|
||||
<Scribble variant="circle" className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block" />
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</Heading>
|
||||
<p className="text-lg md:text-xl text-white/90 leading-relaxed max-w-2xl mb-10 md:mb-12">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 md:gap-6">
|
||||
<Button href="/contact" variant="accent" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg">
|
||||
{t('cta')}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-1">→</span>
|
||||
</Button>
|
||||
<Button href="/products" variant="ghost" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg text-white border-white/20 hover:bg-white/10 md:bg-white md:text-saturated md:hover:bg-neutral-light md:border-none">
|
||||
{t('exploreProducts')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
className="max-w-5xl"
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={containerVariants}
|
||||
>
|
||||
<motion.div variants={headingVariants}>
|
||||
<Heading level={1} className="mb-6 md:mb-8 max-w-[12ch] md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl">
|
||||
{t.rich('title', {
|
||||
green: (chunks) => (
|
||||
<span className="relative inline-block">
|
||||
<motion.span
|
||||
className="relative z-10 text-accent italic"
|
||||
variants={accentVariants}
|
||||
>
|
||||
{chunks}
|
||||
</motion.span>
|
||||
<motion.div
|
||||
variants={scribbleVariants}
|
||||
className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute"
|
||||
>
|
||||
<Scribble variant="circle" />
|
||||
</motion.div>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</Heading>
|
||||
</motion.div>
|
||||
<motion.div variants={subtitleVariants}>
|
||||
<p className="text-lg md:text-xl text-white/90 leading-relaxed max-w-2xl mb-10 md:mb-12">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="flex flex-col sm:flex-row gap-4 md:gap-6"
|
||||
variants={buttonContainerVariants}
|
||||
>
|
||||
<motion.div variants={buttonVariants}>
|
||||
<Button href="/contact" variant="accent" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg">
|
||||
{t('cta')}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-1">→</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
<motion.div variants={buttonVariants}>
|
||||
<Button href="/products" variant="ghost" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg text-white border-white/20 hover:bg-white/10 md:bg-white md:text-saturated md:hover:bg-neutral-light md:border-none">
|
||||
{t('exploreProducts')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</Container>
|
||||
|
||||
<div className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 animate-bounce hidden sm:block">
|
||||
<motion.div
|
||||
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, ease: "easeOut", delay: 3 }}
|
||||
>
|
||||
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
|
||||
<div className="w-1 h-2 bg-white rounded-full" />
|
||||
<motion.div
|
||||
className="w-1 h-2 bg-white rounded-full"
|
||||
animate={{ y: [0, -10, 0] }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 1 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.12,
|
||||
delayChildren: 0.4
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const headingVariants = {
|
||||
hidden: { opacity: 0, y: 60, scale: 0.85 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { duration: 1.2, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
} as const;
|
||||
|
||||
const accentVariants = {
|
||||
hidden: { opacity: 0, scale: 0.9, rotate: -5 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
} as const;
|
||||
|
||||
const scribbleVariants = {
|
||||
hidden: { opacity: 0, scale: 0, rotate: 180 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
transition: { duration: 1, type: "spring", stiffness: 300, damping: 20 }
|
||||
}
|
||||
} as const;
|
||||
|
||||
const subtitleVariants = {
|
||||
hidden: { opacity: 0, y: 40, scale: 0.95 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { duration: 1, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
} as const;
|
||||
|
||||
const buttonContainerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.4
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const buttonVariants = {
|
||||
hidden: { opacity: 0, y: 30, scale: 0.9 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { type: "spring", stiffness: 400, damping: 20 }
|
||||
}
|
||||
} as const;
|
||||
|
||||
55
components/team/Gallery.tsx
Normal file
55
components/team/Gallery.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Lightbox from '@/components/Lightbox';
|
||||
import { Section, Container, Heading } from '@/components/ui';
|
||||
|
||||
export default function Gallery() {
|
||||
const t = useTranslations('Team');
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
const teamGalleryImages = [
|
||||
'/uploads/2024/12/DSC07539-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07460-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07469-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07433-Large-600x400.webp',
|
||||
'/uploads/2024/12/DSC07387-Large-600x400.webp'
|
||||
];
|
||||
|
||||
return (
|
||||
<Section className="bg-primary-dark py-16 md:py-32">
|
||||
<Container>
|
||||
<Heading level={2} subtitle={t('gallery.subtitle')} align="center" className="text-white mb-12 md:mb-20">
|
||||
<span className="text-white">{t('gallery.title')}</span>
|
||||
</Heading>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-6">
|
||||
{teamGalleryImages.map((src, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
setLightboxIndex(idx);
|
||||
setLightboxOpen(true);
|
||||
}}
|
||||
className="relative aspect-[3/4] rounded-2xl md:rounded-[32px] overflow-hidden group shadow-2xl cursor-pointer"
|
||||
>
|
||||
<Image src={src} alt={t('gallery.title')} fill className="object-cover transition-transform duration-1000 group-hover:scale-110" />
|
||||
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-transparent transition-all duration-500" />
|
||||
<div className="absolute inset-0 border-0 group-hover:border-[8px] md:group-hover:border-[12px] border-white/10 transition-all duration-500 pointer-events-none" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<Lightbox
|
||||
isOpen={lightboxOpen}
|
||||
images={teamGalleryImages}
|
||||
initialIndex={lightboxIndex}
|
||||
onClose={() => setLightboxOpen(false)}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -166,17 +166,24 @@
|
||||
|
||||
@utility reveal-on-scroll {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
transform: translateY(40px) scale(0.95) rotate(-2deg);
|
||||
filter: blur(12px);
|
||||
transition:
|
||||
opacity 1s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
||||
transform 1s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
||||
filter 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
|
||||
&.is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transform: translateY(0) scale(1) rotate(0deg);
|
||||
filter: blur(0);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition: opacity 0.5s ease-out;
|
||||
opacity: 1;
|
||||
transform: none !important;
|
||||
filter: none !important;
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user