"use client" import Image from "next/image" import { Building2 } from "lucide-react" import { useState, useRef, useEffect } from "react" function ClientIcon({ icon }: { icon?: string }) { const [hasError, setHasError] = useState(false) if (!icon || hasError) { return } return ( setHasError(true)} /> ) } const clients = [ { name: "Max Planck Institute for Social Anthropology", location: "Halle/Saale", href: "https://www.eth.mpg.de/", icon: "/assets/icons/eth-mpg.png", }, { name: "German National Academy of Sciences Leopoldina", location: "Halle/Saale", href: "https://www.leopoldina.org/", icon: "/assets/icons/leopoldina.png", }, { name: "German National Museum", location: "Nuremberg", href: "https://www.gnm.de/", icon: "/assets/icons/gnm.png", }, { name: "Central Institute for Art History", location: "Munich", href: "https://www.zikg.eu/", icon: "/assets/icons/zikg.png", }, { name: "German Fairy Tale and Weser Legends Museum", location: "Bad Oeynhausen", href: "https://www.badoeynhausen.de/freizeit-kultur-sport/kultur/staedtische-museen/deutsches-maerchen-und-wesersagenmuseum", icon: "/assets/icons/badoeynhausen.png", }, { name: "Roli-Bar", location: "roli-bar.de", href: "https://roli-bar.de/", icon: "/assets/icons/roli-bar.png", }, { name: "Re-Cycle Halle", location: "Halle/Saale", href: "https://re-cycle-halle.de/", icon: "/assets/icons/re-cycle-halle.png", }, { name: "bold + bündig", location: "Leipzig", href: "https://boldundbuendig.de/", icon: "/assets/icons/boldundbuendig.png", }, ] function ClientsBandContent() { return ( <> {clients.map((client) => { const content = ( <> {client.name} ({client.location}) ) const className = "home-clients-band-item mx-4 flex shrink-0 items-center gap-2 rounded-xl border border-slate-200 bg-white px-6 py-4 text-base text-slate-700 shadow-sm" return client.href ? ( {content} ) : ( {content} ) })} ) } const SCROLL_SPEED = 40 export function HomeClients() { const bandRef = useRef(null) const trackRef = useRef(null) const posRef = useRef(0) const isHoveredRef = useRef(false) const isDraggingRef = useRef(false) const dragStartXRef = useRef(0) const dragStartPosRef = useRef(0) const lastTimeRef = useRef(null) const rafRef = useRef(null) const [isDragging, setIsDragging] = useState(false) useEffect(() => { const track = trackRef.current if (!track) return const step = (time: number) => { const halfWidth = track.scrollWidth / 2 if (halfWidth > 0 && lastTimeRef.current !== null && !isHoveredRef.current && !isDraggingRef.current) { const dt = time - lastTimeRef.current posRef.current += SCROLL_SPEED * dt / 1000 if (posRef.current >= halfWidth) posRef.current -= halfWidth } lastTimeRef.current = time track.style.transform = `translateX(${-posRef.current}px)` rafRef.current = requestAnimationFrame(step) } rafRef.current = requestAnimationFrame(step) return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, []) const handleMouseEnter = () => { isHoveredRef.current = true } const handleMouseLeave = () => { isHoveredRef.current = false isDraggingRef.current = false setIsDragging(false) } const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault() isDraggingRef.current = true dragStartXRef.current = e.clientX dragStartPosRef.current = posRef.current setIsDragging(true) } const handleMouseMove = (e: React.MouseEvent) => { if (!isDraggingRef.current) return const track = trackRef.current if (!track) return const halfWidth = track.scrollWidth / 2 const dx = dragStartXRef.current - e.clientX posRef.current = ((dragStartPosRef.current + dx) % halfWidth + halfWidth) % halfWidth } const handleMouseUp = () => { isDraggingRef.current = false setIsDragging(false) } const handleTouchStart = (e: React.TouchEvent) => { isDraggingRef.current = true dragStartXRef.current = e.touches[0].clientX dragStartPosRef.current = posRef.current } const handleTouchMove = (e: React.TouchEvent) => { if (!isDraggingRef.current) return const track = trackRef.current if (!track) return const halfWidth = track.scrollWidth / 2 const dx = dragStartXRef.current - e.touches[0].clientX posRef.current = ((dragStartPosRef.current + dx) % halfWidth + halfWidth) % halfWidth } const handleTouchEnd = () => { isDraggingRef.current = false } return (

Employers & Customers

Research institutions and organisations I have worked with.

) }