Edge light
- Source
- After bencho.dev ↗
- Author
- Interaction after bencho.dev
- Licence
- Original code — interaction after bencho.dev
Loading…
Bring your pointer near the card and its edge lights up where you are.
Code
"use client";
import { motion, useMotionTemplate, useMotionValue, useSpring } from "motion/react";
import { Sparkles } from "lucide-react";
import { useRef } from "react";
import { Surface } from "../ui";
export default function EdgeLight() {
const card = useRef<HTMLDivElement>(null);
const mx = useMotionValue(50);
const my = useMotionValue(0);
const glow = useSpring(0, { stiffness: 200, damping: 30 });
const rx = useSpring(0, { stiffness: 180, damping: 18 });
const ry = useSpring(0, { stiffness: 180, damping: 18 });
const ring = useMotionTemplate`radial-gradient(14em circle at ${mx}% ${my}%, rgba(43,70,184,${glow}), rgba(43,70,184,0) 60%)`;
const sheen = useMotionTemplate`radial-gradient(18em circle at ${mx}% ${my}%, rgba(43,70,184,0.07), transparent 60%)`;
return (
<Surface
onPointerMove={(e) => {
const r = card.current!.getBoundingClientRect();
const x = ((e.clientX - r.left) / r.width) * 100;
const y = ((e.clientY - r.top) / r.height) * 100;
mx.set(x);
my.set(y);
// brighter as the pointer nears the card
const dx = Math.max(r.left - e.clientX, 0, e.clientX - r.right);
const dy = Math.max(r.top - e.clientY, 0, e.clientY - r.bottom);
glow.set(Math.max(0.15, 1 - Math.hypot(dx, dy) / 160));
const inside = dx === 0 && dy === 0;
rx.set(inside ? (y - 50) * -0.12 : 0);
ry.set(inside ? (x - 50) * 0.12 : 0);
}}
onPointerLeave={() => {
glow.set(0);
rx.set(0);
ry.set(0);
}}
>
<div className="[perspective:900px]">
<motion.div ref={card} style={{ rotateX: rx, rotateY: ry }} className="relative w-[17em] rounded-[1.2em] p-px">
{/* the lit edge is a gradient behind a 1px inset */}
<motion.div className="absolute inset-0 rounded-[1.2em] bg-[#e4e3de]" />
<motion.div className="absolute inset-0 rounded-[1.2em]" style={{ background: ring }} />
<div className="relative overflow-hidden rounded-[calc(1.2em-1px)] bg-white p-[1.3em]">
<motion.div className="pointer-events-none absolute inset-0" style={{ background: sheen }} />
<div className="relative">
<div className="mb-[1em] grid h-[2.4em] w-[2.4em] place-items-center rounded-[0.7em] bg-[#151515] text-white">
<Sparkles className="h-[1.1em] w-[1.1em]" />
</div>
<p className="text-[1.05em] font-medium">Pro plan</p>
<p className="mt-[0.3em] text-[0.8em] leading-snug text-[#8a8984]">Everything in Free, plus unlimited projects and a faster queue.</p>
<div className="mt-[1.1em] flex items-baseline gap-[0.3em]">
<span className="text-[1.6em] font-medium">$12</span>
<span className="text-[0.75em] text-[#8a8984]">/ month</span>
</div>
</div>
</div>
</motion.div>
</div>
</Surface>
);
}
Prompt
Build "Edge light", a card animation. What it does: Bring your pointer near the card and its edge lights up where you are. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using lucide-react, motion. Technique: an HTML canvas, the Motion library, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://bencho.dev (Interaction after bencho.dev, Original code — interaction after bencho.dev).