Ir al contenido
Evaluar mi proyecto
Componentes · HerosDisponible

Hero con pila isométrica 3D

Cuatro paneles de interfaz flotan en profundidad, se separan al apuntarlos y se inclinan con el puntero. Solo CSS 3D.

Cada capa de tu negocio, a la vista

Datos, procesos, integraciones y reportes apilados en un solo sistema. Apunta la pila para separarlas.

Código completo

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

hero-pila-3d.tsx106 líneas
Descargar
"use client";

import { useRef, type CSSProperties, type PointerEvent } from "react";

/** Hero con pila isométrica de paneles: cuatro capas de interfaz flotan en profundidad, se separan al apuntarlas y se inclinan con el puntero. Solo CSS 3D. React + TypeScript, sin dependencias. */
export type HeroPila3dProps = {
  title?: string;
  description?: string;
  primary?: string;
  secondary?: string;
  href?: string;
  layers?: string[];
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gchpl-root { box-sizing: border-box; position: relative; container-type: inline-size; width: 100%; min-height: 640px; overflow: hidden; color: #e9ecf6; background: radial-gradient(90% 80% at 80% 40%, #1a2150, #090b1b 70%); font: 500 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
.gchpl-root *, .gchpl-root *::before, .gchpl-root *::after { box-sizing: border-box; }
.gchpl-root a { -webkit-tap-highlight-color: transparent; }
.gchpl-root :focus-visible { outline: 2px solid #8fa8ff; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .gchpl-root *, .gchpl-root *::before, .gchpl-root *::after { transition-duration: .01ms !important; animation: none !important; } }
.gchpl-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); align-items: center; gap: 24px; min-height: 640px; padding: 56px clamp(24px, 6cqi, 80px); }
.gchpl-title { margin: 0 0 20px; max-width: 13ch; font: 700 clamp(40px, 6.4cqi, 80px)/1 ui-sans-serif, system-ui, -apple-system, sans-serif; letter-spacing: -.04em; text-wrap: balance; }
.gchpl-desc { margin: 0 0 30px; max-width: 42ch; color: #a9b2d6; font-size: clamp(15px, 1.7cqi, 19px); text-wrap: pretty; }
.gchpl-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.gchpl-btn { display: inline-flex; align-items: center; min-height: 50px; padding: 0 26px; border-radius: 10px; font-weight: 600; text-decoration: none; color: #e9ecf6; border: 1px solid #3a4478; transition: background .2s, transform .25s cubic-bezier(.16, 1, .3, 1); }
.gchpl-btn:hover { background: rgba(120, 140, 255, .14); transform: translateY(-2px); }
.gchpl-btn[data-p="true"] { background: #6f86ff; border-color: #6f86ff; color: #070a22; }
.gchpl-btn[data-p="true"]:hover { background: #8da0ff; }
.gchpl-scene { position: relative; height: 480px; perspective: 1400px; }
.gchpl-stack { --rx: 58deg; --rz: -38deg; position: absolute; left: 50%; top: 50%; width: 330px; height: 330px; margin: -165px 0 0 -165px; transform-style: preserve-3d; transform: rotateX(var(--rx)) rotateZ(var(--rz)); transition: transform .6s cubic-bezier(.16, 1, .3, 1); }
.gchpl-layer { position: absolute; inset: 0; border-radius: 22px; transform: translateZ(calc(var(--i) * var(--gap, 46px))); transition: transform .7s cubic-bezier(.16, 1, .3, 1); background: linear-gradient(145deg, color-mix(in srgb, var(--c) 28%, #0d1230), color-mix(in srgb, var(--c) 10%, #0a0d24)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 60%, transparent), inset 0 1px 0 rgba(255, 255, 255, .22), 0 40px 40px -20px rgba(0, 0, 0, .6); padding: 22px; }
.gchpl-scene:hover .gchpl-stack, .gchpl-scene:focus-within .gchpl-stack { --gap: 74px; }
.gchpl-scene:hover .gchpl-layer, .gchpl-scene:focus-within .gchpl-layer { transform: translateZ(calc(var(--i) * 74px)); }
.gchpl-tag { display: inline-block; padding: 4px 10px; border-radius: 6px; background: color-mix(in srgb, var(--c) 40%, transparent); font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #fff; }
.gchpl-bars { position: absolute; left: 22px; right: 22px; bottom: 22px; display: flex; align-items: flex-end; gap: 8px; height: 90px; }
.gchpl-bars i { flex: 1; border-radius: 4px 4px 0 0; background: color-mix(in srgb, var(--c) 70%, #fff); opacity: .85; height: var(--h); }
.gchpl-rows { position: absolute; left: 22px; right: 22px; top: 62px; display: grid; gap: 10px; }
.gchpl-rows i { display: block; height: 10px; border-radius: 6px; background: color-mix(in srgb, var(--c) 36%, transparent); }
.gchpl-rows i:nth-child(2) { width: 72%; } .gchpl-rows i:nth-child(3) { width: 48%; }
@container (max-width: 800px) { .gchpl-grid { grid-template-columns: minmax(0, 1fr); } .gchpl-scene { height: 360px; } .gchpl-stack { width: 240px; height: 240px; margin: -120px 0 0 -120px; } }`;

const COLORS = ["#6f86ff", "#5de0c0", "#ffb86b", "#ff7ab8"];

export function HeroPila3d({ title = "Cada capa de tu negocio, a la vista", description = "Datos, procesos, integraciones y reportes apilados en un solo sistema. Apunta la pila para separarlas.", primary = "Ver cómo funciona", secondary = "Hablar con el equipo", href = "#", layers = ["Datos", "Procesos", "Integraciones", "Reportes"], className = "", style }: HeroPila3dProps) {
  const scene = useRef<HTMLDivElement>(null);

  function move(event: PointerEvent<HTMLDivElement>) {
    if (event.pointerType === "touch") return;
    const rect = event.currentTarget.getBoundingClientRect();
    const x = (event.clientX - rect.left) / rect.width - 0.5;
    const y = (event.clientY - rect.top) / rect.height - 0.5;
    const stack = scene.current?.firstElementChild as HTMLElement | null;
    stack?.style.setProperty("--rx", `${58 - y * 14}deg`);
    stack?.style.setProperty("--rz", `${-38 + x * 22}deg`);
  }
  function leave() {
    const stack = scene.current?.firstElementChild as HTMLElement | null;
    stack?.style.setProperty("--rx", "58deg");
    stack?.style.setProperty("--rz", "-38deg");
  }

  return (
    <section className={`gchpl-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gchpl-grid">
        <div>
          <h1 className="gchpl-title">{title}</h1>
          <p className="gchpl-desc">{description}</p>
          <div className="gchpl-actions">
            <a className="gchpl-btn" data-p="true" href={href}>
              {primary}
            </a>
            <a className="gchpl-btn" href={href}>
              {secondary}
            </a>
          </div>
        </div>
        <div ref={scene} className="gchpl-scene" onPointerMove={move} onPointerLeave={leave} aria-hidden="true">
          <div className="gchpl-stack">
            {layers.map((name, index) => (
              <div key={name} className="gchpl-layer" style={{ "--i": index, "--c": COLORS[index % COLORS.length] } as CSSProperties}>
                <span className="gchpl-tag">{name}</span>
                {index % 2 === 0 ? (
                  <span className="gchpl-bars">
                    {[40, 62, 48, 80, 58, 90].map((height, i) => (
                      <i key={i} style={{ "--h": `${height}%` } as CSSProperties} />
                    ))}
                  </span>
                ) : (
                  <span className="gchpl-rows">
                    <i />
                    <i />
                    <i />
                  </span>
                )}
              </div>
            ))}
          </div>
        </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/hero-pila-3d

Ejemplo de uso de Hero con pila isométrica 3D

Después de copiar hero-pila-3d.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { HeroPila3d } from "./hero-pila-3d";

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

Propiedades de HeroPila3d

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

PropiedadTipoDescripción
title?stringOpcional.
description?stringOpcional.
primary?stringOpcional.
secondary?stringOpcional.
href?stringOpcional.
layers?string[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Hero con pila isométrica 3D 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
--gap46px

Accesibilidad y rendimiento

El archivo tiene 106 líneas (7 KB) y no necesita @keyframes. Esto es lo que hace Hero con pila isométrica 3D 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).
  • Calcula tonos con color-mix(), así que un solo color base alcanza.
  • 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)