EH
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-icons2. 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