Baktiyar Ablaikhan

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).