Communitygithub.com

anhlamtruong/living-ui-effects

Agrega efectos vivos e interactivos que mantienen una página web en movimiento después de cargar. Incluye un shader WebGL aurora/luz líquida detrás del héroe que se curva alrededor del puntero, tarjetas 3D holográficas con inclinación, deslumbramiento y borde iridiscente, un foco de cuadrícula que sigue el cursor, encabezados con fallo RGB, y texto revuelto al pasar el ratón sobre enlaces y botones. Proporciona código React/Next.js listo para usar (un bucle rAF compartido, sin cambio de diseño, accesible) más las reglas que lo mantienen fluido. Úsalo cada vez que alguien quiera que una página se sienta más viva, psicodélica, premium o 'guau'. También úsalo para un fondo shader o degradado animado, tarjetas 3D/inclinación/holograma, efectos de seguimiento del cursor o foco, texto con fallo o decodificación/revuelto, o efectos interactivos al pasar el ratón, incluso si no nombran una técnica.

¿Qué es living-ui-effects?

El skill `living-ui-effects` es una colección de cuatro efectos visuales frontend de alto rendimiento diseñados para hacer que las páginas web se sientan más vivas y receptivas. Está dirigido a desarrolladores que usan React, Next.js o cualquier framework que admita componentes React. El skill es útil para agregar una sensación premium e interactiva sin sacrificar rendimiento ni accesibilidad. Proporciona un shader WebGL aurora que se curva alrededor del puntero, tarjetas 3D holográficas con inclinación y deslumbramiento, un foco de cuadrícula que sigue el cursor, y efectos de texto con fallo y revuelto. El código está diseñado para ser 'drop-in', requiriendo solo copiar una carpeta de activos (`assets/fx/`) y montar dos componentes clientes en una página. La característica clave son las ocho reglas de rendimiento que garantizan que los efectos no causen tirones, cambios de diseño, robos de clics ni problemas de accesibilidad. Estas incluyen usar un único bucle `requestAnimationFrame` compartido que se duerme cuando no es necesario, animar solo `transform`, `opacity` y `clip-path` para cero cambios de diseño, y mantener el texto real accesible para lectores de pantalla. Los efectos se adaptan al dispositivo: en pantallas táctiles, la inclinación de tarjetas y el foco se reemplazan por alternativas apropiadas. El skill también incluye un sistema de tematización completo a través de propiedades personalizadas CSS y un sistema de compuerta de construcción para sincronizar los efectos con el contenido que se está revelando. La implementación del shader tiene un presupuesto de GPU: renderiza a resolución reducida, solo cuando es visible, y reduce la calidad si la velocidad de fotogramas cae por debajo de ~38 FPS. En general, es un conjunto de herramientas premium, listo para producción para sitios que buscan una experiencia de usuario destacada.

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/anhlamtruong/living-ui-skills/tree/HEAD/skills/living-ui-effects

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Efectos vivos

Cuatro efectos que mantienen una página en movimiento una vez que su contenido está en pantalla:

  • Héroe aurora: un campo WebGL de luz suave detrás del héroe que se curva alrededor del puntero.
  • Tarjetas holograma: una tarjeta se inclina hacia el cursor, se eleva y captura un deslumbramiento y un borde iridiscente.
  • Foco de cuadrícula: la cuadrícula de fondo de la página se ilumina en un círculo suave alrededor del cursor.
  • Texto con fallo y revuelto: los encabezados se dividen en RGB cuando aparecen, y las etiquetas de enlace se decodifican al pasar el ratón.

El estándar es "vivo, nunca molesto". Las personas deben sentir que la página reacciona a ellas, pero nada puede tironear, cambiar el diseño, robar clics u ocultar texto de un lector de pantalla.

Ejemplo en vivo: https://www.lamanhtruong.com (pasa el ratón sobre las tarjetas de proyecto, muévete sobre el héroe, pasa el ratón sobre la navegación).

Qué hay en assets/fx/

ArchivoRol
ticker.tsUn bucle requestAnimationFrame compartido que se duerme cuando nadie pide fotogramas
built.tsCompuerta de preparación (isBuilt, buildActive), frozen(), PRNG con semilla, consulta de medios para puntero
aurora.tsEl shader WebGL, su escalador de calidad y ciclo de vida (createAurora)
hero-shader.tsx<HeroShader />, el envoltorio React para la aurora
holo.tsInclinación + deslumbramiento + brillo en tarjetas, física de muelle, superposición inyectada
spotlight.tsFoco de cuadrícula que sigue al cursor (solo transform)
glitch.ts, glitch-copies.tsxPlanificador de fallo, <GlitchCopies /> y <GlitchHeading />
scramble.ts, scramble-text.tsxEjecutor de revuelto y el enganche de marcado <ScrambleText />
fx-root.tsx<FxRoot />: monta holo, foco, fallo y revuelto una vez
fx.cssTodos los estilos, tematizados con propiedades personalizadas --fx-*

El código solo necesita React; ninguna biblioteca de animación. Las plantillas verifican tipos bajo TypeScript estricto contra los tipos de React 18 y 19.

Instalar

  1. Copia assets/fx/ en la aplicación, p.ej. src/components/fx/. Mantén los archivos juntos, ya que se importan entre sí por ruta relativa.
  2. Móntalo. La página en sí puede seguir siendo un componente servidor estático; solo FxRoot y HeroShader son componentes cliente.
import { FxRoot } from "@/components/fx/fx-root";
import { HeroShader } from "@/components/fx/hero-shader";
import { GlitchHeading } from "@/components/fx/glitch-copies";
import { ScrambleText } from "@/components/fx/scramble-text";

export default function Page() {
  return (
    <div className="fx-grid">                        {/* cuadrícula base fija; omite si tienes la tuya propia */}
      <main className="relative z-[1]">              {/* el contenido está sobre la cuadrícula + el foco */}
        <section className="relative min-h-[80vh]">  {/* el héroe debe tener position: relative */}
          <HeroShader />                             {/* primer hijo, detrás del contenido del héroe */}
          <h1>…</h1>
        </section>

        <GlitchHeading as="h2" className="text-5xl font-semibold">Trabajo seleccionado</GlitchHeading>

        <article data-holo className="rounded-2xl border p-6">
          <a href="/work/x" className="inline-flex items-center gap-2">
            <ScrambleText>Leer el caso de estudio</ScrambleText> <ArrowIcon />
          </a>
        </article>
      </main>
      <FxRoot />                                     {/* una vez por página, dentro del envoltorio de cuadrícula */}
    </div>
  );
}
  1. Tematízalo: edita las variables --fx-* en la parte superior de fx.css, y pasa palette / intensity a <HeroShader /> (consulta "Tematización" más abajo).
  2. Verifica con las comprobaciones de Playwright en references/verification.md antes de darlo por terminado.

Apaga piezas con props: <FxRoot holo={false} spotlight={false} />. Apunta la inclinación a tarjetas existentes sin tocar su marcado: <FxRoot holo={{ selector: ".card", within: "main" }} />.

Reglas que lo mantienen fluido

Estas reglas son lo que separa "premium" de "con tirones". Cada una existe porque romperla causó un problema visible.

  1. Espera hasta que el contenido esté listo. Nunca inclines, hagas fallo o revuelvas algo que otro script todavía está revelando o midiendo. Una transformación en un elemento a medio revelar corrompe las mediciones de quien lo está animando, y el resultado parece un tartamudeo. built.ts implementa un contrato: mientras html[data-build="on"] esté establecido, un elemento solo cuenta una vez que su [data-build-item] y [data-build-section] llevan [data-built]. Sin un motor de construcción en la página, todo cuenta como construido de inmediato. Cualquier animación de entrada puede optar por establecer esos atributos. Detalles: references/gating-and-performance.md.

  2. Usa un bucle de fotogramas, y déjalo dormir. Cada efecto se suscribe a ticker.ts en lugar de llamar a requestAnimationFrame por su cuenta. Un suscriptor devuelve false cuando se ha estabilizado, y cuando nadie necesita fotogramas, el bucle se detiene. Una página inactiva entonces no cuesta nada, y los efectos no pueden pelear entre sí por los fotogramas. El fallo no usa fotogramas en absoluto: son keyframes de CSS activados por un atributo.

  3. Anima solo transform, opacity y clip-path. Estos se quedan en el compositor. Nada aquí cambia de tamaño, posición en el flujo o métricas de fuente, por lo que el cambio de diseño es cero por construcción. El revuelto mantiene el texto real en su lugar (transparente) y dibuja los glifos en una superposición, por lo que incluso una etiqueta que cambia nunca cambia de ancho.

  4. Adecúa el efecto a la entrada. La inclinación y el foco que sigue solo existen en (hover: hover) and (pointer: fine). En táctil, inclinarse bajo un dedo se lee como un error, por lo que el táctil obtiene un breve desvanecimiento en el punto de toque y una lente shader autodesplazante en su lugar.

  5. Deja el DOM de React en reposo. El <span> de superposición de holo y la transform en línea se añaden cuando una tarjeta se activa y se eliminan cuando ha vuelto a su estado de reposo. En reposo, el DOM es exactamente lo que React renderizó, por lo que los rerenderizados, la hidratación y los estilos :hover de la tarjeta siguen funcionando. Las capas de fallo y los span de revuelto están escritos en el marcado (<GlitchCopies>, <ScrambleText>) en lugar de insertarse alrededor de nodos propiedad de React en tiempo de ejecución; reestructurar el árbol de React desde fuera rompe la reconciliación.

  6. Mantén el texto real. Las copias de fallo son solo cliente y aria-hidden, por lo que el HTML estático, la búsqueda en página y los lectores de pantalla ven cada encabezado una vez. La superposición de revuelto es aria-hidden y la etiqueta real sigue siendo el nombre accesible durante todo el proceso.

  7. Limpia y sobrevive al StrictMode. Cada setup* devuelve un eliminador que elimina listeners, observadores, superposiciones y estilos en línea. El shader libera su búfer y programa, pero deliberadamente no llama a WEBGL_lose_context, porque React StrictMode monta, desmonta y vuelve a montar en el mismo canvas, y un contexto perdido se queda perdido.

  8. Presupuesta la GPU. El shader renderiza a resolución reducida bajo un presupuesto de píxeles, dibuja solo mientras es visible, limita los dibujos a ~80 por segundo en pantallas de alta frecuencia de actualización, y reduce la calidad una vez si la página funciona por debajo de ~38 FPS. Se niega a iniciar en una alternativa de software (failIfMajorPerformanceCaveat) y no renderiza nada en lugar de algo lento.

Los efectos

Shader héroe aurora (aurora.ts, hero-shader.tsx)

El shader es ruido de valor con deformación de dominio (fbm deformado por fbm), dibujado como un amplio resplandor más hebras brillantes y delgadas a lo largo de los contornos deformados. El puntero actúa como una lente: magnifica y arremolina el flujo cercano. Entrar en el héroe, hacer clic o moverse rápido deja caer un anillo de ondulación suave. La salida es alfa premultiplicado y se desvanece en cada borde, por lo que el fondo de la página y cualquier cuadrícula fija se ven a través, y el héroe se mezcla con la siguiente sección en lugar de terminar en una caja.

Ciclo de vida: el canvas aparece gradualmente en 1.8 s una vez que su sección está construida. Renderiza solo mientras la sección está en pantalla (IntersectionObserver con un margen de 80 px) y la pestaña es visible. Si WebGL no está disponible, no renderiza nada.

Perillas:

  • palette: { veil, primary, secondary, accent } como hex. La paleta se compila en el shader, por lo que los cambios necesitan un remontaje.
  • intensity: multiplica la luz. 0.6 es calmado, 1.4 es intenso.
  • Legibilidad del texto: el shader sesga la luz lejos de la esquina inferior izquierda, donde suele estar el texto del héroe. Si tu texto está en otro lugar, cambia la línea bias (consulta la referencia).

Lee references/aurora-shader.md antes de cambiar las matemáticas del shader, la velocidad o el escalador de calidad.

Tarjetas holograma (holo.ts)

Un pointerover delegado encuentra la tarjeta bajo un puntero fino. Al activarse, mide la caja sin transformar de la tarjeta, inyecta un span de superposición aria-hidden, y ejecuta un muelle con amortiguación crítica (subdividido a 120 Hz para que se comporte igual a 60 o 144 fps) hacia una inclinación apuntada al puntero. El borde bajo el cursor viene hacia el espectador. Al salir, vuelve con un ligero rebasamiento y elimina todo una vez que se ha estabilizado.

  • La inclinación escala con el tamaño de la tarjeta, 2.5° a 7.5°: las tarjetas grandes se inclinan menos para que sus bordes lejanos no oscilen. La perspectiva es 1.6 veces el lado más grande. La elevación es de 3 px.
  • La superposición tiene un deslumbramiento radial en el puntero, un lavado de color diagonal tenue y un anillo de gradiente cónico enmascarado al borde (tenue en todas partes, brillante cerca del puntero).
  • En una tarjeta con overflow: hidden la caja del borde de 1 px está recortada, por lo que el anillo se dibuja justo en el interior (.fx-holo--in, detectado automáticamente).
  • La superposición está en z-index: 3 con pointer-events: none, por lo que pinta sobre los controles dentro de la tarjeta (un alternador de video, un enlace) sin bloquear los clics.
  • Una tarjeta con position: static obtiene position: relative solo mientras está activa.
  • Opt-out: data-holo="off" en una tarjeta o cualquier ancestro. Pausa toda inclinación con html[data-fx-freeze] mientras algo lee rectángulos reales (un inspector, una pasada de captura de pantalla). El alternador de inspección living-ui-swarm se detecta automáticamente.

Foco de cuadrícula (spotlight.ts, div.fx-spot)

Una ventana fija de 560 px, enmascarada radialmente, contiene una copia más brillante de la cuadrícula base. Cada fotograma mueve solo dos transformaciones: la ventana sigue al cursor, y la cuadrícula interior se desplaza hacia atrás por floor(x / cell) * cell - x para que sus líneas caigan exactamente sobre el entramado de la cuadrícula base. Ambas cuadrículas deben estar ancladas al origen de la ventana gráfica, y .fx-grid::before lo está. La interpolación rezagada se estabiliza y el bucle se duerme cuando el cursor se detiene. Se oculta al perder el foco de la ventana o cuando el puntero sale de la ventana. El táctil obtiene un desvanecimiento de 360 ms en el punto de toque.

El tamaño de celda de la cuadrícula, el espaciado de líneas finas y el tamaño de la ventana provienen de --fx-grid-cell, --fx-grid-fine y --fx-spot-size (valores en px), por lo que CSS sigue siendo la fuente única de verdad.

Encabezados con fallo (glitch.ts, <GlitchHeading> / <GlitchCopies>)

Marcado: un envoltorio .fx-glitch[data-fx-glitch] que contiene el encabezado real (.fx-glitch__base) y dos copias solo cliente. GlitchHeading escribe todo; usa GlitchCopies directamente cuando el encabezado ya existe y lleva otros atributos.

  • Entrada: 560 ms cuando un encabezado construido está al menos al 50% visible. El mismo encabezado no se reproducirá de nuevo dentro de 2.5 s.
  • Micro-fallo: 260 ms en un encabezado visible aleatorio cada 8–14 s.
  • La aleatoriedad tiene semilla, por lo que el ritmo es el mismo en cada visita y en las pruebas.
  • Solo un encabezado falla a la vez; las entradas se ponen en cola detrás de la otra.
  • Implementado como keyframes CSS puros activados por [data-glitch]: corta con clip-path: inset(), desplaza con translate3d, y divide el color con text-shadow.
  • --fx-gi escala los desplazamientos por ejecución.

Texto revuelto (scramble.ts, <ScrambleText>)

Envuelve solo la etiqueta, nunca el icono: <a><Icon/> <ScrambleText>{etiqueta}</ScrambleText></a>.

  • Disparadores: pasar el ratón (no táctil) o :focus-visible del teclado en el a, button o [data-fx-scr-host] que lo envuelve.
  • Duración: 360–520 ms, dependiendo de la longitud.
  • Resolución: los caracteres se resuelven de izquierda a derecha con un poco de fluctuación. La cola no resuelta se redibuja a ~18 Hz en lugar de cada fotograma, por lo que se lee como decodificación, no como ruido.
  • Conjuntos de glifos: coinciden con mayúsculas/minúsculas, y la puntuación como @ . / : - se queda quieta para que los correos electrónicos y las rutas sigan siendo legibles durante el revuelto.
  • Limpieza: la superposición siempre se vacía, incluido un resguardo de temporizador para pestañas en segundo plano donde rAF se detiene.

Usa inline para enlaces en línea simples (mantiene el subrayado). Usa quiet en botones rellenos, junto con --fx-scr-tail: currentColor en el botón.

Tematización

VariableUsada porPor defecto
--fx-accentcapa de fallo A, glifos de revuelto, resplandor del foco, sombra de holo#5eead4
--fx-hotcapa de fallo B#f472b6
--fx-sheen-1..5anillo holo, en orden alrededor del círculoverde azulado, azul, violeta, rosa, ámbar
--fx-grid-line, --fx-grid-fine-linelíneas de cuadrícula basepizarra de baja opacidad
--fx-grid-cell, --fx-grid-fineespaciado de cuadrícula (px)96px, 24px
--fx-spot-sizeventana de foco (px)560px
--fx-monofuente de glifos de revueltomono del sistema
--fx-scr-tailanulación de color de glifo por elementosin establecer

Los colores se mezclan a través de color-mix(in srgb, …), compatible con todos los navegadores actuales. El shader toma su propia paleta: <HeroShader palette={{ accent: "#22d3ee", secondary: "#f0abfc" }} intensity={0.8} />.

Elige colores para el shader de la misma familia que el acento de la página. El shader es un ambiente, no un segundo color de marca; si un visitante primerizo nota el shader antes que el titular, es demasiado fuerte.

Capas

Capaz-indexNotas
Cuadrícula base (.fx-grid::before, fija)0Anclada al origen de la ventana gráfica
Foco (div.fx-spot, fijo)0Después de la cuadrícula en orden de árbol, por lo que pinta sobre ella
Contenido (main, pie de página)1Debe estar posicionado con z-index: 1
Shader del héroe (canvas.fx-shader)autoAbsoluto dentro del héroe, primer hijo, por lo que el contenido posterior pinta sobre él
Superposición holo3 dentro de su tarjetapointer-events: none
Encabezado fijo, cualquier canvas de superposición de animación de construcciónsobre el contenidoNo afectado

Si el foco se muestra sobre tu texto, al envoltorio de contenido le falta position: relative; z-index: 1.

Preferencias de movimiento

En lamanhtruong.com, el propietario eligió reproducir todos los efectos para todos, incluidos los visitantes con prefers-reduced-motion, porque el movimiento es el punto del portafolio. Esa es una decisión de producto legítima, pero tómala a propósito. Para respetar la preferencia en su lugar:

  • <FxRoot motion="reduce" />: respecta prefers-reduced-motion y desactiva los efectos de parpadeo y movimiento (fallo, revuelto, inclinación de tarjetas y foco de cuadrícula). La aurora se mantiene como una animación de fondo sutil y suave. El foco de cuadrícula se convierte en un simple realce de opacidad estático.
  • <FxRoot motion="off" />: desactiva todos los efectos animados. La página se renderiza exactamente como lo haría sin el skill.
  • El valor por defecto es "on", que reproduce todos los efectos independientemente de la preferencia del usuario.

La detección de prefers-reduced-motion se realiza a través de una consulta de medios CSS y se pasa al contexto de FxRoot. Los efectos individuales pueden optar por no participar consultando la misma variable CSS o el contexto de React.

Referencias

Se proporcionan documentos de referencia adicionales en el directorio references/:

  • references/aurora-shader.md: Explicación detallada de las matemáticas del shader, la deformación de dominio, la función de ruido y el modelo de iluminación. Léelo antes de modificar el shader.
  • references/gating-and-performance.md: Explicación del sistema de compuerta de construcción, cómo funciona built.ts y cómo los efectos esperan a que el contenido esté listo. También cubre las optimizaciones de rendimiento y el bucle de fotogramas.
  • references/verification.md: Lista de verificación de Playwright para asegurar que los efectos funcionan correctamente, no causan problemas de accesibilidad y mantienen el rendimiento.

Solución de problemas

ProblemaCausa probableSolución
El shader no se muestraWebGL no disponible o fallóVerifica la consola del navegador para mensajes de error de WebGL. Asegúrate de que el canvas esté dentro de una sección con position: relative.
Las tarjetas no se inclinanSelector incorrecto o tarjeta envueltaUsa <FxRoot holo={{ selector: ".mi-tarjeta" }} /> o asegúrate de que la tarjeta tenga el atributo data-holo.
El foco de cuadrícula no apareceEl envoltorio de contenido carece de z-indexAñade position: relative; z-index: 1 al <main> o contenedor de contenido.
El texto revuelto no se decodificaEl elemento no es a o buttonEnvuelve el texto en <ScrambleText> y asegúrate de que el elemento padre sea un enlace o botón.
El fallo de encabezado no se reproduceEl encabezado no está en vistaDesplázate para que el encabezado esté al menos al 50% visible. La reproducción está limitada a una vez cada 2.5 s.
El rendimiento es lentoDemasiados efectos o GPU débilReduce la calidad del shader con intensity más bajo o desactiva efectos no esenciales con props en FxRoot.

Licencia

El código proporcionado en este skill se ofrece como ejemplo y guía. Siéntete libre de adaptarlo y usarlo en tus propios proyectos. Se agradece la atribución, pero no es obligatoria.


Documentación generada para el skill anhlamtruong/living-ui-effects. Para la documentación más reciente y ejemplos, visita el repositorio original.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills relacionados