dECIPHER CARD
A high-fidelity terminal-style card component. Hover to initiate a brute-force decryption sequence that reveals the hidden message and background content.
Live Preview
Decipher CardCARD_01
Dependencies
npm install
framer-motionUtils
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Code
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
const DecipherCard = ({
title = "CARD_01",
targetText = "I BUILT THIS SH*T, ME. BRICK BY BRICK.",
bgImage = "",
}) => {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#%&@*+=-";
const [displayText, setDisplayText] = useState("");
const [bgText, setBgText] = useState("");
const [isHovered, setIsHovered] = useState(false);
const [isRevealed, setIsRevealed] = useState(false);
const generateNoise = (length: number) => {
return Array.from(
{ length },
() => chars[Math.floor(Math.random() * chars.length)],
).join("");
};
useEffect(() => {
let interval: any;
let revealTimeout: any;
if (isHovered) {
interval = setInterval(() => {
setBgText(generateNoise(1600));
}, 50);
revealTimeout = setTimeout(() => {
setIsRevealed(true);
clearInterval(interval);
let iteration = 0;
const innerInterval = setInterval(() => {
setDisplayText(
targetText
.split("")
.map((letter, index) => {
if (targetText[index] === " ") return " ";
if (index < iteration) return targetText[index];
return chars[Math.floor(Math.random() * chars.length)];
})
.join(""),
);
if (iteration >= targetText.length) clearInterval(innerInterval);
iteration += 1;
}, 30);
}, 1500);
} else {
setIsRevealed(false);
setBgText(generateNoise(1600)); // Static noise for rest mode
setDisplayText("");
clearTimeout(revealTimeout);
}
return () => {
clearInterval(interval);
clearTimeout(revealTimeout);
};
}, [isHovered, targetText]);
return (
<div className="relative flex items-center justify-center">
<motion.div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className="z-10 relative w-80 h-[450px] bg-black border border-white/20 rounded-sm overflow-hidden p-6 flex flex-col justify-between transition-colors duration-500 hover:border-white/80 shadow-2xl"
style={{ fontFamily: "'Space Mono', monospace" }}
>
<AnimatePresence>
{!isRevealed && (
<motion.div
initial={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, ease: "anticipate" }}
className="absolute cursor-default w-80 h-[450px] inset-0 z-30 bg-black p-4 rounded-sm overflow-hidden pointer-events-none break-all"
>
<p className="text-white overflow-hidden h-[410px] font-mono text-[10px] leading-tight text-justify opacity-80">
{bgText}
</p>
</motion.div>
)}
</AnimatePresence>
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%)] bg-[length:100%_4px] z-20 opacity-20" />
<div
className="flex justify-between items-start z-10 h-full w-full bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: bgImage !== "" ? bgImage : "url('https://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/franklin_re7o3r.webp')",
}}
>
<div className="flex flex-col">
<span className="text-[10px] text-white/40 tracking-[0.3em] font-light italic uppercase">
Decipher Card
</span>
<span className="text-[10px] text-white/80 tracking-widest">
{title}
</span>
</div>
<div className="w-1.5 h-1.5 bg-cyan-400 shadow-[0_0_8px_#22d3ee]" />
</div>
<div className="z-10 py-10">
<motion.h2 className="text-xl font-bold tracking-tighter leading-tight text-white">
{displayText}
</motion.h2>
</div>
</motion.div>
</div>
);
};
export default DecipherCard;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | CARD_01 | Top-left label |
| targetText | string | I BUILT THIS SH*T, ME. BRICK BY BRICK. | Decoded msg |
| bgImage | string | - | image for background |