{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "scrubbable-video-reveal", "type": "registry:block", "title": "Scrubbable Video Reveal", "description": "Scroll either way. Every frame follows your hand like apple.com.", "author": "Wensity ", "dependencies": [ "clsx", "framer-motion", "tailwind-merge" ], "registryDependencies": [], "files": [ { "path": "registry/wensity/lib/utils.ts", "type": "registry:lib", "target": "@lib/utils.ts", "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n" }, { "path": "registry/wensity/scrubbable-video-reveal.tsx", "type": "registry:component", "target": "@components/wensity/scrubbable-video-reveal.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n motion,\n useScroll,\n useTransform,\n useMotionValueEvent,\n useReducedMotion,\n} from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ScrubbableVideoRevealProps {\n /** Image sequence URLs in order. Use 100–200 highly-compressed WebP / JPEG. */\n frames: string[];\n /** Aspect ratio for the canvas wrapper (e.g. \"16 / 10\"). */\n aspect?: string;\n /** Multiplier on the scroll travel — total inner = `travel × 100vh`. */\n travel?: number;\n /** Render the surrounding device frame. */\n framed?: boolean;\n /** Pin the canvas via `position: sticky` while user scrolls. */\n sticky?: boolean;\n /** Optional content rendered above the canvas (overlays). */\n children?: React.ReactNode;\n /** Use the window scroll instead of an inner scrollable container. */\n useWindow?: boolean;\n /** Loading strategy for the image preload. */\n preload?: \"eager\" | \"lazy\";\n /** Scroll container height (when `useWindow` is false). */\n height?: string;\n className?: string;\n}\n\n/**\n * ScrubbableVideoReveal\n *\n * Apple-grade scroll-scrubbed sequence. We never use a `