Shatter Reveal Card

The is a high-end UI component that acts as a "digital veil." It hides an image behind a grid of flickering binary pixels (0s and 1s). When you hover over it, the pixels shatter and fade away in a random, staggered sequence to reveal the content beneath.

Live Preview
Card for:Peaky Blinders

One minute. That's all it takes. Sometimes, one minute is all you have

Dependencies

npm installframer-motion

Code

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

const ShatterRevealCard = ({
  title = "Peaky Blinders",
  image = "https://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/peaky_vizcxc.jpg",
}) => {
  const [isHovered, setIsHovered] = useState(false);

  const rows = 20;
  const cols = 15;
  const totalBoxes = rows * cols;
  const [shuffledIndices, setShuffledIndices] = useState<any>([]);

  useEffect(() => {
    const indices = [...Array(totalBoxes).keys()];
    for (let i = indices.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [indices[i], indices[j]] = [indices[j], indices[i]];
    }
    setShuffledIndices(indices);
  }, [totalBoxes]);

  return (
    <motion.div
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      className="relative w-80 h-[450px] bg-black border border-white/20 rounded-sm overflow-hidden shadow-2xl group cursor-default"
      style={{ fontFamily: "'Space Mono', monospace" }}
    >
      <div
        className="absolute inset-0 z-0 transition-transform duration-700 group-hover:scale-105"
        style={{
          backgroundImage: `url(${image})`,
          backgroundSize: "cover",
          backgroundPosition: "center",
        }}
      />

      <div
        className="absolute inset-0 z-10 grid"
        style={{
          gridTemplateColumns: `repeat(${cols}, 1fr)`,
          gridTemplateRows: `repeat(${rows}, 1fr)`,
        }}
      >
        {shuffledIndices.map((originalIndex: any, orderIndex: any) => (
          <motion.div
            key={originalIndex}
            initial={false}
            animate={{
              opacity: isHovered ? 0 : 1,
              scale: isHovered ? 0.8 : 1,
            }}
            transition={{
              duration: 0.4,
              delay: isHovered ? (orderIndex / totalBoxes) * 1.5 : 0,
              ease: "easeOut",
            }}
            className="w-full h-full bg-black z-20 border-[0.5px] border-white/5"
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
            }}
          >
            {!isHovered && (
              <motion.span
                animate={{ opacity: [0.1, 0.5, 0.1] }}
                transition={{
                  repeat: Infinity,
                  duration: Math.random() * 2 + 1,
                }}
                className="text-[10px] text-white"
              >
                {Math.random() > 0.5 ? "0" : "1"}
              </motion.span>
            )}
          </motion.div>
        ))}
      </div>

      <div className="absolute inset-0 z-0 p-6 flex flex-col justify-between pointer-events-none">
        <div className="flex justify-between items-start">
          <div className="bg-black/80 px-2 py-1 border border-white/10">
            <span className="text-[10px] text-white/40 tracking-[0.3em] italic uppercase block">
              Card for:
            </span>
            <span className="text-[10px] text-white tracking-widest">
              {title}
            </span>
          </div>
          <div className="w-2 h-2 bg-red-500 shadow-[0_0_10px_red] animate-pulse" />
        </div>

        <AnimatePresence>
          <motion.div
            exit={{ opacity: 0, x: -20 }}
            className="bg-black/60 backdrop-blur-sm p-4 border-l-2 border-white"
          >
            <p className="text-[14px] text-white tracking-tighter leading-tight uppercase">
              One minute. That's all it takes. Sometimes, one minute is all you
              have
            </p>
          </motion.div>
        </AnimatePresence>
      </div>

      <div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px] z-30 opacity-20" />
    </motion.div>
  );
};

export default ShatterRevealCard;

Props

PropTypeDefaultDescription
titlestringPeaky BlindersThe text shown in the top corner badge.
image stringhttps://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/peaky_vizcxc.jpgThe background image revealed on hover.