Nube de logos en marquesina
Marcas abstractas de muestra con bordes que se desvanecen, color al apuntar y pausa. Sustituye por las tuyas.
Empresas de muestra que confían en nosotros
Marcas abstractas de muestra. No representan clientes reales.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import type { CSSProperties, ReactNode } from "react";
/** Nube de logos en marquesina con bordes que se desvanecen, color al apuntar y pausa. Las marcas incluidas son formas abstractas de muestra, nunca clientes reales: sustitúyelas por las tuyas. React + TypeScript, sin dependencias. */
export type SeccionLogosItem = { name: string; mark: ReactNode };
export type SeccionLogosProps = {
title?: string;
items?: SeccionLogosItem[];
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcslg-root { box-sizing: border-box; container-type: inline-size; width: 100%; padding: clamp(28px, 5cqi, 64px) 0; background: #fff; color: #18181b; font: 500 15px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; overflow: hidden; }
.gcslg-root *, .gcslg-root *::before, .gcslg-root *::after { box-sizing: border-box; }
.gcslg-root :focus-visible { outline: 2px solid #2563eb; outline-offset: 3px; }
.gcslg-title { margin: 0 0 28px; text-align: center; font-size: 14px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: #71717a; }
.gcslg-track { display: flex; width: max-content; animation: gcslg-run 36s linear infinite; -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }
.gcslg-wrap { -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }
.gcslg-wrap .gcslg-track { -webkit-mask: none; mask: none; }
.gcslg-wrap:hover .gcslg-track, .gcslg-wrap:focus-within .gcslg-track { animation-play-state: paused; }
.gcslg-item { display: flex; align-items: center; gap: 12px; margin: 0 clamp(24px, 4cqi, 56px); color: #a1a1aa; font: 700 22px/1 ui-sans-serif, system-ui, sans-serif; letter-spacing: -.03em; white-space: nowrap; filter: grayscale(1); opacity: .8; transition: color .25s, filter .25s, opacity .25s; }
.gcslg-item:hover, .gcslg-item:focus-visible { color: var(--c, #18181b); filter: none; opacity: 1; }
.gcslg-item svg { width: 30px; height: 30px; }
.gcslg-note { margin: 22px 0 0; text-align: center; font-size: 12px; color: #a1a1aa; }
@keyframes gcslg-run { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .gcslg-track { animation: none; width: auto; flex-wrap: wrap; justify-content: center; row-gap: 20px; } .gcslg-track [aria-hidden="true"] { display: none; } .gcslg-wrap { -webkit-mask: none; mask: none; } }`;
const mark = (shape: ReactNode) => (
<svg viewBox="0 0 32 32" fill="currentColor" aria-hidden="true">
{shape}
</svg>
);
const DEFAULT: SeccionLogosItem[] = [
{ name: "Marca A", mark: mark(<circle cx="16" cy="16" r="13" />) },
{ name: "Marca B", mark: mark(<rect x="3" y="3" width="26" height="26" rx="7" />) },
{ name: "Marca C", mark: mark(<path d="M16 2 30 28H2z" />) },
{ name: "Marca D", mark: mark(<path d="M4 16a12 12 0 0 1 24 0v12H4z" />) },
{ name: "Marca E", mark: mark(<path d="M16 2l4 10 10 1-8 7 3 10-9-6-9 6 3-10-8-7 10-1z" />) },
{ name: "Marca F", mark: mark(<path d="M2 8h28v6H2zM2 18h20v6H2z" />) },
];
const HUES = ["#2f5bff", "#ff6a3d", "#18b37e", "#9b5de5", "#e5489a", "#0ea5b7"];
export function SeccionLogos({ title = "Empresas de muestra que confían en nosotros", items = DEFAULT, className = "", style }: SeccionLogosProps) {
return (
<section className={`gcslg-root ${className}`} style={style} aria-label="Logos">
<style>{CSS}</style>
<h2 className="gcslg-title">{title}</h2>
<div className="gcslg-wrap">
<div className="gcslg-track">
{[0, 1].map((n) => (
<div key={n} style={{ display: "flex" }} aria-hidden={n === 1 || undefined}>
{items.map((item, i) => (
<span key={item.name} className="gcslg-item" tabIndex={n === 0 ? 0 : -1} style={{ "--c": HUES[i % HUES.length] } as CSSProperties}>
{item.mark}
{item.name}
</span>
))}
</div>
))}
</div>
</div>
<p className="gcslg-note">Marcas abstractas de muestra. No representan clientes reales.</p>
</section>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/seccion-logosEjemplo de uso de Nube de logos en marquesina
Después de copiar seccion-logos.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { SeccionLogos } from "./seccion-logos";
export default function Ejemplo() {
return <SeccionLogos title="Ejemplo" />;
}Propiedades de SeccionLogos
SeccionLogos 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? | SeccionLogosItem[] | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Nube de logos 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 |
|---|---|
--c | #18181b |
Accesibilidad y rendimiento
El archivo tiene 70 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Nube de logos 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".