Banda de llamada a la acción
Titular enorme, texto corrido de fondo y un botón magnético que sigue al puntero y se rellena con color.
Cuéntanos qué quieres resolver.
Una conversación de 30 a 45 minutos, sin costo. Sales con claridad aunque no trabajemos juntos.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useRef, type CSSProperties, type PointerEvent } from "react";
/** Banda de llamada a la acción final: titular enorme, texto corrido de fondo y un botón magnético que sigue al puntero y se rellena con color. Oscura y directa. React + TypeScript, sin dependencias. */
export type SeccionCtaProps = {
title?: string;
description?: string;
action?: string;
secondary?: string;
href?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcsca-root { box-sizing: border-box; container-type: inline-size; position: relative; width: 100%; overflow: hidden; padding: clamp(48px, 9cqi, 120px) clamp(18px, 5cqi, 72px); background: #0c0c0e; color: #f4f2ec; font: 500 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; text-align: center; }
.gcsca-root *, .gcsca-root *::before, .gcsca-root *::after { box-sizing: border-box; }
.gcsca-root a { -webkit-tap-highlight-color: transparent; }
.gcsca-root :focus-visible { outline: 2px solid #ff6a3d; outline-offset: 5px; }
@media (prefers-reduced-motion: reduce) { .gcsca-run { animation: none !important; } .gcsca-btn, .gcsca-btn span { transition: none !important; } }
.gcsca-bg { position: absolute; inset: 0; display: grid; align-content: center; gap: 6px; overflow: hidden; pointer-events: none; opacity: .09; }
.gcsca-run { display: flex; width: max-content; white-space: nowrap; font: 900 clamp(60px, 14cqi, 200px)/.9 "Arial Black", ui-sans-serif, system-ui, sans-serif; letter-spacing: -.04em; text-transform: uppercase; animation: gcsca-run 40s linear infinite; }
.gcsca-run:nth-child(2) { animation-direction: reverse; -webkit-text-stroke: 2px #fff; color: transparent; }
.gcsca-body { position: relative; display: grid; justify-items: center; gap: 24px; }
.gcsca-title { margin: 0; max-width: 14ch; font: 700 clamp(40px, 8cqi, 100px)/.98 ui-sans-serif, system-ui, sans-serif; letter-spacing: -.05em; text-wrap: balance; }
.gcsca-desc { margin: 0; max-width: 44ch; color: #b6b2a6; font-size: clamp(16px, 1.8cqi, 20px); text-wrap: pretty; }
.gcsca-actions { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 18px 28px; margin-top: 8px; }
.gcsca-btn { position: relative; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; min-height: 64px; padding: 0 40px; border-radius: 999px; background: #f4f2ec; color: #0c0c0e; font-weight: 700; font-size: 18px; text-decoration: none; will-change: transform; transition: transform .35s cubic-bezier(.16, 1, .3, 1); }
.gcsca-btn::before { content: ""; position: absolute; inset: 0; background: #ff6a3d; border-radius: 50%; transform: translateY(170%) scale(1.4); transition: transform .5s cubic-bezier(.16, 1, .3, 1); }
.gcsca-btn:hover::before, .gcsca-btn:focus-visible::before { transform: translateY(0) scale(1.4); }
.gcsca-btn span { position: relative; transition: color .3s; }
.gcsca-btn:hover span { color: #fff; }
.gcsca-sec { color: #f4f2ec; text-underline-offset: 6px; font-weight: 600; }
@keyframes gcsca-run { to { transform: translateX(-50%); } }`;
export function SeccionCta({ title = "Cuéntanos qué quieres resolver.", description = "Una conversación de 30 a 45 minutos, sin costo. Sales con claridad aunque no trabajemos juntos.", action = "Agendar diagnóstico", secondary = "Ver cómo trabajamos", href = "#", className = "", style }: SeccionCtaProps) {
const btn = useRef<HTMLAnchorElement>(null);
function move(event: PointerEvent<HTMLElement>) {
if (event.pointerType === "touch") return;
const rect = btn.current!.getBoundingClientRect();
const dx = event.clientX - (rect.left + rect.width / 2);
const dy = event.clientY - (rect.top + rect.height / 2);
const d = Math.hypot(dx, dy);
const k = d < 160 ? (1 - d / 160) * 0.35 : 0;
btn.current!.style.transform = `translate(${dx * k}px, ${dy * k}px)`;
}
const words = "Hablemos de tu proyecto · ".repeat(6);
return (
<section className={`gcsca-root ${className}`} style={style} onPointerMove={move} onPointerLeave={() => btn.current && (btn.current.style.transform = "")}>
<style>{CSS}</style>
<div className="gcsca-bg" aria-hidden="true">
<div className="gcsca-run">
<span>{words}</span>
<span>{words}</span>
</div>
<div className="gcsca-run">
<span>{words}</span>
<span>{words}</span>
</div>
</div>
<div className="gcsca-body">
<h2 className="gcsca-title">{title}</h2>
<p className="gcsca-desc">{description}</p>
<div className="gcsca-actions">
<a ref={btn} className="gcsca-btn" href={href}>
<span>{action}</span>
</a>
<a className="gcsca-sec" href={href}>{secondary}</a>
</div>
</div>
</section>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/seccion-ctaEjemplo de uso de Banda de llamada a la acción
Después de copiar seccion-cta.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { SeccionCta } from "./seccion-cta";
export default function Ejemplo() {
return <SeccionCta title="Ejemplo" />;
}Propiedades de SeccionCta
SeccionCta acepta 7 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
title? | string | Opcional. |
description? | string | Opcional. |
action? | string | Opcional. |
secondary? | string | Opcional. |
href? | string | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Accesibilidad y rendimiento
El archivo tiene 75 líneas (5 KB) y define 1 animación con @keyframes. Esto es lo que hace Banda de llamada a la acción por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Expone su estado a tecnologías de apoyo con atributos aria-*.
- Reacciona al puntero con eventos pointer (ratón, lápiz y táctil).
- Es un componente de cliente de Next.js: incluye la directiva "use client".