Docs
Warp Background
A Card with a time warping background effect.
Installation
Install dependencies
npm install framer-motion lucide-react
Run the following command
It will create a new file warp-background.tsx
inside the components/mage-ui/background
directory.
mkdir -p components/mage-ui/background && touch components/mage-ui/background/warp-background.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import React, { HTMLAttributes, useCallback, useMemo } from "react";
// Card Components
interface CardProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const Card: React.FC<CardProps> = ({ children, className, ...props }) => {
return (
<div
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
>
{children}
</div>
);
};
const CardContent: React.FC<CardProps> = ({ children, className, ...props }) => {
return (
<div className={cn("p-6 pt-0", className)} {...props}>
{children}
</div>
);
};
const CardTitle: React.FC<CardProps> = ({ children, className, ...props }) => {
return (
<h3
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
>
{children}
</h3>
);
};
const CardDescription: React.FC<CardProps> = ({ children, className, ...props }) => {
return (
<p
className={cn("text-sm text-muted-foreground", className)}
{...props}
>
{children}
</p>
);
};
// WarpBackground Component
interface WarpBackgroundProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
perspective?: number;
beamsPerSide?: number;
beamSize?: number;
beamDelayMax?: number;
beamDelayMin?: number;
beamDuration?: number;
gridColor?: string;
}
const Beam = ({
width,
x,
delay,
duration,
}: {
width: string | number;
x: string | number;
delay: number;
duration: number;
}) => {
const hue = Math.floor(Math.random() * 360);
const ar = Math.floor(Math.random() * 10) + 1;
return (
<motion.div
style={
{
"--x": `${x}`,
"--width": `${width}`,
"--aspect-ratio": `${ar}`,
"--background": `linear-gradient(hsl(${hue} 80% 60%), transparent)`,
} as React.CSSProperties
}
className={`absolute left-[var(--x)] top-0 [aspect-ratio:1/var(--aspect-ratio)] [background:var(--background)] [width:var(--width)]`}
initial={{ y: "100cqmax", x: "-50%" }}
animate={{ y: "-100%", x: "-50%" }}
transition={{
duration,
delay,
repeat: Infinity,
ease: "linear",
}}
/>
);
};
const WarpBackground: React.FC<WarpBackgroundProps> = ({
children,
perspective = 100,
className,
beamsPerSide = 3,
beamSize = 5,
beamDelayMax = 3,
beamDelayMin = 0,
beamDuration = 3,
gridColor = "rgb(229 231 235)", // Default border color
...props
}) => {
const generateBeams = useCallback(() => {
const beams = [];
const cellsPerSide = Math.floor(100 / beamSize);
const step = cellsPerSide / beamsPerSide;
for (let i = 0; i < beamsPerSide; i++) {
const x = Math.floor(i * step);
const delay =
Math.random() * (beamDelayMax - beamDelayMin) + beamDelayMin;
beams.push({ x, delay });
}
return beams;
}, [beamsPerSide, beamSize, beamDelayMax, beamDelayMin]);
const topBeams = useMemo(() => generateBeams(), [generateBeams]);
const rightBeams = useMemo(() => generateBeams(), [generateBeams]);
const bottomBeams = useMemo(() => generateBeams(), [generateBeams]);
const leftBeams = useMemo(() => generateBeams(), [generateBeams]);
return (
<div className={cn("relative rounded border p-20", className)} {...props}>
<div
style={
{
"--perspective": `${perspective}px`,
"--grid-color": gridColor,
"--beam-size": `${beamSize}%`,
} as React.CSSProperties
}
className={
"pointer-events-none absolute left-0 top-0 size-full overflow-hidden [clipPath:inset(0)] [container-type:size] [perspective:var(--perspective)] [transform-style:preserve-3d]"
}
>
{/* top side */}
<div className="absolute z-20 [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:50%_0%] [transform:rotateX(-90deg)] [width:100cqi]">
{topBeams.map((beam, index) => (
<Beam
key={`top-${index}`}
width={`${beamSize}%`}
x={`${beam.x * beamSize}%`}
delay={beam.delay}
duration={beamDuration}
/>
))}
</div>
{/* bottom side */}
<div className="absolute top-full [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:50%_0%] [transform:rotateX(-90deg)] [width:100cqi]">
{bottomBeams.map((beam, index) => (
<Beam
key={`bottom-${index}`}
width={`${beamSize}%`}
x={`${beam.x * beamSize}%`}
delay={beam.delay}
duration={beamDuration}
/>
))}
</div>
{/* left side */}
<div className="absolute left-0 top-0 [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:0%_0%] [transform:rotate(90deg)_rotateX(-90deg)] [width:100cqh]">
{leftBeams.map((beam, index) => (
<Beam
key={`left-${index}`}
width={`${beamSize}%`}
x={`${beam.x * beamSize}%`}
delay={beam.delay}
duration={beamDuration}
/>
))}
</div>
{/* right side */}
<div className="absolute right-0 top-0 [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [width:100cqh] [transform-origin:100%_0%] [transform:rotate(-90deg)_rotateX(-90deg)]">
{rightBeams.map((beam, index) => (
<Beam
key={`right-${index}`}
width={`${beamSize}%`}
x={`${beam.x * beamSize}%`}
delay={beam.delay}
duration={beamDuration}
/>
))}
</div>
</div>
<div className="relative">{children}</div>
</div>
);
};
// Main Demo Component
export default function WarpBackgroundDemo() {
return (
<div className="min-h-screen bg-background p-8 flex items-center justify-center">
<WarpBackground className="bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<Card className="w-80 bg-white/80 backdrop-blur-sm dark:bg-slate-900/80">
<CardContent className="flex flex-col gap-2 p-4">
<CardTitle className="text-slate-900 dark:text-slate-100">
Congratulations on Your Promotion!
</CardTitle>
<CardDescription className="text-slate-600 dark:text-slate-400">
Your hard work and dedication have paid off. We're thrilled to
see you take this next step in your career. Keep up the fantastic
work!
</CardDescription>
</CardContent>
</Card>
</WarpBackground>
</div>
);
}