'use client'; import * as React from 'react'; import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { m, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { useTranslations } from 'next-intl'; import { LogoArcs } from '@/components/ui/LogoArcs'; export interface TeamMember { id: string; name: string; position: string; email?: string | null; phone?: string | null; image?: { url: string; alt?: string; } | string | null; branch?: string; } interface TeamGridProps { members: TeamMember[]; } function hashString(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { hash = (hash << 5) - hash + str.charCodeAt(i); hash |= 0; } return Math.abs(hash); } export function getMemberLogoArcsProps(memberId: string) { const hash = hashString(memberId); const rotation = Math.round((hash * 137.5) % 360); const size = 160 + ((hash * 13) % 180); // 160px to 340px const top = -60 + ((hash * 7) % 80); // -60px to 20px const right = (hash % 2 === 0) ? -50 + ((hash * 11) % 70) : undefined; const left = (hash % 2 !== 0) ? -50 + ((hash * 11) % 70) : undefined; const opacity = Number((0.015 + ((hash % 7) * 0.005)).toFixed(3)); // 0.015 to 0.045 (ultra subtle) return { rotation, size, top, right, left, opacity }; } export function TeamGrid({ members }: TeamGridProps) { const t = useTranslations('TeamGrid'); const [selectedMember, setSelectedMember] = useState(null); const [mounted, setMounted] = useState(false); const [[page, direction], setPage] = useState([0, 0]); const selectedIndex = selectedMember ? members.findIndex(m => m.id === selectedMember.id) : -1; const prevMember = selectedIndex !== -1 && members.length > 1 ? members[(selectedIndex - 1 + members.length) % members.length] : null; const nextMember = selectedIndex !== -1 && members.length > 1 ? members[(selectedIndex + 1) % members.length] : null; const paginate = (newDirection: number) => { if (newDirection > 0 && nextMember) { setPage([page + 1, newDirection]); setSelectedMember(nextMember); } else if (newDirection < 0 && prevMember) { setPage([page - 1, newDirection]); setSelectedMember(prevMember); } }; useEffect(() => { setMounted(true); }, []); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { setSelectedMember(null); } else if (e.key === 'ArrowLeft' && prevMember) { paginate(-1); } else if (e.key === 'ArrowRight' && nextMember) { paginate(1); } }; window.addEventListener('keydown', handleKeyDown); if (selectedMember) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { window.removeEventListener('keydown', handleKeyDown); document.body.style.overflow = ''; }; }, [selectedMember, prevMember, nextMember, page]); const handlePreload = (url: string | null) => { if (url && typeof window !== 'undefined') { const img = new window.Image(); img.src = url; } }; if (!members || members.length === 0) return null; const slideVariants = { enter: (dir: number) => ({ x: dir > 0 ? 350 : dir < 0 ? -350 : 0, opacity: 0, scale: 0.82, }), center: { x: 0, opacity: 1, scale: 1, }, exit: (dir: number) => ({ x: dir < 0 ? 350 : dir > 0 ? -350 : 0, opacity: 0, scale: 0.82, }), }; return (

{t('badge')}

{t('title')}

{t('subtitle')}

{members.map((member, i) => { const imageUrl = member.image ? (typeof member.image === 'string' ? member.image : member.image.url) : null; const arcsProps = getMemberLogoArcsProps(member.id); return ( handlePreload(imageUrl)} onTouchStart={() => handlePreload(imageUrl)} className="group flex flex-col bg-white rounded-[2rem] border border-neutral-100 shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:shadow-[0_20px_40px_rgba(238,114,3,0.1)] transition-all duration-500 hover:-translate-y-2 overflow-hidden" > {/* Card Banner with Signature E-TIB LogoArcs Design Element */}
{/* Signature E-TIB Logo Arcs Design Element */}
{/* Overlapping Profile Picture with Shared Layout Animation */} { setPage([0, 0]); setSelectedMember(member); }} onMouseEnter={() => handlePreload(imageUrl)} onTouchStart={() => handlePreload(imageUrl)} className="w-32 h-32 md:w-36 md:h-36 rounded-2xl overflow-hidden border-4 border-white shadow-xl bg-white relative -mt-16 md:-mt-20 mb-6 group-hover:-translate-y-2 transition-transform duration-500 cursor-pointer group/avatar" > {imageUrl ? ( <> {member.name} {/* Zoom Icon Overlay on Hover */}
) : (
)}
{/* Details */}

{member.name}

{member.position}

{member.branch && (
{member.branch === 'e-tib' ? t('branchETIB') : member.branch === 'ing' ? t('branchIng') : member.branch === 'bohrtechnik' ? t('branchBohr') : member.branch}
)} {/* Contacts (Sticky at bottom) */}
{member.email && (
{member.email}
)} {member.phone && (
{member.phone}
)}
); })}
{/* 3D Carousel Lightbox Modal with Neighbor Blurred Preview Cards & React Portal */} {mounted && createPortal( {selectedMember && ( setSelectedMember(null)} className="fixed inset-0 z-[9999] bg-black/85 backdrop-blur-xl flex flex-col items-center justify-center p-4 sm:p-6 md:p-12 overflow-y-auto" > {/* Close Button */} {/* Desktop Left Arrow Navigation Button */} {prevMember && ( )} {/* Desktop Right Arrow Navigation Button */} {nextMember && ( )} {/* 3D Carousel Stage */}
{/* Previous Neighbor Card (Blurred Background Left - Desktop only) */} {prevMember && ( { e.stopPropagation(); paginate(-1); }} className="hidden lg:flex flex-col absolute -left-12 xl:-left-20 w-64 lg:w-72 rounded-3xl overflow-hidden bg-white/10 border border-white/20 backdrop-blur-md opacity-35 hover:opacity-70 scale-90 transition-all duration-500 cursor-pointer z-10 shadow-2xl select-none" >
{prevMember.image && ( {prevMember.name} )}

{prevMember.name}

{prevMember.position}

)} {/* Active Main Card with Directional Slide Animation into Center */} { if (Math.abs(info.offset.y) > 100 || Math.abs(info.velocity.y) > 500) { setSelectedMember(null); } }} transition={{ x: { type: 'spring', stiffness: 300, damping: 30 }, opacity: { duration: 0.25 }, scale: { duration: 0.25 }, }} onClick={(e) => e.stopPropagation()} className="bg-white rounded-3xl overflow-hidden max-w-lg w-full shadow-2xl border border-white/20 relative flex flex-col z-20 my-auto touch-none cursor-grab active:cursor-grabbing" > {/* Mobile Drag Indicator Bar */}
{/* Portrait Container - Height tuned for Mobile without vertical squishing */} {selectedMember.image ? ( {selectedMember.name} ) : (
)}

{selectedMember.name}

{selectedMember.position}

{/* Contact Info Footer */}
{selectedMember.email && (
{selectedMember.email}
)} {selectedMember.phone && (
{selectedMember.phone}
)}
{/* Next Neighbor Card (Blurred Background Right - Desktop only) */} {nextMember && ( { e.stopPropagation(); paginate(1); }} className="hidden lg:flex flex-col absolute -right-12 xl:-right-20 w-64 lg:w-72 rounded-3xl overflow-hidden bg-white/10 border border-white/20 backdrop-blur-md opacity-35 hover:opacity-70 scale-90 transition-all duration-500 cursor-pointer z-10 shadow-2xl select-none" >
{nextMember.image && ( {nextMember.name} )}

{nextMember.name}

{nextMember.position}

)}
{/* Mobile Dedicated Navigation Bar (Bottom) */}
e.stopPropagation()} className="flex md:hidden items-center justify-between w-full max-w-md mt-4 px-4 py-2.5 bg-white/10 backdrop-blur-xl rounded-full border border-white/20 text-white z-40 shrink-0" > {selectedIndex + 1} / {members.length}
)} , document.body )}
); }