components

Blocks

Interactive UI

Blob Cursor

Source

The Cursor component replaces the standard browser pointer with a minimalist, interactive dual-element system. It is context-aware, automatically adjusting its shape and behavior based on the elements it interacts with.

Note: Wrap this component in CursorProvider before adding to layout.tsx and use data-cursor-hover to enable interactions.
CODE
"use client";

import { useEffect, useRef, useState } from "react";
import gsap from "gsap";

const Cursor = () => {
    const cursorRef = useRef(null);
    const backdropRef = useRef(null);
    const [isMobile, setIsMobile] = useState(false);

    useEffect(() => {
        const checkMobile = () => {
            setIsMobile(
                /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
                window.innerWidth <= 768 ||
                "ontouchstart" in window
            );
        };
        checkMobile();
        window.addEventListener("resize", checkMobile);
        return () => window.removeEventListener("resize", checkMobile);
    }, []);

    useEffect(() => {
        if (isMobile || !cursorRef.current || !backdropRef.current) return;

        const cursor = cursorRef.current;
        const backdrop = backdropRef.current;
        
        let mouseX = 0;
        let mouseY = 0;
        let isHovering = false;

        gsap.set([cursor, backdrop], { xPercent: -50, yPercent: -50, opacity: 1 });

        const updatePosition = (e) => {
            mouseX = e.clientX;
            mouseY = e.clientY;
            
            gsap.set(cursor, { x: mouseX, y: mouseY });

            if (!isHovering) {
                gsap.to(backdrop, {
                    x: mouseX,
                    y: mouseY,
                    duration: 0.15,
                    ease: "power3.out",
                });
            }
        };

        const handleHover = (e) => {
            isHovering = true;
            const target = e.currentTarget;
            const rect = target.getBoundingClientRect();

            gsap.killTweensOf(backdrop);

            gsap.to(backdrop, {
                x: rect.left + rect.width / 2,
                y: rect.top + rect.height / 2,
                width: rect.width + 10,
                height: rect.height + 4,
                borderRadius: "8px",
                backgroundColor: "rgba(255, 255, 255, 0.1)",
                duration: 0.3,
                ease: "power3.out",
            });

            gsap.to(cursor, { opacity: 0, duration: 0.3 });
        };

        const handleUnHover = () => {
            isHovering = false;
            gsap.to(backdrop, {
                width: 16,
                height: 16,
                borderRadius: "100%",
                backgroundColor: "rgba(0, 0, 0, 0.1)",
                duration: 0.5,
                ease: "power3.out",
            });
            gsap.to(cursor, { opacity: 1, duration: 0.3 });
        };

        window.addEventListener("mousemove", updatePosition);

        const refreshListeners = () => {
            const elements = document.querySelectorAll("a, button, [data-cursor-hover]");
            elements.forEach((el) => {
                el.addEventListener("mouseenter", handleHover);
                el.addEventListener("mouseleave", handleUnHover);
            });
            return elements;
        };

        const hoverElements = refreshListeners();

        return () => {
            window.removeEventListener("mousemove", updatePosition);
            hoverElements.forEach((el) => {
                el.removeEventListener("mouseenter", handleHover);
                el.removeEventListener("mouseleave", handleUnHover);
            });
        };
    }, [isMobile]);

    if (isMobile) return null;

    return (
        <>
            <div ref={cursorRef} className="fixed top-0 left-0 pointer-events-none w-2 h-2 rounded-full bg-white mix-blend-difference z-9998" />
            <div ref={backdropRef} className="fixed top-0 left-0 pointer-events-none w-4 h-4 rounded-full border border-slate-400 bg-white/5 z-9997" style={{willChange: "width, height, transform"}} />
        </>
    );
};

export default Cursor;

Theme Management

Theme Clipper

Source

A smooth theme switcher utilizing the View Transition API for a circular expansion effect. It persists user preferences and respects system settings.

Dependency Checklist: motion/react | react-hot-toast

SVG Assets: Requires Moon.svg and Sun.svg to be present in your public folder.

View Transition Styles

::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
}

* {
    transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

Live Preview

COMPONENT

Interactive UI

Rect Tip

Source

The Cursor component replaces the standard browser pointer with a minimalist, interactive dual-element system. It is context-aware, automatically adjusting its shape and behavior based on the elements it interacts with.

The RectTip is actually a hyperlink

Dependency Checklist: motion/react

Props: heading, description, photo, link, width, height

Note: The RectTip is actually a hyperlink
CODE