{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "image-expand-to-fullscreen", "title": "Image Expand To Fullscreen", "description": "An image lifts out of a feed post and morphs into a fullscreen editor with toolbars sliding in.", "dependencies": [ "remotion" ], "files": [ { "path": "registry/remocn/image-expand-to-fullscreen/index.tsx", "content": "\"use client\";\n\nimport { interpolate, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\n\nexport interface ImageExpandRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\nexport interface ImageExpandToFullscreenProps {\n from?: ImageExpandRect;\n to?: ImageExpandRect;\n borderRadiusFrom?: number;\n borderRadiusTo?: number;\n morphAt?: number;\n imageColorA?: string;\n imageColorB?: string;\n imageColorC?: string;\n feedBackground?: string;\n editorBackground?: string;\n accent?: string;\n postAuthor?: string;\n postBody?: string;\n speed?: number;\n className?: string;\n}\n\nconst FONT_FAMILY =\n \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nconst VIEWPORT_W = 1280;\nconst VIEWPORT_H = 720;\n\nconst DEFAULT_FROM: ImageExpandRect = {\n left: 460,\n top: 280,\n width: 360,\n height: 200,\n};\n\nconst DEFAULT_TO: ImageExpandRect = {\n left: 200,\n top: 120,\n width: 880,\n height: 480,\n};\n\nexport function ImageExpandToFullscreen({\n from = DEFAULT_FROM,\n to = DEFAULT_TO,\n borderRadiusFrom = 12,\n borderRadiusTo = 16,\n morphAt = 30,\n imageColorA = \"#ff6b6b\",\n imageColorB = \"#845ec2\",\n imageColorC = \"#4d8dff\",\n feedBackground = \"#f4f4f5\",\n editorBackground = \"#0a0a0a\",\n accent = \"#fafafa\",\n postAuthor = \"Maya Larsson\",\n postBody = \"Sunset over the old harbor — color graded straight out of camera.\",\n speed = 1,\n className,\n}: ImageExpandToFullscreenProps) {\n const frame = useCurrentFrame() * speed;\n const { fps } = useVideoConfig();\n\n // ONE spring drives the entire shared-element transition. Slightly more\n // critically damped than the morphing modal — the image lands with a\n // tight settle rather than a heavy bloom.\n const morph = spring({\n frame: frame - morphAt,\n fps,\n config: { mass: 1.4, damping: 24, stiffness: 130 },\n });\n\n const left = interpolate(morph, [0, 1], [from.left, to.left]);\n const top = interpolate(morph, [0, 1], [from.top, to.top]);\n const width = interpolate(morph, [0, 1], [from.width, to.width]);\n const height = interpolate(morph, [0, 1], [from.height, to.height]);\n const radius = interpolate(morph, [0, 1], [borderRadiusFrom, borderRadiusTo]);\n\n // Feed UI fades fully out in the FIRST half of the morph; the dark\n // editor background fades in across the same window.\n const feedOpacity = interpolate(morph, [0, 0.5], [1, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n const editorOpacity = interpolate(morph, [0, 0.5], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n\n // Toolbars slide in during the LAST third of the morph — they should\n // feel called in by the image landing at fullscreen.\n const toolbarOpacity = interpolate(morph, [0.5, 1], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n const topToolbarTy = interpolate(morph, [0.5, 1], [-100, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n const bottomToolbarTy = interpolate(morph, [0.5, 1], [100, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n\n const imageGradient = `linear-gradient(135deg, ${imageColorA}, ${imageColorB}, ${imageColorC})`;\n\n return (\n