Ir al contenido
Evaluar mi proyecto
Componentes · HerosDisponible

Hero de obturador luminoso

Hero con seis hojas luminosas que abren su máscara según el cursor; composición estática completa para móvil y movimiento reducido.

El siguiente capítulo empieza contigo.

Dale forma a una idea que merezca ser vista.

Abrir posibilidades

Mueve el cursor para abrir la luz · composición de muestra

Código completo

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

hero-obturador.tsx100 líneas
Descargar
"use client";

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

/** Hero de obturador luminoso. React + TypeScript, sin dependencias. Personaliza con variables --gcys-*. */
export type HeroObturadorProps = {
  title?: string;
  description?: string;
  href?: string;
  label?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcys-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcys-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcys-root *, .gcys-root *::before, .gcys-root *::after { box-sizing: border-box; }
.gcys-root button, .gcys-root input, .gcys-root textarea, .gcys-root select { font: inherit; }
.gcys-root button, .gcys-root a { -webkit-tap-highlight-color: transparent; }
.gcys-root :focus-visible { outline: 2px solid var(--gcys-accent, #25724b); outline-offset: 4px; }
.gcys-root ::selection { background: var(--gcys-accent, #25724b); color: #fff; }
.gcys-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcys-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcys-root *, .gcys-root *::before, .gcys-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcys-root { position:relative; isolation:isolate; width:100%; min-height:590px; overflow:hidden; background:var(--gcys-bg,#151312); color:var(--gcys-fg,#faf0e3); padding:64px clamp(24px,6vw,76px) 28px; }
.gcys-light { position:absolute; inset:0 0 0 47%; z-index:-1; display:flex; transform:skewX(-15deg); opacity:.8; }
.gcys-light i { flex:1; background:linear-gradient(180deg,#c87036,#592d19 65%,#151312); border-left:1px solid #ebaa6e40; clip-path:inset(0 calc(100% - var(--gcys-open,48%)) 0 0); transition:clip-path .65s cubic-bezier(.16,1,.3,1); }
.gcys-light i:nth-child(even) { transform:translateY(-70px); }
.gcys-content { max-width:620px; }
.gcys-content h2 { font-size:clamp(42px,5.5vw,76px); font-weight:600; line-height:1.06; letter-spacing:-.035em; text-wrap:balance; margin:0 0 28px; }
.gcys-content p { font-size:16px; color:#d2bda9; max-width:30ch; margin:0 0 34px; }
.gcys-content a { display:inline-flex; align-items:center; gap:30px; padding:14px 22px; min-height:48px; border:1px solid #b8a087; border-radius:3px; color:#faf0e3; font-size:13px; font-weight:600; text-decoration:none; transition:background .2s; }
.gcys-content a:hover { background:#38251a; }
.gcys-caption { font-size:11px; color:#c3ad97; margin:70px 0 0; max-width:40ch; }
@media(max-width:540px) { .gcys-light { inset:0 0 0 20%; opacity:.4; }
.gcys-content h2 { font-size:44px; } }`;

export function HeroObturador({
  title = "El siguiente capítulo empieza contigo.",
  description = "Dale forma a una idea que merezca ser vista.",
  href = "#empezar",
  label = "Abrir posibilidades",
  className = "",
  style,
}: HeroObturadorProps) {
  const root = useRef<HTMLDivElement>(null);
  function move(e: PointerEvent<HTMLDivElement>) {
    if (
      e.pointerType !== "mouse" ||
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
      return;
    const box = e.currentTarget.getBoundingClientRect();
    root.current?.style.setProperty(
      "--gcys-open",
      `${20 + ((e.clientX - box.left) / box.width) * 65}%`,
    );
  }
  return (
    <div
      ref={root}
      className={`gcys-root ${className}`}
      style={style}
      onPointerMove={move}
      onPointerLeave={() => root.current?.style.removeProperty("--gcys-open")}
    >
      <style>{CSS}</style>
      <div className="gcys-light" aria-hidden="true">
        <i />
        <i />
        <i />
        <i />
        <i />
        <i />
      </div>
      <section className="gcys-content">
        <h2>{title}</h2>
        <p>{description}</p>
        <a href={href}>
          {label}
          <svg
            width="20"
            height="20"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="1.5"
            aria-hidden="true"
          >
            <path d="M5 12h14m-6-6 6 6-6 6" />
          </svg>
        </a>
      </section>
      <p className="gcys-caption">
        Mueve el cursor para abrir la luz · composición de muestra
      </p>
    </div>
  );
}

Ú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-obturador

Ejemplo de uso de Hero de obturador luminoso

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

ejemplo.tsx5 líneas
import { HeroObturador } from "./hero-obturador";

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

Propiedades de HeroObturador

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

PropiedadTipoDescripción
title?stringOpcional.
description?stringOpcional.
href?stringOpcional.
label?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Hero de obturador luminoso 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
--gcys-fg#202421
--gcys-accent#25724b
--gcys-bg#151312
--gcys-open48%

Accesibilidad y rendimiento

El archivo tiene 100 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Hero de obturador luminoso 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).
  • 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)