Testimonios en marquesina
Dos filas de citas que corren en sentidos opuestos y se pausan al apuntar o enfocar. Textos de muestra.
Lo que dicen quienes ya trabajaron con nosotros
“Pasamos de hojas de cálculo sueltas a un solo sistema. Lo notamos en la primera semana.”
“Explicaron todo sin tecnicismos. Siempre supimos qué venía y cuánto costaba.”
“Las entregas semanales nos dejaron corregir el rumbo a tiempo.”
“El soporte después del lanzamiento fue lo que nos hizo confiar.”
“Cada decisión quedó por escrito. Eso en auditoría vale oro.”
“Por fin una herramienta que se adapta a cómo trabajamos.”
Testimonios de muestra para la demostración.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import type { CSSProperties } from "react";
/** Sección de testimonios con dos filas en marquesina que corren en sentidos opuestos y se pausan al apuntar o enfocar. Textos y nombres de muestra. React + TypeScript, sin dependencias. */
export type SeccionTestimoniosItem = { quote: string; name: string; role: string };
export type SeccionTestimoniosProps = {
title?: string;
items?: SeccionTestimoniosItem[];
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcstm-root { box-sizing: border-box; container-type: inline-size; width: 100%; padding: clamp(32px, 6cqi, 80px) 0; background: #f6f3ec; color: #1c1a17; font: 400 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; overflow: hidden; }
.gcstm-root *, .gcstm-root *::before, .gcstm-root *::after { box-sizing: border-box; }
.gcstm-root :focus-visible { outline: 2px solid #1c1a17; outline-offset: 3px; }
.gcstm-title { margin: 0 auto 36px; max-width: 18ch; padding: 0 20px; text-align: center; font: 500 clamp(30px, 5.4cqi, 60px)/1.04 "Newsreader", Georgia, ui-serif, serif; letter-spacing: -.03em; text-wrap: balance; }
.gcstm-rows { display: grid; gap: 16px; -webkit-mask: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); mask: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); }
.gcstm-row { display: flex; width: max-content; gap: 16px; animation: gcstm-run var(--t) linear infinite; animation-direction: var(--dir, normal); }
.gcstm-rows:hover .gcstm-row, .gcstm-rows:focus-within .gcstm-row { animation-play-state: paused; }
.gcstm-card { flex: none; width: 340px; padding: 22px; border: 1px solid #e3ddd0; border-radius: 16px; background: #fff; }
.gcstm-q { margin: 0 0 18px; font: 400 17px/1.45 "Newsreader", Georgia, ui-serif, serif; }
.gcstm-who { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.gcstm-av { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: #1c1a17; color: #f6f3ec; font-weight: 700; }
.gcstm-who span { display: block; color: #6e685b; }
.gcstm-note { margin: 22px 0 0; text-align: center; font-size: 12px; color: #8a8374; }
@keyframes gcstm-run { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .gcstm-row { animation: none; width: auto; flex-wrap: wrap; justify-content: center; padding: 0 16px; } .gcstm-row [data-dup="true"] { display: none; } .gcstm-rows { -webkit-mask: none; mask: none; } }`;
const DEFAULT: SeccionTestimoniosItem[] = [
{ quote: "Pasamos de hojas de cálculo sueltas a un solo sistema. Lo notamos en la primera semana.", name: "Persona de muestra", role: "Dirección de operaciones" },
{ quote: "Explicaron todo sin tecnicismos. Siempre supimos qué venía y cuánto costaba.", name: "Persona de muestra", role: "Dueña de negocio" },
{ quote: "Las entregas semanales nos dejaron corregir el rumbo a tiempo.", name: "Persona de muestra", role: "Gerencia de ventas" },
{ quote: "El soporte después del lanzamiento fue lo que nos hizo confiar.", name: "Persona de muestra", role: "Administración" },
{ quote: "Cada decisión quedó por escrito. Eso en auditoría vale oro.", name: "Persona de muestra", role: "Cumplimiento" },
{ quote: "Por fin una herramienta que se adapta a cómo trabajamos.", name: "Persona de muestra", role: "Logística" },
];
function Row({ items, dir, t }: { items: SeccionTestimoniosItem[]; dir: "normal" | "reverse"; t: string }) {
return (
<div className="gcstm-row" style={{ "--dir": dir, "--t": t } as CSSProperties}>
{[0, 1].map((n) =>
items.map((item, i) => (
<figure key={`${n}-${i}`} className="gcstm-card" data-dup={n === 1} aria-hidden={n === 1 || undefined} tabIndex={n === 1 ? -1 : 0} style={{ margin: 0 }}>
<blockquote className="gcstm-q" style={{ margin: "0 0 18px" }}>“{item.quote}”</blockquote>
<figcaption className="gcstm-who">
<span className="gcstm-av" aria-hidden="true">{item.name[0]}</span>
<div>
{item.name}
<span>{item.role}</span>
</div>
</figcaption>
</figure>
)),
)}
</div>
);
}
export function SeccionTestimonios({ title = "Lo que dicen quienes ya trabajaron con nosotros", items = DEFAULT, className = "", style }: SeccionTestimoniosProps) {
const half = Math.ceil(items.length / 2);
return (
<section className={`gcstm-root ${className}`} style={style} aria-label="Testimonios">
<style>{CSS}</style>
<h2 className="gcstm-title">{title}</h2>
<div className="gcstm-rows">
<Row items={items.slice(0, half)} dir="normal" t="46s" />
<Row items={items.slice(half)} dir="reverse" t="52s" />
</div>
<p className="gcstm-note">Testimonios de muestra para la demostración.</p>
</section>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/seccion-testimoniosEjemplo de uso de Testimonios en marquesina
Después de copiar seccion-testimonios.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { SeccionTestimonios } from "./seccion-testimonios";
export default function Ejemplo() {
return <SeccionTestimonios title="Ejemplo" />;
}Propiedades de SeccionTestimonios
SeccionTestimonios acepta 4 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
title? | string | Opcional. |
items? | SeccionTestimoniosItem[] | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Testimonios en marquesina con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--dir | normal |
Accesibilidad y rendimiento
El archivo tiene 76 líneas (5 KB) y define 1 animación con @keyframes. Esto es lo que hace Testimonios en marquesina 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-*.
- Es un componente de cliente de Next.js: incluye la directiva "use client".