Ir al contenido
Evaluar mi proyecto
Componentes · FondosDisponible

Fondo de eclipse orbital

Eclipse geométrico con movimiento orbital lento, contenido superpuesto y pausa manual; detiene el trabajo fuera de pantalla.

Entre luz y sombra.

Una órbita lenta. Un instante distinto.

Código completo

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

fondo-eclipse.tsx103 líneas
Descargar
"use client";

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

/** Fondo de eclipse orbital. React + TypeScript, sin dependencias. Personaliza con variables --gcze-*. */
export type FondoEclipseProps = {
  children?: ReactNode;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcze-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcze-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcze-root *, .gcze-root *::before, .gcze-root *::after { box-sizing: border-box; }
.gcze-root button, .gcze-root input, .gcze-root textarea, .gcze-root select { font: inherit; }
.gcze-root button, .gcze-root a { -webkit-tap-highlight-color: transparent; }
.gcze-root :focus-visible { outline: 2px solid var(--gcze-accent, #25724b); outline-offset: 4px; }
.gcze-root ::selection { background: var(--gcze-accent, #25724b); color: #fff; }
.gcze-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcze-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcze-root *, .gcze-root *::before, .gcze-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcze-root { position:relative; isolation:isolate; width:100%; min-height:400px; background:var(--gcze-bg,#171b24); color:var(--gcze-fg,#eef0f5); overflow:hidden; display:grid; place-items:center; padding:40px 24px; }
.gcze-art { position:absolute; inset:0; z-index:-1; display:grid; place-items:center; }
.gcze-disc { width:250px; height:250px; border-radius:50%; background:linear-gradient(145deg,#7e94bd,#3d517a 50%,#1a2135); transform:rotate(-15deg); }
.gcze-moon { position:absolute; width:224px; height:224px; border-radius:50%; background:#171b24; transform:translate(25px,-10px); animation:gcze-orbit 9s ease-in-out infinite alternate; animation-play-state:paused; }
.gcze-root[data-active="true"] .gcze-moon { animation-play-state:running; }
.gcze-art svg { position:absolute; inset:0; height:100%; width:100%; fill:none; stroke:#8f9caf; stroke-width:.6; opacity:.45; transform:rotate(-20deg); }
.gcze-content { text-align:center; }
.gcze-content h3 { font-size:clamp(30px,5vw,48px); line-height:1.1; font-weight:600; letter-spacing:-.03em; margin:0 0 16px; }
.gcze-content p { color:#d0d6e2; font-size:14px; margin:0; }
.gcze-root>button { position:absolute; bottom:16px; right:20px; min-height:44px; padding:10px 12px; border:1px solid #667082; border-radius:5px; background:#171b24; color:#d6deed; font-size:11px; cursor:pointer; }
@keyframes gcze-orbit { from { transform:translate(-45px,-18px); } to { transform:translate(90px,28px); } }`;

function useMotionActive() {
  const ref = useRef<HTMLDivElement>(null);
  const [active, setActive] = useState(false);
  useEffect(() => {
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    let visible = false;
    const sync = () => setActive(visible && !document.hidden && !media.matches);
    const observer = new IntersectionObserver(([entry]) => {
      visible = entry.isIntersecting;
      sync();
    });
    if (ref.current) observer.observe(ref.current);
    media.addEventListener("change", sync);
    document.addEventListener("visibilitychange", sync);
    return () => {
      observer.disconnect();
      media.removeEventListener("change", sync);
      document.removeEventListener("visibilitychange", sync);
    };
  }, []);
  return { ref, active };
}

export function FondoEclipse({
  children,
  className = "",
  style,
}: FondoEclipseProps) {
  const { ref, active } = useMotionActive();
  const [paused, setPaused] = useState(false);
  return (
    <div
      ref={ref}
      className={`gcze-root ${className}`}
      style={style}
      data-active={active && !paused}
    >
      <style>{CSS}</style>
      <div className="gcze-art" aria-hidden="true">
        <i className="gcze-disc" />
        <i className="gcze-moon" />
        <svg viewBox="0 0 600 400">
          <ellipse cx="300" cy="200" rx="220" ry="92" />
        </svg>
      </div>
      <div className="gcze-content">
        {children ?? (
          <>
            <h3>Entre luz y sombra.</h3>
            <p>Una órbita lenta. Un instante distinto.</p>
          </>
        )}
      </div>
      <button
        type="button"
        aria-pressed={paused}
        onClick={() => setPaused(!paused)}
      >
        {paused ? "Reanudar movimiento" : "Pausar movimiento"}
      </button>
    </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/fondo-eclipse

Ejemplo de uso de Fondo de eclipse orbital

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

ejemplo.tsx5 líneas
import { FondoEclipse } from "./fondo-eclipse";

export default function Ejemplo() {
  return <FondoEclipse>Contenido</FondoEclipse>;
}

Propiedades de FondoEclipse

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

PropiedadTipoDescripción
children?ReactNodeOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Fondo de eclipse orbital 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
--gcze-fg#202421
--gcze-accent#25724b
--gcze-bg#171b24

Accesibilidad y rendimiento

El archivo tiene 103 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Fondo de eclipse orbital 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
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)