"use client"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import type { ShelfBook } from "@/lib/books"; const W = 41.5; const H = 220; const SPINE = `${W}px`; const COVER = `${W * 4}px`; const BOOK = `${W * 5}px`; const FILTER = "brightness(0.8) contrast(2)"; const PAPER = { pointerEvents: "none", opacity: 0.4, filter: "url(#paper)", zIndex: 50, } as const; export function Bookshelf({ books }: { books: ShelfBook[] }) { const router = useRouter(); const pathname = usePathname(); const slug = pathname.split("/")[2]; const bookIndex = slug ? books.findIndex((b) => b.slug === slug) : -1; const viewportRef = useRef(null); const timer = useRef | null>(null); const [scroll, setScroll] = useState(0); const [inView, setInView] = useState(0); const [isScrolling, setIsScrolling] = useState(false); const maxScroll = (W + 12) * (books.length - inView) + (bookIndex > -1 ? W * 4 : 0) + 5; const clamp = (x: number) => Math.max(0, Math.min(maxScroll, x)); useEffect(() => { const el = viewportRef.current; if (!el) return; const measure = () => setInView(el.clientWidth / (W + 11)); measure(); const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect(); }, []); useEffect(() => { if (bookIndex === -1) setScroll((s) => Math.max(0, s)); else setScroll(Math.max(0, (bookIndex - (inView - 4.5) / 2) * (W + 11))); }, [bookIndex, inView]); useEffect( () => () => { if (timer.current) clearInterval(timer.current); }, [], ); const start = (dir: 1 | -1) => { if (timer.current) clearInterval(timer.current); setIsScrolling(true); timer.current = setInterval(() => setScroll((s) => clamp(s + dir * 3)), 10); }; const stop = () => { setIsScrolling(false); if (timer.current) clearInterval(timer.current); timer.current = null; }; const arrow = (dir: 1 | -1) => ({ onMouseEnter: () => start(dir), onMouseLeave: stop, onTouchStart: () => start(dir), onTouchEnd: stop, }); return ( <>
0 ? "block" : "hidden"}`} >
{books.map((book, i) => { const open = bookIndex === i; return ( ); })}
); } function Chevron({ dir }: { dir: "left" | "right" }) { return ( ); }