100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import SkeudCard from './SkeudCard';
|
|
|
|
interface Project {
|
|
id: string;
|
|
title: string;
|
|
subtitle?: string;
|
|
description: string;
|
|
image?: string;
|
|
category: string;
|
|
status: string;
|
|
url?: string;
|
|
stack: string[];
|
|
featured?: boolean;
|
|
}
|
|
|
|
interface Props {
|
|
projects: Project[];
|
|
}
|
|
|
|
export default function Crate({ projects }: Props) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const trackRef = useRef<HTMLDivElement>(null);
|
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current;
|
|
const track = trackRef.current;
|
|
if (!container || !track) return;
|
|
// Mobile / touch : skip pinned logic, default vertical grid via CSS
|
|
if (window.matchMedia('(max-width: 767px)').matches) return;
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
|
|
|
const getTotalScroll = () => track.scrollWidth - window.innerWidth + 100;
|
|
|
|
function onScroll() {
|
|
if (!container || !track) return;
|
|
const rect = container.getBoundingClientRect();
|
|
const totalScroll = getTotalScroll();
|
|
if (totalScroll <= 0) return;
|
|
|
|
if (rect.top <= 0 && rect.bottom > window.innerHeight) {
|
|
const progress = Math.min(1, Math.max(0, -rect.top / (container.offsetHeight - window.innerHeight)));
|
|
track.style.transform = `translate3d(${-progress * totalScroll}px, 0, 0)`;
|
|
}
|
|
}
|
|
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
window.addEventListener('resize', onScroll);
|
|
onScroll();
|
|
return () => {
|
|
window.removeEventListener('scroll', onScroll);
|
|
window.removeEventListener('resize', onScroll);
|
|
};
|
|
}, [projects.length]);
|
|
|
|
// Height of container = 100vh * (project count factor) to give scroll room
|
|
const containerHeightStyle = {
|
|
height: `${Math.max(100, projects.length * 60)}vh`,
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Desktop : pinned horizontal scroll */}
|
|
<div
|
|
ref={containerRef}
|
|
className="hidden md:block relative"
|
|
style={containerHeightStyle}
|
|
>
|
|
<div
|
|
ref={scrollerRef}
|
|
className="sticky top-16 h-screen overflow-hidden flex items-center"
|
|
>
|
|
<div
|
|
ref={trackRef}
|
|
className="flex items-center gap-10 pl-[10vw] pr-[20vw] will-change-transform"
|
|
>
|
|
{projects.map((project, i) => (
|
|
<SkeudCard key={project.id} project={project} index={i} />
|
|
))}
|
|
<div
|
|
className="flex-shrink-0 font-mono text-xs text-[color:var(--color-text-dim)] uppercase tracking-widest writing-mode-vertical"
|
|
style={{ writingMode: 'vertical-rl' }}
|
|
>
|
|
end of crate · {projects.length} skeuds
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile : vertical grid fallback */}
|
|
<div className="md:hidden grid grid-cols-1 gap-10 pt-8">
|
|
{projects.map((project, i) => (
|
|
<SkeudCard key={project.id} project={project} index={i} />
|
|
))}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|