92 lines
4.5 KiB
TypeScript
92 lines
4.5 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { gsap } from 'gsap';
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
const horizontalImages = [
|
|
{ src: '/product_images/kitchenware.png', title: 'Coffee Cups', description: 'Wheel-thrown cups for your morning ritual — no two alike' },
|
|
{ src: '/product_images/lass_das_so_202603231510.png', title: 'Bowls', description: 'Handcrafted stoneware bowls for the table and the kitchen' },
|
|
{ src: '/product_images/Produkt_foto_studio_202603231654 (1).png', title: 'Tableware', description: 'Small-batch dinnerware made to be used every day' },
|
|
{ src: '/product_images/Produkt_foto_studio_202603231744.png', title: 'Kitchenware', description: 'Functional ceramics built for the rhythms of daily life' },
|
|
{ src: '/product_images/Produkt_foto_studio_202603231654 (2).png', title: 'Decoration', description: 'Sculptural pieces inspired by the textures of the Gulf Coast' },
|
|
];
|
|
|
|
const HorizontalScrollSection: React.FC = () => {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current;
|
|
const scrollContainer = scrollRef.current;
|
|
|
|
if (!container || !scrollContainer) return;
|
|
|
|
const cards = Array.from(scrollContainer.children) as HTMLDivElement[];
|
|
const lastCard = cards[cards.length - 1];
|
|
|
|
if (!lastCard) return;
|
|
|
|
const lastCardRightEdge = lastCard.offsetLeft + lastCard.offsetWidth;
|
|
const mobileEndInset = window.innerWidth < 768 ? 24 : 64;
|
|
const maxScroll = Math.max(lastCardRightEdge - window.innerWidth + mobileEndInset, 0);
|
|
|
|
const tween = gsap.to(scrollContainer, {
|
|
x: -maxScroll,
|
|
ease: 'none',
|
|
scrollTrigger: {
|
|
trigger: container,
|
|
start: 'top top',
|
|
end: () => `+=${maxScroll * 0.5}`,
|
|
scrub: 1,
|
|
pin: true,
|
|
anticipatePin: 1,
|
|
},
|
|
});
|
|
|
|
return () => {
|
|
tween.scrollTrigger?.kill();
|
|
tween.kill();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<section ref={containerRef} className="relative overflow-hidden bg-clay-dark h-screen w-full">
|
|
<div
|
|
ref={scrollRef}
|
|
className="flex h-screen items-center"
|
|
>
|
|
{horizontalImages.map((image, index) => (
|
|
<div
|
|
key={index}
|
|
className="relative flex-shrink-0 w-[86vw] md:w-[75vw] h-screen flex items-center justify-center px-4 pr-16 md:p-8"
|
|
>
|
|
<div className="relative w-full h-full max-w-4xl max-h-[60vh] overflow-hidden rounded-lg shadow-2xl group">
|
|
<img
|
|
src={image.src}
|
|
alt={image.title}
|
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent" />
|
|
<div className="absolute bottom-0 left-0 p-8 md:p-12 text-white">
|
|
<h3 className="font-display text-4xl md:text-5xl font-light mb-4">{image.title}</h3>
|
|
<p className="font-body text-base md:text-lg font-light opacity-80 max-w-md">{image.description}</p>
|
|
</div>
|
|
</div>
|
|
<div className="absolute top-1/2 right-3 translate-x-[24%] -translate-y-1/2 text-white/20 font-display text-[4.5rem] sm:text-[6rem] md:right-0 md:translate-x-[42%] md:text-[12rem] xl:text-[15rem] leading-none select-none pointer-events-none">
|
|
{String(index + 1).padStart(2, '0')}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex items-center gap-4 text-white/60">
|
|
<span className="material-symbols-outlined text-sm">arrow_back</span>
|
|
<span className="text-xs uppercase tracking-[0.3em] font-light">Scroll to explore</span>
|
|
<span className="material-symbols-outlined text-sm">arrow_forward</span>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default HorizontalScrollSection;
|