'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import Image, { StaticImageData } from 'next/image'; import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion'; import { cn } from '@documenso/ui/lib/utils'; import { Card, CardContent } from '@documenso/ui/primitives/card'; export type SigningCardProps = { className?: string; name: string; signingCelebrationImage?: StaticImageData; }; /** * 2D signing card. */ export const SigningCard = ({ className, name, signingCelebrationImage }: SigningCardProps) => { return (
{signingCelebrationImage && ( )}
); }; /** * 3D signing card that follows the mouse movement within a certain range. */ export const SigningCard3D = ({ className, name, signingCelebrationImage }: SigningCardProps) => { // Should use % based dimensions by calculating the window height/width. const boundary = 400; const [trackMouse, setTrackMouse] = useState(false); const timeoutRef = useRef(); const cardX = useMotionValue(0); const cardY = useMotionValue(0); const rotateX = useTransform(cardY, [-600, 600], [8, -8]); const rotateY = useTransform(cardX, [-600, 600], [-8, 8]); const diagonalMovement = useTransform( [rotateX, rotateY], ([newRotateX, newRotateY]) => newRotateX + newRotateY, ); const sheenPosition = useTransform(diagonalMovement, [-16, 16], [-100, 200]); const sheenOpacity = useTransform(sheenPosition, [-100, 50, 200], [0, 0.1, 0]); const sheenGradient = useMotionTemplate`linear-gradient( 30deg, transparent, rgba(var(--sheen-color) / ${sheenOpacity}) ${sheenPosition}%, transparent)`; const cardRef = useRef(null); const cardCenterPosition = useCallback(() => { if (!cardRef.current) { return { x: 0, y: 0 }; } const { x, y, width, height } = cardRef.current.getBoundingClientRect(); return { x: x + width / 2, y: y + height / 2 }; }, [cardRef]); const onMouseMove = useCallback( (event: MouseEvent) => { const { x, y } = cardCenterPosition(); const offsetX = event.clientX - x; const offsetY = event.clientY - y; // Calculate distance between the mouse pointer and center of the card. const distance = Math.sqrt(offsetX * offsetX + offsetY * offsetY); // Mouse enters enter boundary. if (distance <= boundary && !trackMouse) { setTrackMouse(true); } else if (!trackMouse) { return; } cardX.set(offsetX); cardY.set(offsetY); clearTimeout(timeoutRef.current); // Revert the card back to the center position after the mouse stops moving. timeoutRef.current = setTimeout(() => { void animate(cardX, 0, { duration: 2, ease: 'backInOut' }); void animate(cardY, 0, { duration: 2, ease: 'backInOut' }); void animate(sheenOpacity, 0, { duration: 2, ease: 'backInOut' }); setTrackMouse(false); }, 1000); }, [cardX, cardY, cardCenterPosition, trackMouse, sheenOpacity], ); useEffect(() => { window.addEventListener('mousemove', onMouseMove); return () => { window.removeEventListener('mousemove', onMouseMove); }; }, [onMouseMove]); return (
{signingCelebrationImage && ( )}
); }; type SigningCardContentProps = { name: string; className?: string; }; const SigningCardContent = ({ className, name }: SigningCardContentProps) => { return ( {name} ); }; type SigningCardImageProps = { signingCelebrationImage: StaticImageData; }; const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) => { return ( background pattern ); };