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.
"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.
npx shadcn@latest add https://www.gassierconsulting.com/r/tarjeta-spotlightEjemplo 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.
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".
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.