| @@ -448,7 +448,7 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) | |||||
| const [spread, setSpread] = useState(0); | const [spread, setSpread] = useState(0); | ||||
| const [flipping, setFlipping] = useState<'forward' | 'backward' | null>(null); | const [flipping, setFlipping] = useState<'forward' | 'backward' | null>(null); | ||||
| const audioRef = useRef<AudioContext | null>(null); | const audioRef = useRef<AudioContext | null>(null); | ||||
| const touchStartX = useRef<number | null>(null); | |||||
| const dragStartX = useRef<number | null>(null); | |||||
| const getCtx = () => { | const getCtx = () => { | ||||
| if (!audioRef.current) { | if (!audioRef.current) { | ||||
| @@ -493,13 +493,14 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) | |||||
| return () => window.removeEventListener('keydown', onKey); | return () => window.removeEventListener('keydown', onKey); | ||||
| }, [onClose, goNext, goPrev]); | }, [onClose, goNext, goPrev]); | ||||
| const onTouchStart = (e: React.TouchEvent) => { touchStartX.current = e.touches[0].clientX; }; | |||||
| const onTouchEnd = (e: React.TouchEvent) => { | |||||
| if (touchStartX.current === null) return; | |||||
| const dx = e.changedTouches[0].clientX - touchStartX.current; | |||||
| const onPointerDown = (e: React.PointerEvent) => { dragStartX.current = e.clientX; }; | |||||
| const onPointerUp = (e: React.PointerEvent) => { | |||||
| if (dragStartX.current === null) return; | |||||
| const dx = e.clientX - dragStartX.current; | |||||
| if (Math.abs(dx) > 50) (dx < 0 ? goNext() : goPrev()); | if (Math.abs(dx) > 50) (dx < 0 ? goNext() : goPrev()); | ||||
| touchStartX.current = null; | |||||
| dragStartX.current = null; | |||||
| }; | }; | ||||
| const onPointerCancel = () => { dragStartX.current = null; }; | |||||
| // Pages visible underneath the flipping overlay | // Pages visible underneath the flipping overlay | ||||
| const visibleLeft = flipping === 'backward' ? getPage(prevLeftIdx) : getPage(leftIdx); | const visibleLeft = flipping === 'backward' ? getPage(prevLeftIdx) : getPage(leftIdx); | ||||
| @@ -516,10 +517,11 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) | |||||
| return ( | return ( | ||||
| <div | <div | ||||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/95" | |||||
| onTouchStart={onTouchStart} | |||||
| onTouchEnd={onTouchEnd} | |||||
| style={{ perspective: '2500px' }} | |||||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/95 select-none" | |||||
| onPointerDown={onPointerDown} | |||||
| onPointerUp={onPointerUp} | |||||
| onPointerCancel={onPointerCancel} | |||||
| style={{ perspective: '2500px', touchAction: 'pan-y' }} | |||||
| > | > | ||||
| <style dangerouslySetInnerHTML={{ __html: ` | <style dangerouslySetInnerHTML={{ __html: ` | ||||
| @keyframes flipBookForward { from { transform: rotateY(0deg); } to { transform: rotateY(-180deg); } } | @keyframes flipBookForward { from { transform: rotateY(0deg); } to { transform: rotateY(-180deg); } } | ||||
| @@ -542,13 +544,35 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) | |||||
| }} | }} | ||||
| > | > | ||||
| {/* Static left page */} | {/* Static left page */} | ||||
| <div className="absolute left-0 top-0 w-1/2 h-full bg-white overflow-hidden"> | |||||
| {visibleLeft && <img src={visibleLeft} className={pageImgClass} alt="" draggable={false} />} | |||||
| </div> | |||||
| {visibleLeft ? ( | |||||
| <div className="absolute left-0 top-0 w-1/2 h-full bg-white overflow-hidden"> | |||||
| <img src={visibleLeft} className={pageImgClass} alt="" draggable={false} /> | |||||
| </div> | |||||
| ) : ( | |||||
| <div | |||||
| className="absolute left-0 top-0 w-1/2 h-full overflow-hidden" | |||||
| style={{ | |||||
| background: 'linear-gradient(135deg, #4a3826 0%, #2e2114 55%, #1a1108 100%)', | |||||
| boxShadow: 'inset 0 0 80px rgba(0,0,0,0.55)', | |||||
| }} | |||||
| aria-hidden | |||||
| /> | |||||
| )} | |||||
| {/* Static right page */} | {/* Static right page */} | ||||
| <div className="absolute right-0 top-0 w-1/2 h-full bg-white overflow-hidden"> | |||||
| {visibleRight && <img src={visibleRight} className={pageImgClass} alt="" draggable={false} />} | |||||
| </div> | |||||
| {visibleRight ? ( | |||||
| <div className="absolute right-0 top-0 w-1/2 h-full bg-white overflow-hidden"> | |||||
| <img src={visibleRight} className={pageImgClass} alt="" draggable={false} /> | |||||
| </div> | |||||
| ) : ( | |||||
| <div | |||||
| className="absolute right-0 top-0 w-1/2 h-full overflow-hidden" | |||||
| style={{ | |||||
| background: 'linear-gradient(225deg, #4a3826 0%, #2e2114 55%, #1a1108 100%)', | |||||
| boxShadow: 'inset 0 0 80px rgba(0,0,0,0.55)', | |||||
| }} | |||||
| aria-hidden | |||||
| /> | |||||
| )} | |||||
| {/* Spine */} | {/* Spine */} | ||||
| <div className="absolute left-1/2 top-0 -translate-x-1/2 w-2 h-full bg-gradient-to-r from-black/40 via-black/20 to-black/40 z-10 pointer-events-none" /> | <div className="absolute left-1/2 top-0 -translate-x-1/2 w-2 h-full bg-gradient-to-r from-black/40 via-black/20 to-black/40 z-10 pointer-events-none" /> | ||||