Ir al contenido
Evaluar mi proyecto
Componentes · TarjetasDisponible

Tarjeta con foco de luz

Un foco sigue al puntero e ilumina el borde; sin puntero recorre la tarjeta solo.

Automatización

Conecta tus herramientas y deja que el trabajo repetitivo se haga solo.

Integraciones

Una sola vista de los datos que hoy viven en cinco lugares.

Reportes

Cifras al día sin armar hojas de cálculo cada semana.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

spotlight-card.tsx70 líneas
Descargar
"use client";

/**
 * Tarjeta oscura con un foco de luz que sigue al puntero e ilumina el borde.
 * Sin puntero la luz recorre la tarjeta despacio, así que también se ve viva en pantallas táctiles.
 *
 *   <SpotlightCard accent="#7c5cff">
 *     <h3>Automatización</h3>
 *     <p>Conecta tus herramientas.</p>
 *   </SpotlightCard>
 */

import { useRef, type CSSProperties, type HTMLAttributes, type PointerEvent } from "react";

export type SpotlightCardProps = HTMLAttributes<HTMLDivElement> & {
  /** Color de la luz en hexadecimal. */
  accent?: string;
};

const CSS = `
@property --gcp-x { syntax: "<percentage>"; initial-value: 30%; inherits: false; }
@property --gcp-y { syntax: "<percentage>"; initial-value: 20%; inherits: false; }
.gcp-card { --gcp-accent: #7c5cff; --gcp-x: 30%; --gcp-y: 20%; position: relative; box-sizing: border-box; padding: 28px; overflow: hidden; border-radius: 18px; background: #0c0c11; color: #ecebf5; font-family: inherit;
  animation: gcp-roam 9s ease-in-out infinite alternate; }
.gcp-card[data-live="true"] { animation: none; }
.gcp-card::before, .gcp-card::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
.gcp-card::before { background: radial-gradient(360px circle at var(--gcp-x) var(--gcp-y), color-mix(in srgb, var(--gcp-accent) 22%, transparent), transparent 70%); }
.gcp-card::after { padding: 1px; background: radial-gradient(240px circle at var(--gcp-x) var(--gcp-y), var(--gcp-accent), rgba(255, 255, 255, .07) 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0); }
.gcp-card > * { position: relative; z-index: 1; }
.gcp-card h3 { margin: 0 0 8px; font-size: 19px; font-weight: 600; letter-spacing: -.01em; }
.gcp-card p { margin: 0; color: #a9a8bb; font-size: 15px; line-height: 1.55; }
@keyframes gcp-roam { 0% { --gcp-x: 18%; --gcp-y: 12%; } 50% { --gcp-x: 78%; --gcp-y: 34%; } 100% { --gcp-x: 42%; --gcp-y: 86%; } }
@media (prefers-reduced-motion: reduce) { .gcp-card { animation: none; } }
`;

export function SpotlightCard({ accent = "#7c5cff", className, style, onPointerMove, onPointerLeave, children, ...props }: SpotlightCardProps) {
  const ref = useRef<HTMLDivElement>(null);

  function move(event: PointerEvent<HTMLDivElement>) {
    const card = ref.current;
    if (card && event.pointerType !== "touch") {
      const box = card.getBoundingClientRect();
      card.dataset.live = "true";
      card.style.setProperty("--gcp-x", `${(((event.clientX - box.left) / box.width) * 100).toFixed(1)}%`);
      card.style.setProperty("--gcp-y", `${(((event.clientY - box.top) / box.height) * 100).toFixed(1)}%`);
    }
    onPointerMove?.(event);
  }

  function leave(event: PointerEvent<HTMLDivElement>) {
    const card = ref.current;
    if (card) {
      card.dataset.live = "false";
      card.style.removeProperty("--gcp-x");
      card.style.removeProperty("--gcp-y");
    }
    onPointerLeave?.(event);
  }

  return (
    <>
      <style>{CSS}</style>
      <div ref={ref} {...props} className={["gcp-card", className].filter(Boolean).join(" ")} style={{ ["--gcp-accent" as string]: accent, ...style } as CSSProperties} onPointerMove={move} onPointerLeave={leave}>
        {children}
      </div>
    </>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/tarjeta-spotlight

Ejemplo de uso de Tarjeta con foco de luz

Después de copiar spotlight-card.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { SpotlightCard } from "./spotlight-card";

export default function Ejemplo() {
  return <SpotlightCard />;
}

Accesibilidad y rendimiento

El archivo tiene 70 líneas (3 KB) y define 1 animación con @keyframes. Esto es lo que hace Tarjeta con foco de luz por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Reacciona al puntero con eventos pointer (ratón, lápiz y táctil).
  • Usa @property de CSS para animar valores que normalmente no se interpolan.
  • Calcula tonos con color-mix(), así que un solo color base alcanza.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React · Next.js (quita la directiva use client fuera de Next.js) · Sin Tailwind

Acerca del producto

Una tarjeta oscura con un foco de luz que sigue al cursor e ilumina el borde. Sin cursor, la luz recorre la tarjeta despacio (animación con @property), así que también se ve viva en pantallas táctiles.

Incluye

Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.

  • Color de la luz configurable (accent)
  • Borde iluminado con máscara, sin elementos extra
  • Animación de reposo con @property
  • Respeta prefers-reduced-motion

Cómo usarlo

Pon tu contenido dentro y dale ancho con style. Combina varias en una fila para un efecto de cuadrícula.