shadimage
uitripled
uiai-unlock-animation-shadcnuisource: ok · hit

AI Unlock Animation

Premium unlock animation with particles, ripples, and loading states

npx shadcn add "https://ui.tripled.work/r/ai-unlock-animation-shadcnui.json"
free

Where it goes

statsblog-list

Visual style

particle

Interaction

animated-counter

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationframer-motion

Files1 · source cached 10/8/2026, 7:05:04 PM

@uitripled/react-shadcn/src/components/motion-core/ai-unlock-animation.tsx
"use client";

import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useState } from "react";

type AIUnlockAnimationProps = {
  autoPlay?: boolean;
};

export function AIUnlockAnimation({ autoPlay = true }: AIUnlockAnimationProps) {
  const [isUnlocking, setIsUnlocking] = useState(false);
  const [isComplete, setIsComplete] = useState(false);

  // Auto-start if autoplay is enabled
  useEffect(() => {
    if (autoPlay) {
      setIsUnlocking(true);
      setIsComplete(false);
      const timer = setTimeout(() => {
        setIsComplete(true);
      }, 3000);
      return () => clearTimeout(timer);
    }
  }, [autoPlay]);

  const triggerUnlock = () => {
    setIsUnlocking(true);
    setIsComplete(false);
    setTimeout(() => {
      setIsComplete(true);
    }, 3000);
  };

  const resetAnimation = () => {
    setIsUnlocking(false);
    setIsComplete(false);
  };

  // Generate minimal particles
  const particles = Array.from({ length: 24 }, (_, i) => ({
    id: i,
    angle: (Math.PI * 2 * i) / 24,
    delay: Math.random() * 0.3,
    duration: 1.2 + Math.random() * 0.5,
  }));

  return (
    <div className="relative w-full max-w-md">
      {/* Main content card */}
      <motion.div
        className="relative bg-card border border-border rounded-2xl p-16 shadow-sm"
        animate={
          isUnlocking
            ? {
                boxShadow: [
                  "0 1px 3px 0 rgb(0 0 0 / 0.1)",
                  "0 0 0 4px rgb(0 0 0 / 0.05)",
                  "0 1px 3px 0 rgb(0 0 0 / 0.1)",
                ],
              }
            : {}
        }
        transition={{ duration: 1.5, ease: "easeInOut" }}
      >
        {/* Ripple effects */}
        <AnimatePresence>
          {isUnlocking && (
            <>
              {[0, 0.2, 0.4].map((delay, i) => (
                <motion.div
                  key={i}
                  className="absolute inset-0 rounded-2xl border border-foreground z-0"
                  initial={{ scale: 1, opacity: 0.3 }}
                  style={{ zIndex: -1 }}
                  animate={{ scale: 2, opacity: 0 }}
                  exit={{ opacity: 0 }}
                  transition={{ duration: 1.5, delay, ease: "easeOut" }}
                />
              ))}
            </>
          )}
        </AnimatePresence>

        {/* Particle system */}
        <AnimatePresence>
          {isUnlocking &&
            particles.map((particle) => (
              <motion.div
                key={particle.id}
                className="absolute w-0.5 h-0.5 bg-foreground rounded-full"
                style={{
                  left: "50%",
                  top: "50%",
                }}
                initial={{ scale: 0, x: 0, y: 0, opacity: 0 }}
                animate={{
                  scale: [0, 1, 0],
                  x: Math.cos(particle.angle) * 200,
                  y: Math.sin(particle.angle) * 200,
                  opacity: [0, 0.6, 0],
                }}
                transition={{
                  duration: particle.duration,
                  delay: particle.delay,
                  ease: "easeOut",
                }}
              />
            ))}
        </AnimatePresence>

        {/* AI Icon */}
        <div className="relative z-10 flex flex-col items-center space-y-8">
          <motion.div
            className="relative"
            animate={
              isUnlocking
                ? {
                    scale: [1, 1.1, 1],
                  }
                : {}
            }
            transition={{ duration: 1.5, ease: "easeInOut" }}
          >
            {/* Minimal rings */}
            <AnimatePresence>
              {isUnlocking && (
                <>
                  <motion.div
                    className="absolute inset-0 rounded-full border border-foreground"
                    initial={{ scale: 1, opacity: 0.4 }}
                    animate={{ scale: 1.8, opacity: 0 }}
                    transition={{ duration: 1.2, repeat: Infinity }}
                  />
                  <motion.div
                    className="absolute inset-0 rounded-full border border-border"
                    initial={{ scale: 1, opacity: 0.3 }}
                    animate={{ scale: 1.8, opacity: 0 }}
                    transition={{ duration: 1.2, delay: 0.4, repeat: Infinity }}
                  />
                </>
              )}
            </AnimatePresence>

            {/* AI Icon - Simple circle with sparkle */}
            <motion.div
              className="relative w-20 h-20 rounded-full border-2 border-foreground bg-card flex items-center justify-center"
              animate={
                isUnlocking
                  ? {
                      borderColor: [
                        "hsl(var(--foreground))",
                        "hsl(var(--muted-foreground))",
                        "hsl(var(--foreground))",
                      ],
                    }
                  : {}
              }
              transition={{ duration: 1.5, repeat: isUnlocking ? Infinity : 0 }}
            >
              <svg
                className="w-9 h-9 text-foreground"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={1.5}
                  d="M13 10V3L4 14h7v7l9-11h-7z"
                />
              </svg>
            </motion.div>
          </motion.div>

          {/* Text animations */}
          <div className="text-center space-y-4">
            <AnimatePresence mode="wait">
              {!isUnlocking && !isComplete && (
                <motion.h2
                  initial={{ opacity: 0, y: 10 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: -10 }}
                  className="text-2xl font-semibold text-foreground"
                >
                  Unlock Lifetime Access
                </motion.h2>
              )}

              {isUnlocking && !isComplete && (
                <motion.h2
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                  exit={{ opacity: 0 }}
                  className="text-2xl font-semibold text-foreground"
                >
                  Activating...
                </motion.h2>
              )}

              {isComplete && (
                <motion.div
                  initial={{ opacity: 0, y: 10 }}
                  animate={{ opacity: 1, y: 0 }}
                  className="space-y-2"
                >
                  <motion.h2
                    className="text-2xl font-semibold text-foreground"
                    animate={{ scale: [1, 1.02, 1] }}
                    transition={{ duration: 0.4 }}
                  >
                    Unlocked
                  </motion.h2>
                  <motion.p
                    initial={{ opacity: 0 }}
                    animate={{ opacity: 1 }}
                    transition={{ delay: 0.2 }}
                    className="text-sm text-muted-foreground"
                  >
                    Premium features activated
                  </motion.p>
                </motion.div>
              )}
            </AnimatePresence>

            {/* Minimal loading bar */}
            {isUnlocking && !isComplete && (
              <motion.div
                className="w-48 h-px bg-border overflow-hidden"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
              >
                <motion.div
                  className="h-full bg-foreground"
                  initial={{ width: "0%" }}
                  animate={{ width: "100%" }}
                  transition={{ duration: 2.8, ease: "easeInOut" }}
                />
              </motion.div>
            )}
          </div>

          {/* Action buttons */}
          <div className="flex gap-3 pt-2">
            {!isUnlocking && !isComplete && (
              <motion.button
                onClick={triggerUnlock}
                className="px-6 py-2 bg-foreground text-background text-sm font-medium rounded-md hover:opacity-90 transition-opacity"
                whileHover={{ scale: 1.02 }}
                whileTap={{ scale: 0.98 }}
              >
                Activate
              </motion.button>
            )}

            {isComplete && (
              <motion.button
                onClick={resetAnimation}
                className="px-6 py-2 border border-border text-foreground text-sm font-medium rounded-md hover:bg-accent transition-colors"
                initial={{ opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                whileHover={{ scale: 1.02 }}
                whileTap={{ scale: 0.98 }}
              >
                Replay
              </motion.button>
            )}
          </div>
        </div>

        {/* Corner accents - minimal */}
        <AnimatePresence>
          {isUnlocking && (
            <>
              {["top-left", "top-right", "bottom-left", "bottom-right"].map(
                (corner, i) => (
                  <motion.div
                    key={corner}
                    className={`absolute w-8 h-8 border-foreground ${
                      corner === "top-left"
                        ? "top-0 left-0 border-t border-l"
                        : corner === "top-right"
                          ? "top-0 right-0 border-t border-r"
                          : corner === "bottom-left"
                            ? "bottom-0 left-0 border-b border-l"
                            : "bottom-0 right-0 border-b border-r"
                    }`}
                    initial={{ opacity: 0, scale: 0.8 }}
                    animate={{ opacity: [0, 0.4, 0], scale: [0.8, 1, 1] }}
                    transition={{ duration: 0.8, delay: i * 0.05 }}
                  />
                )
              )}
            </>
          )}
        </AnimatePresence>
      </motion.div>
    </div>
  );
}

More from uitripled

Browse all components in this library →