{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "3d-book", "title": "3D Book", "description": "An interactive 3D book component with page flip animations.", "dependencies": [ "motion", "clsx", "tailwind-merge" ], "files": [ { "path": "registry/default/example/3d-book.tsx", "content": "\"use client\";\r\n\r\nimport { useRef, useState } from \"react\";\r\n\r\nexport default function InteractiveBook() {\r\n const bookRef = useRef(null);\r\n const [progress, setProgress] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n\r\n // Linear interpolation function\r\n const lerp = (a: number, b: number, t: number) => a + (b - a) * t;\r\n\r\n const handlePointerMove = (e: React.PointerEvent) => {\r\n if (!bookRef.current) return;\r\n\r\n const rect = bookRef.current.getBoundingClientRect();\r\n const cursorX = e.clientX;\r\n const bookCenterX = rect.left + rect.width / 2;\r\n\r\n // Calculate how far cursor is from center (-1 to 1)\r\n const distanceFromCenter = (cursorX - bookCenterX) / (rect.width / 2);\r\n\r\n // Map cursor position to progress\r\n // Left side (negative) = open (1), Right side (positive) = closed (0)\r\n const targetProgress = lerp(1, 0, (distanceFromCenter + 1) / 2);\r\n\r\n // Clamp between 0 and 1\r\n setProgress(Math.max(0, Math.min(1, targetProgress)));\r\n };\r\n\r\n const handlePointerDown = (e: React.PointerEvent) => {\r\n setIsDragging(true);\r\n handlePointerMove(e);\r\n };\r\n\r\n const handlePointerUp = () => {\r\n setIsDragging(false);\r\n setProgress(0);\r\n };\r\n\r\n const handlePointerLeave = () => {\r\n // Close book when cursor leaves (only if not dragging on mobile)\r\n if (!isDragging) {\r\n setProgress(0);\r\n }\r\n };\r\n\r\n // Generate pages - all at same size and position\r\n const totalPages = 15;\r\n const pages = [];\r\n\r\n // All pages same size, different rotation angles\r\n for (let i = 1; i <= totalPages; i++) {\r\n const rotationAngle = (i + 1) * 10; // -20, -30, -40... -160\r\n\r\n pages.push(\r\n \r\n );\r\n }\r\n\r\n return (\r\n
\r\n \r\n {/* Back Cover (underneath all pages) */}\r\n \r\n\r\n {/* Pages - all same size, different rotations */}\r\n {pages}\r\n\r\n {/* Front Cover */}\r\n \r\n {/* Cover shadow overlay */}\r\n \r\n\r\n {/* Red top section */}\r\n \r\n\r\n {/* Spine edge */}\r\n
\r\n
\r\n
\r\n
\r\n\r\n {/* Title */}\r\n \r\n Notebook\r\n
\r\n\r\n {/* Back label */}\r\n \r\n Back Page\r\n
\r\n
\r\n \r\n \r\n );\r\n}\r\n", "type": "registry:component" } ], "type": "registry:component" }