Ir al contenido
Evaluar mi proyecto
Componentes · HerosDisponible

Hero cinematográfico

Hero a pantalla de cine con franjas, una fuente de luz cálida y título que sube línea por línea.

Software que seadapta a tuoperación.

Sistemas hechos a la medida de cómo trabaja tu equipo, no al revés.

Código completo

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

hero-cinematic.tsx114 líneas
Descargar
/**
 * Hero a pantalla de cine: dos franjas negras que se cierran como un encuadre 2.39:1, un
 * fondo oscuro con una sola fuente de luz cálida y grano fijo, y un título que sube línea
 * por línea desde una máscara. Todo se dispara una vez al entrar en pantalla. El fondo
 * avanza lentamente (zoom de 24 s). Con movimiento reducido el hero aparece ya terminado.
 *
 *   <HeroCinematic
 *     title={["Software que se", "adapta a tu", "operación."]}
 *     description="Sistemas hechos a la medida de cómo trabaja tu equipo."
 *     primary={{ label: "Evaluar mi proyecto", href: "/cotizar" }}
 *   />
 */

"use client";

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

type Cta = { label: string; href: string };

export type HeroCinematicProps = {
  title?: string[];
  description?: string;
  primary?: Cta;
  secondary?: Cta;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gchc-root { --gchc-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .55 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"); position: relative; isolation: isolate; overflow: hidden; box-sizing: border-box; min-height: 720px; display: grid; align-content: center; padding: clamp(96px, 14vw, 180px) clamp(20px, 6vw, 96px); background: #050506; color: #f1f0ee; font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.gchc-scene { position: absolute; inset: -4%; z-index: -3; background: radial-gradient(55% 48% at 80% 86%, rgba(255, 168, 92, .26), transparent 70%), radial-gradient(70% 60% at 12% 0%, rgba(96, 128, 190, .16), transparent 66%), linear-gradient(180deg, #0a0b0e 0%, #050506 100%); animation: gchc-push 24s ease-in-out infinite alternate; will-change: transform; }
.gchc-vignette { position: absolute; inset: 0; z-index: -2; background: radial-gradient(120% 90% at 50% 50%, transparent 40%, rgba(0, 0, 0, .75) 100%); }
.gchc-grain { position: absolute; inset: 0; z-index: -1; background: var(--gchc-grain); background-size: 180px 180px; opacity: .1; mix-blend-mode: overlay; pointer-events: none; }
.gchc-bar { position: absolute; left: 0; right: 0; z-index: 1; height: 26%; background: #000; transform: scaleY(.27); transition: transform 1.4s cubic-bezier(.16, 1, .3, 1); }
.gchc-bar[data-edge="top"] { top: 0; transform-origin: top; }
.gchc-bar[data-edge="bottom"] { bottom: 0; transform-origin: bottom; }
.gchc-root:not([data-revealed="true"]) .gchc-bar { transform: none; }
.gchc-copy { position: relative; max-width: 1100px; }
.gchc-title { margin: 0; font-size: clamp(44px, 8vw, 120px); font-weight: 300; line-height: 1; letter-spacing: -.045em; text-wrap: balance; }
.gchc-line { display: block; overflow: hidden; padding-bottom: .08em; margin-bottom: -.08em; }
.gchc-line > span { display: block; transform: translateY(105%); transition: transform 1.1s cubic-bezier(.16, 1, .3, 1); }
.gchc-line:last-child > span { font-weight: 600; }
.gchc-root[data-revealed="true"] .gchc-line > span { transform: none; }
.gchc-line:nth-child(2) > span { transition-delay: .12s; }
.gchc-line:nth-child(3) > span { transition-delay: .24s; }
.gchc-desc { max-width: 46ch; margin: clamp(24px, 3vw, 40px) 0 0; color: #b6b4af; font-size: clamp(16px, 1.4vw, 19px); line-height: 1.6; }
.gchc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 28px; margin-top: 36px; }
.gchc-desc, .gchc-actions { opacity: 0; transform: translateY(14px); transition: opacity .9s ease, transform .9s cubic-bezier(.16, 1, .3, 1); }
.gchc-desc { transition-delay: .55s; }
.gchc-actions { transition-delay: .7s; }
.gchc-root[data-revealed="true"] .gchc-desc, .gchc-root[data-revealed="true"] .gchc-actions { opacity: 1; transform: none; }
.gchc-btn { display: inline-flex; align-items: center; min-height: 50px; padding: 0 26px; border-radius: 4px; background: #f1f0ee; color: #0a0a0b; font-size: 15px; font-weight: 600; text-decoration: none; transition: background-color .2s; }
.gchc-btn:hover { background: #fff; }
.gchc-link { color: #f1f0ee; font-size: 15px; font-weight: 500; text-decoration: underline; text-decoration-color: rgba(241, 240, 238, .35); text-underline-offset: 6px; transition: text-decoration-color .2s; }
.gchc-link:hover { text-decoration-color: currentColor; }
.gchc-btn:focus-visible, .gchc-link:focus-visible { outline: 2px solid #84b3ff; outline-offset: 4px; }
@keyframes gchc-push { to { transform: scale(1.08) translate(-1%, -1%); } }
@media (prefers-reduced-motion: reduce) { .gchc-scene { animation: none; } .gchc-bar, .gchc-line > span, .gchc-desc, .gchc-actions { transition: none; } .gchc-root:not([data-revealed="true"]) .gchc-bar { transform: scaleY(.27); } .gchc-line > span { transform: none; } .gchc-desc, .gchc-actions { opacity: 1; transform: none; } }
`;

export function HeroCinematic({
  title = ["Software que se", "adapta a tu", "operación."],
  description = "Sistemas hechos a la medida de cómo trabaja tu equipo, no al revés.",
  primary = { label: "Evaluar mi proyecto", href: "#cotizar" },
  secondary = { label: "Ver servicios", href: "#servicios" },
  className,
  style,
}: HeroCinematicProps) {
  const rootRef = useRef<HTMLElement>(null);
  const [revealed, setRevealed] = useState(false);

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    if (typeof IntersectionObserver === "undefined") {
      const fallback = setTimeout(() => setRevealed(true), 0);
      return () => clearTimeout(fallback);
    }
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries.some((e) => e.isIntersecting)) { setRevealed(true); observer.disconnect(); }
      },
      { threshold: 0.25 },
    );
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  return (
    <>
      <style>{CSS}</style>
      <section ref={rootRef} className={["gchc-root", className].filter(Boolean).join(" ")} style={style} data-revealed={revealed}>
        <span className="gchc-scene" aria-hidden="true" />
        <span className="gchc-vignette" aria-hidden="true" />
        <span className="gchc-grain" aria-hidden="true" />
        <span className="gchc-bar" data-edge="top" aria-hidden="true" />
        <span className="gchc-bar" data-edge="bottom" aria-hidden="true" />
        <div className="gchc-copy">
          <h1 className="gchc-title">
            {title.map((line) => (
              <span key={line} className="gchc-line"><span>{line}</span></span>
            ))}
          </h1>
          <p className="gchc-desc">{description}</p>
          <div className="gchc-actions">
            <a className="gchc-btn" href={primary.href}>{primary.label}</a>
            <a className="gchc-link" href={secondary.href}>{secondary.label}</a>
          </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-cinematic

Ejemplo de uso de Hero cinematográfico

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

ejemplo.tsx5 líneas
import { HeroCinematic } from "./hero-cinematic";

export default function Ejemplo() {
  return <HeroCinematic description="Ejemplo" />;
}

Propiedades de HeroCinematic

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

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

Accesibilidad y rendimiento

El archivo tiene 114 líneas (7 KB) y define 1 animación con @keyframes. Esto es lo que hace Hero cinematográfico 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-*.
  • Se pausa cuando sale de la pantalla para no gastar batería ni CPU.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18

Acerca del producto

Dos franjas negras se cierran como un encuadre 2.39:1, el fondo avanza despacio y el título aparece desde una máscara. Todo se dispara una vez al entrar en pantalla.

Incluye

Un solo archivo React + TypeScript con estilos dentro. Sin Tailwind ni dependencias externas.

  • Franjas de encuadre animadas
  • Título con revelado por máscara
  • Grano fijo y viñeta
  • Zoom lento de fondo
  • Aparece terminado con movimiento reducido