Ir al contenido
Evaluar mi proyecto
Componentes · SeccionesDisponible

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

Marca AMarca BMarca CMarca DMarca EMarca F

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.

seccion-logos.tsx70 líneas
Descargar
"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.

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

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

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

PropiedadTipoDescripción
title?stringOpcional.
items?SeccionLogosItem[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

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.

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