Ir al contenido
Evaluar mi proyecto
Componentes · SeccionesDisponible

Equipo con tarjetas que se voltean

Tarjetas de iniciales que giran al apuntar o enfocar y muestran una frase corta. Sin fotos externas; personas de muestra.

Un equipo pequeño y senior

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

seccion-equipo.tsx70 líneas
Descargar
"use client";

import { useState, type CSSProperties } from "react";

/** Sección de equipo con tarjetas de iniciales: al apuntar o enfocar, la tarjeta se voltea y muestra una frase corta. Sin fotos externas. Personas de muestra. React + TypeScript, sin dependencias. */
export type SeccionEquipoPersona = { name: string; role: string; bio: string; hue: number };

export type SeccionEquipoProps = {
  title?: string;
  people?: SeccionEquipoPersona[];
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcseq-root { box-sizing: border-box; container-type: inline-size; width: 100%; padding: clamp(32px, 6cqi, 80px) clamp(18px, 5cqi, 72px); background: #fff; color: #18181b; font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
.gcseq-root *, .gcseq-root *::before, .gcseq-root *::after { box-sizing: border-box; }
.gcseq-root button { font: inherit; -webkit-tap-highlight-color: transparent; }
.gcseq-root :focus-visible { outline: 3px solid #2563eb; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .gcseq-in { transition-duration: .01ms !important; } }
.gcseq-title { margin: 0 0 32px; font: 700 clamp(28px, 4.4cqi, 48px)/1.05 ui-sans-serif, system-ui, sans-serif; letter-spacing: -.04em; }
.gcseq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; margin: 0; padding: 0; list-style: none; }
.gcseq-card { position: relative; display: block; width: 100%; aspect-ratio: 4 / 5; padding: 0; border: 0; background: none; color: inherit; text-align: left; cursor: pointer; perspective: 900px; }
.gcseq-in { position: absolute; inset: 0; transform-style: preserve-3d; transition: transform .7s cubic-bezier(.16, 1, .3, 1); }
.gcseq-card:hover .gcseq-in, .gcseq-card:focus-visible .gcseq-in, .gcseq-card[aria-pressed="true"] .gcseq-in { transform: rotateY(180deg); }
.gcseq-face { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end; padding: 18px; border-radius: 20px; backface-visibility: hidden; -webkit-backface-visibility: hidden; overflow: hidden; }
.gcseq-front { background: hsl(var(--h) 80% 90%); }
.gcseq-front .gcseq-ini { position: absolute; top: 14px; left: 18px; font: 900 clamp(64px, 11cqi, 120px)/.9 "Arial Black", ui-sans-serif, sans-serif; letter-spacing: -.06em; color: hsl(var(--h) 70% 48%); }
.gcseq-back { background: hsl(var(--h) 60% 22%); color: #fff; transform: rotateY(180deg); justify-content: center; }
.gcseq-name { font-weight: 700; font-size: 17px; }
.gcseq-role { color: #52525b; font-size: 14px; }
.gcseq-back .gcseq-role { color: hsl(var(--h) 80% 82%); margin-bottom: 10px; }
.gcseq-back p { margin: 0; font: 400 17px/1.35 "Newsreader", Georgia, ui-serif, serif; }`;

const DEFAULT: SeccionEquipoPersona[] = [
  { name: "Persona Uno", role: "Dirección técnica", bio: "Prefiere un sistema aburrido y confiable a uno vistoso que falla.", hue: 220 },
  { name: "Persona Dos", role: "Diseño de producto", bio: "Dibuja primero en papel y solo después en pantalla.", hue: 14 },
  { name: "Persona Tres", role: "Desarrollo", bio: "Cree que el mejor código es el que no hubo que escribir.", hue: 150 },
  { name: "Persona Cuatro", role: "Calidad y pruebas", bio: "Encuentra el error que tú jurabas que no existía.", hue: 280 },
];

export function SeccionEquipo({ title = "Un equipo pequeño y senior", people = DEFAULT, className = "", style }: SeccionEquipoProps) {
  const [pressed, setPressed] = useState<number | null>(null);
  return (
    <section className={`gcseq-root ${className}`} style={style}>
      <style>{CSS}</style>
      <h2 className="gcseq-title">{title}</h2>
      <ul className="gcseq-grid">
        {people.map((person, i) => (
          <li key={person.name}>
            <button className="gcseq-card" type="button" aria-pressed={pressed === i} aria-label={`${person.name}, ${person.role}. ${person.bio}`} style={{ "--h": person.hue } as CSSProperties} onClick={() => setPressed(pressed === i ? null : i)} onBlur={() => setPressed((value) => (value === i ? null : value))}>
              <span className="gcseq-in" aria-hidden="true">
                <span className="gcseq-face gcseq-front">
                  <span className="gcseq-ini">{person.name.split(" ").map((part) => part[0]).join("").slice(0, 2)}</span>
                  <span className="gcseq-name">{person.name}</span>
                  <span className="gcseq-role">{person.role}</span>
                </span>
                <span className="gcseq-face gcseq-back">
                  <span className="gcseq-role">{person.role}</span>
                  <p>“{person.bio}”</p>
                </span>
              </span>
            </button>
          </li>
        ))}
      </ul>
    </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-equipo

Ejemplo de uso de Equipo con tarjetas que se voltean

Después de copiar seccion-equipo.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { SeccionEquipo } from "./seccion-equipo";

export default function Ejemplo() {
  return <SeccionEquipo title="Ejemplo" />;
}

Propiedades de SeccionEquipo

SeccionEquipo acepta 4 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
title?stringOpcional.
people?SeccionEquipoPersona[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 70 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Equipo con tarjetas que se voltean 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)