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 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.
npx shadcn@latest add https://www.gassierconsulting.com/r/hero-cinematicEjemplo de uso de Hero cinematográfico
Después de copiar hero-cinematic.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
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.
| Propiedad | Tipo | Descripción |
|---|---|---|
title? | string[] | Opcional. |
description? | string | Opcional. |
primary? | Cta | Opcional. |
secondary? | Cta | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
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".
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