Ir al contenido
Evaluar mi proyecto
Componentes · SeccionesDisponible

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.

seccion-cta.tsx75 líneas
Descargar
"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.

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

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

ejemplo.tsx5 líneas
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.

PropiedadTipoDescripción
title?stringOpcional.
description?stringOpcional.
action?stringOpcional.
secondary?stringOpcional.
href?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)