|
|
|
|
@@ -46,7 +46,21 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|
|
|
|
const scrollLeftRef = React.useRef(0);
|
|
|
|
|
const isDraggingRef = React.useRef(false);
|
|
|
|
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Center the first card on mount so it appears in the middle with peek on both sides
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
const el = containerRef.current;
|
|
|
|
|
if (!el) return;
|
|
|
|
|
// Wait for layout to settle
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
const firstCard = el.querySelector('[data-card]') as HTMLElement | null;
|
|
|
|
|
if (!firstCard) return;
|
|
|
|
|
const cardCenter = firstCard.offsetLeft + firstCard.offsetWidth / 2;
|
|
|
|
|
const containerCenter = el.offsetWidth / 2;
|
|
|
|
|
el.scrollLeft = cardCenter - containerCenter;
|
|
|
|
|
});
|
|
|
|
|
}, [references.length]);
|
|
|
|
|
|
|
|
|
|
const badge = props.badge || data?.badge || t('badge');
|
|
|
|
|
const title = props.title || data?.title || t('title');
|
|
|
|
|
const description = props.description || data?.description || t('description');
|
|
|
|
|
@@ -108,9 +122,11 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="relative z-10">
|
|
|
|
|
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" />
|
|
|
|
|
{/* Fade edges for peek effect */}
|
|
|
|
|
<div className="absolute left-0 top-0 bottom-0 w-16 bg-gradient-to-r from-neutral-dark to-transparent z-20 pointer-events-none" />
|
|
|
|
|
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 pointer-events-none" />
|
|
|
|
|
|
|
|
|
|
{/* Carousel: breaks viewport edge on both sides but initial padding aligns with container */}
|
|
|
|
|
{/* Carousel: centered first card with peek on both sides */}
|
|
|
|
|
<LazyMotion features={domAnimation}>
|
|
|
|
|
<div
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
@@ -118,7 +134,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|
|
|
|
onMouseLeave={onMouseLeave}
|
|
|
|
|
onMouseUp={onMouseUp}
|
|
|
|
|
onMouseMove={onMouseMove}
|
|
|
|
|
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
|
|
|
|
|
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-[calc(50vw-160px)] md:px-[calc(50vw-240px)] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
|
|
|
|
|
>
|
|
|
|
|
{references.map((ref, i) => {
|
|
|
|
|
const imgSrc = ref.image
|
|
|
|
|
@@ -128,11 +144,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|
|
|
|
return (
|
|
|
|
|
<m.div
|
|
|
|
|
key={ref.id}
|
|
|
|
|
data-card
|
|
|
|
|
initial={{ opacity: 0, x: 20 }}
|
|
|
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
|
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
|
|
|
transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
|
|
|
|
|
className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto"
|
|
|
|
|
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
|
|
|
|
|
>
|
|
|
|
|
<Link
|
|
|
|
|
href={`/${locale}/referenzen#${ref.slug}`}
|
|
|
|
|
@@ -169,8 +186,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|
|
|
|
</m.div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
{/* Right padding spacer so last card doesn't clip at viewport edge */}
|
|
|
|
|
<div className="flex-shrink-0 w-6" aria-hidden="true" />
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</LazyMotion>
|
|
|
|
|
</div>
|
|
|
|
|
|