Real-time Presence

Dynamic avatar animations and multiplayer cursors that bring your workspace to life.

Animation
Presence
Multiplayer
Collaboration
UI
Avatar
Avatar
Avatar
Avatar
Avatar
EH
Avatar
Avatar
Bento

Real-time Presence

Dynamic avatar animations and cursor that bring your workspace to life.


Installation

1. Install the following packages

npm
npm install motion @hugeicons/react @hugeicons/core-free-icons

2. Add cn utility function to your project

@/lib/utils/utils.ts
1import { clsx, type ClassValue } from "clsx" 2import { twMerge } from "tailwind-merge" 3 4export function cn(...inputs: ClassValue[]) { 5 return twMerge(clsx(inputs)) 6}

3. Copy and paste the following code into your project

real-time-presence.tsx
1"use client"; 2 3import { cn } from "@/lib/utils"; 4import { Cursor02Icon } from "@hugeicons/core-free-icons"; 5import { HugeiconsIcon } from "@hugeicons/react"; 6import { motion, useAnimate } from "motion/react"; 7import { useEffect } from "react"; 8 9const images = [ 10 "https://img.freepik.com/free-psd/3d-illustration-human-avatar-profile_23-2150671116.jpg", 11 "https://img.freepik.com/free-psd/3d-illustration-human-avatar-profile_23-2150671151.jpg", 12 "https://img.freepik.com/free-psd/3d-render-avatar-character_23-2150611722.jpg", 13 "https://img.freepik.com/free-psd/3d-render-avatar-character_23-2150611759.jpg", 14 "https://img.freepik.com/free-psd/3d-illustration-human-avatar-profile_23-2150671140.jpg", 15 "https://img.freepik.com/free-psd/3d-illustration-human-avatar-profile_23-2150671120.jpg", 16 "https://img.freepik.com/free-psd/3d-render-avatar-character_23-2150611762.jpg", 17]; 18 19function getRandom(min: number, max: number) { 20 return Math.random() * (max - min) + min; 21} 22 23const Box = ({ position }: { position: number }) => { 24 const imagePositons = [2, 3, 5, 6, 8, 10, 11]; 25 26 const [scope, animate] = useAnimate(); 27 28 const animateImages = () => { 29 animate("#avatar", { opacity: [0, 1], scale: [0.9, 1] }, { duration: 0.1, delay: getRandom(0, 10), repeat: Infinity, repeatDelay: 10, repeatType: "mirror" }); 30 }; 31 32 useEffect(() => { 33 animateImages(); 34 }, []); 35 36 return ( 37 <motion.div 38 ref={scope} 39 className={cn( 40 "rounded-lg size-10 ring ring-inset ring-black/10 dark:ring-white/10 overflow-hidden p-[3px] bg-background", 41 (position === 1 || position === 12) && "col-span-2 w-auto", 42 (position === 4 || position === 7) && "row-span-2 h-auto", 43 imagePositons.includes(position) && "outline outline-black/5 dark:outline-white/5", 44 position === 9 && "outline outline-black/5 dark:outline-white/5 bg-gray-100 dark:bg-white/5 duration-300", 45 )} 46 > 47 <motion.div id="avatar" initial={{ opacity: 0 }} className="grid place-items-center h-full w-full"> 48 {imagePositons.includes(position) && <img src={images[imagePositons.indexOf(position)]} key={position} alt="Avatar" className="object-cover rounded-md " />} 49 {position === 9 && <div className="w-full h-full bg-blue-600 text-[10px] grid place-items-center font-medium rounded-md text-white">EH</div>} 50 </motion.div> 51 </motion.div> 52 ); 53}; 54 55export const RealTimePresence = () => { 56 return ( 57 <div className="relative group overflow-hidden max-w-[400px] mx-auto rounded-md p-8 bg-white/50 dark:bg-zinc-950/50 border border-black/5 dark:border-white/10 shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] dark:shadow-[0_0_1px_1px_rgba(255,255,255,0.1)] grid place-items-center"> 58 {/* The Grid */} 59 <div className="grid grid-cols-4 grid-rows-4 gap-1 relative mx-auto z-10"> 60 {Array.from({ length: 12 }).map((_, index) => ( 61 <Box key={index} position={index + 1} /> 62 ))} 63 64 {/* --- Cursor --- */} 65 <motion.data initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 1 }}> 66 <motion.div 67 initial={{ x: 100, y: 80 }} 68 animate={{ y: [100, 85, 100] }} 69 transition={{ duration: 7, repeat: Infinity, ease: "easeInOut" }} 70 className="absolute right-1/2 top-7 flex flex-col items-start justify-center" 71 > 72 <HugeiconsIcon icon={Cursor02Icon} className="size-4 fill-white dark:fill-black text-black/60 dark:text-white/60" /> 73 <div className="ring ring-inset ring-black/20 dark:ring-white/20 bg-white dark:bg-black/10 text-[10px] px-2 rounded-lg ml-4 text-black dark:text-white py-0.5">Bento</div> 74 </motion.div> 75 </motion.data> 76 </div> 77 78 <div className="mt-6 flex flex-col"> 79 <h3 className="font-semibold text-lg text-black dark:text-white">Real-time Presence</h3> 80 <p className="text-muted-foreground text-sm">Dynamic avatar animations and cursor that bring your workspace to life.</p> 81 </div> 82 83 {/* background */} 84 {/* background */} 85 <div 86 className="absolute inset-0 rounded-md -z-10 [--grid-line:rgba(0,0,0,0.1)] dark:[--grid-line:rgba(255,255,255,0.1)]" 87 style={{ 88 backgroundImage: ` 89 linear-gradient(to right, var(--grid-line) 1px, transparent 1px), 90 linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px) 91 `, 92 backgroundSize: "32px 32px", 93 WebkitMaskImage: "radial-gradient(ellipse 80% 80% at 0% 0%, #000 50%, transparent 90%)", 94 maskImage: "radial-gradient(ellipse 80% 80% at 0% 0%, #000 50%, transparent 90%)", 95 }} 96 /> 97 </div> 98 ); 99}; 100