Image Generation Process
An animated image generation button with processing state and final output preview.
Tags: Figma, Animation, AI, Generation
React Code
"use client";
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Sparkles } from "lucide-react";
export default function ImageGeneration() {
const [status, setStatus] = useState<"idle" | "generating" | "completed">("idle");
const [progress, setProgress] = useState(0);
useEffect(() => {
if (status === "generating") {
let currentProgress = 0;
const interval = setInterval(() => {
currentProgress += Math.floor(Math.random() * 6) + 2;
if (currentProgress >= 100) {
currentProgress = 100;
clearInterval(interval);
setTimeout(() => setStatus("completed"), 500);
}
setProgress(currentProgress);
}, 120);
return () => clearInterval(interval);
}
}, [status]);
const handleReset = () => {
setStatus("idle");
setProgress(0);
};
const renderDots = () => {
const dots = [];
for (let row = 0; row < 14; row++) {
for (let col = 0; col < 14; col++) {
// Missing dots logic for the center void
if (row === 5 && col >= 5 && col <= 8) continue;
if (row === 6 && col >= 4 && col <= 9) continue;
if (row === 7 && col >= 4 && col <= 9) continue;
if (row === 8 && col >= 5 && col <= 8) continue;
dots.push(
<div
key={`${row}-${col}`}
className="absolute bg-[#E6E9EC] rounded-full"
style={{
width: "8px",
height: "8px",
left: `${27 + col * 27.615}px`,
top: `${27 + row * 27}px`
}}
/>
);
}
}
return dots;
};
return (
<div className="w-full flex items-center justify-center p-8 bg-transparent">
{/* Main Container - EXACT size 412x412, matching Figma properties */}
<div
className="relative w-[412px] h-[412px] shrink-0 rounded-[40px] overflow-clip bg-[#FBFCFD] shadow-[0px_0px_0px_1.5px_rgba(255,255,255,0.3)]"
>
{/* Inner white glow overlay specified in Figma */}
<div className="absolute inset-0 pointer-events-none rounded-[40px] shadow-[inset_0px_0px_4px_4px_rgba(255,255,255,0.1)] z-50" />
{/* Pixel-Perfect Dot Matrix Background */}
<div className="absolute inset-0 pointer-events-none z-0">
{renderDots()}
</div>
<AnimatePresence mode="wait">
{/* State 1: IDLE */}
{status === "idle" && (
<motion.div
key="idle"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3 }}
className="absolute inset-0 z-10"
>
<motion.div
onClick={() => setStatus("generating")}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="-translate-x-1/2 -translate-y-1/2 absolute bg-[#f7f9fb] border border-[#e6e9ec] border-solid content-stretch flex items-center justify-center left-[calc(50%+0.5px)] px-[24px] py-[16px] rounded-[16px] top-[calc(50%+0.5px)] cursor-pointer"
>
<p
className="[word-break:break-word] bg-clip-text font-sans font-medium leading-[1.2] relative shrink-0 text-[16px] text-[transparent] text-left whitespace-nowrap"
style={{
backgroundImage: "linear-gradient(90deg, rgb(219, 113, 0) 0%, rgb(227, 79, 79) 16.346%, rgb(234, 214, 145) 38.462%, rgb(158, 230, 127) 67.788%, rgb(132, 198, 234) 81.25%, rgb(0, 150, 61) 100%), linear-gradient(90deg, rgb(21, 102, 229) 0%, rgb(21, 102, 229) 100%)"
}}
>
Generate
</p>
</motion.div>
</motion.div>
)}
{/* State 2: GENERATING */}
{status === "generating" && (
<motion.div
key="generating"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { duration: 0.4 } }}
className="absolute inset-0 z-20"
>
{/* Pulsing Dot - Shrinking and growing per request */}
<div className="-translate-x-1/2 -translate-y-1/2 absolute left-[calc(50%-3px)] top-[calc(50%+3px)] size-[66px]">
<motion.div
animate={{ scale: [1, 0.8, 1] }}
transition={{ repeat: Infinity, duration: 1.5, ease: "easeInOut" }}
className="relative w-full h-full"
>
<div className="absolute left-0 top-0 size-[66px] rounded-[55px] drop-shadow-[0px_10px_5px_rgba(0,0,0,0.2)] bg-transparent" />
<div className="absolute left-[7px] top-[7px] size-[52px] rounded-[57.2px] overflow-clip flex items-center justify-center">
<div aria-hidden className="absolute inset-0 pointer-events-none rounded-[57.2px] bg-[#FF4B00]" />
<Sparkles className="text-white size-[21px] relative z-10" strokeWidth={2.5} />
<div className="absolute inset-0 pointer-events-none rounded-[inherit] shadow-[inset_0px_0px_15px_2px_rgba(255,255,255,0.7)]" />
</div>
</motion.div>
</div>
{/* Progress Pill */}
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1, duration: 0.3 }}
className="absolute left-[30px] top-[351px] bg-white border border-[#D7DADC] border-solid flex items-center justify-center px-[16px] py-[8px] rounded-[40px] overflow-clip"
>
<p className="font-sans font-medium text-[#626467] text-[16px] leading-[1.2] relative shrink-0 whitespace-nowrap w-[4ch] text-center">
{progress}%
</p>
</motion.div>
</motion.div>
)}
{/* State 3: COMPLETED */}
{status === "completed" && (
<motion.div
key="completed"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="absolute inset-0 z-30 cursor-pointer group"
onClick={handleReset}
>
<img
src="https://images.unsplash.com/photo-1707343843437-caacff5cfa74?q=80&w=800&auto=format&fit=crop"
alt="Generated Graphic"
className="object-cover w-[412px] h-[412px] rounded-[40px] pointer-events-none"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-300 rounded-[40px] flex items-center justify-center">
<motion.div
initial={{ opacity: 0 }}
whileHover={{ opacity: 1 }}
className="bg-black/50 backdrop-blur-sm text-white px-4 py-2 rounded-full font-medium text-sm opacity-0 transition-opacity"
>
Click to reset
</motion.div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}