@fethabo/animated-ui
v0.7.3
Published
Lightweight animated UI components for React. Zero runtime dependencies, tree-shakeable, fully customizable via CSS custom properties (--aui-*).
Maintainers
Readme
@fethabo/animated-ui
Componentes animados livianos para React: cero dependencias de runtime, tree-shakeable, y customizables al 100% via props y CSS custom properties (--aui-*).
npm install @fethabo/animated-uiimport { AnimatedBackground } from '@fethabo/animated-ui'
export default function Hero() {
return (
<section style={{ position: 'relative', minHeight: '60vh' }}>
<AnimatedBackground variant="aurora" />
<h1 style={{ position: 'relative' }}>Hola mundo</h1>
</section>
)
}No hace falta importar ningún CSS: los estilos se inyectan automáticamente al montar cada componente.
📖 Documentación interactiva: en animated-ui-docs.fethabo.cloud podés ver la documentación completa y los ejemplos de la última versión —cada componente con demo vivo, props, ejemplos con copiar y limitaciones, en español e inglés. El código de la web vive en docs/.
Compatibility
- React 18+ (
reactyreact-domson peer dependencies). - Vite, Next.js App Router (los componentes incluyen
'use client'), y Astro (como island conclient:load). - JavaScript y TypeScript: el paquete publica JavaScript compilado; los tipos
.d.tslos aprovechan los proyectos TypeScript automáticamente y los proyectos JavaScript los ignoran. No necesitás TypeScript para usarlo. - SSR-safe: ningún componente accede a
document/windowdurante el render; las animaciones arrancan tras la hidratación. - Todos los componentes respetan
prefers-reduced-motionpor defecto (desactivable conrespectReducedMotion={false}).
Release Workflow
Las releases se manejan con @fethabo/tagman, que es la herramienta de release del repositorio.
npm run releasees el entrypoint único del flujo de release (npm run release:dry-runpara previsualizar).- La configuración vive en tagman.config.json: repo
npm, tags en formato@fethabo/[email protected]. CHANGELOG.mdse genera a partir de los commits acumulados desde el tag anterior (convención de commits), sin edición manual.package.jsonse actualiza al momento de crear un nuevo tag, con la versión alineada al tag generado.- La herramienta de release no forma parte de las dependencias de runtime del paquete publicado (
files: ["dist"]).
Components
| Componente | Descripción |
| --- | --- |
| AnimatedBackground | Background animado con CSS puro, con variantes aurora, mesh, noise, beam, lava, grid, rays y dots. |
| PixelBackground | Grilla de píxeles sobre canvas con behaviors combinables: hover, idle y reveal. |
| TiltCard | Card con efecto 3D tilt via WAAPI, con glare opcional y render prop de estado. |
| SpotlightCard | Contenedor con spotlight radial que sigue al cursor, sin re-renders por frame. |
| GlowBorder | Borde de gradiente cónico animado, en loop autónomo o apuntando al cursor. |
| MagneticElement | Wrapper que atrae su contenido hacia el cursor, con retorno elástico y render prop. |
| RippleContainer | Ondas expansivas desde el punto de click (material ripple), con nodos efímeros autolimpiados. |
| ShinyText | Texto con un brillo que lo barre en loop, CSS puro; sirve también como texto con gradiente. |
| ScrambleText | Texto que se "descifra" carácter por carácter (efecto decrypt/Matrix), accesible. |
| TypewriterText | Revela texto carácter por carácter (máquina de escribir) con cursor parpadeante y modo loop multi-string, accesible. |
| ScrollReveal | Revela su contenido al entrar al viewport, con dirección y stagger entre hijos. |
| SplitReveal | Parte el texto en char/word/line y revela cada unidad con stagger (presets fade/slide-up/blur), CSS puro y accesible. |
| RotatingText | Rota cíclicamente entre palabras con transición (fade/slide-up/flip) y layout estable, accesible sin aria-live. |
| GlitchText | Glitch RGB-split intermitente para titulares, CSS puro con pseudo-elementos y clip-path. |
| WavyText | Caracteres ondulando en loop continuo (ola que recorre el texto), CSS puro y accesible. |
| CountUp | Número que cuenta hasta su valor al entrar al viewport, con easing de salida y formato configurable, SEO-safe. |
| MouseParallax | Capas con profundidad que se desplazan según el mouse, sin re-renders por frame. |
| ParallaxLayers | Capas con profundidad ligadas a la posición de scroll, sin re-renders por frame. |
| ScrollProgress | Barra fija de progreso de lectura de la página, compositada. |
| ParticleField | Campo de partículas sobre canvas con repulsión/atracción al cursor, modos de deriva y líneas de conexión (constellation). |
| ImageDissolve | Transición entre imágenes con dithering ordered (matriz Bayer 8×8) al cambiar src. |
| StickyScenes | Secciones sticky que transicionan entre escenas durante el scroll, sin re-renders por frame. |
| StackedCards | Cards que se fijan y se apilan una sobre otra al scrollear; las de abajo se encogen/oscurecen, sin re-renders por frame. |
| TextScrollReveal | Párrafo cuyas palabras se "encienden" progresivamente según el avance del scroll (highlight progresivo), reversible y accesible. |
| CircuitBackground | Fondo de circuito PCB generado proceduralmente (seedable y determinista), con pulsos de luz recorriendo las pistas. |
| TeslaCoil | Nodo central que arroja rayos jagged hacia afuera; en hover dirige un rayo al cursor. children interactivo. |
| AttentionCue | Tras inactividad del mouse, dibuja un trazo dirigido a un elemento (modo directed) o ambiental. Idle / Attention. |
| GuidingBranches | Tras inactividad, ramas orgánicas generativas que crecen desde el puntero, con estéticas intercambiables. Idle / Attention. |
| Dock | Fila de ítems que se magnifican por proximidad del cursor (dock de macOS), horizontal o vertical. |
| BorderBeam | Cometa de luz que recorre el perímetro del borde en loop, CSS casi puro (offset-path). |
| Marquee | Cinta infinita de contenido sin costura, accesible, con pausa en hover y modo acoplado a la velocidad de scroll. |
| HorizontalScrollSection | Sección sticky cuyo contenido se desplaza horizontalmente conducido por el scroll vertical. |
| WavesBackground | Fondo de líneas fluidas que ondulan orgánicamente con ruido simplex seedable. |
| FlowField | Partículas que siguen un campo vectorial de ruido dejando trazos orgánicos con fade, seedable. |
| TopographicBackground | Curvas de nivel animadas (mapa topográfico vivo) extraídas por marching squares, seedable. |
| ConfettiBurst | Ráfaga de confetti one-shot disparable imperativamente via ref (fire()), para celebrar submits, logros, likes. Celebración / Feedback. |
| FireworksBurst | Fuegos artificiales one-shot: cohetes que ascienden y explotan en chispas radiales, via ref (fire()). Celebración / Feedback. |
| SparkleBurst | Destellos breves (estrellas de 4 puntas) alrededor de un punto, via ref (fire()) — likes, favoritos. Celebración / Feedback. |
| EmojiBurst | Ráfaga de emojis con física de confetti, renderizados con la fuente de la plataforma (cero assets), via ref (fire()). Celebración / Feedback. |
| ClickSpark | Chispas automáticas en cada click dentro del contenedor — declarativo, sin ref; children interactivos. Celebración / Feedback. |
| StarfieldBackground | Cielo estrellado vivo: estrellas titilando asíncronas + fugaces ocasionales, seedable y determinista. |
| MatrixRain | Lluvia de glifos cayendo por columnas (code rain), seedable, con charset y colores configurables. |
| CursorTrail | Estela de partículas o línea fluida que sigue al puntero dentro del contenedor. Cursor. |
| CustomCursor | Punto + anillo con lag elástico que reemplaza al cursor nativo dentro del contenedor; el anillo se agranda sobre interactivos. Cursor. |
| ImageTrail | Imágenes efímeras que brotan siguiendo el mouse y se desvanecen (efecto agency/portfolio). Cursor. |
| TextHighlighter | Marcador a mano alzada que subraya/resalta/encierra/tacha texto inline, dibujándose al entrar al viewport. SVG stroke. |
| DrawPath | Cualquier SVG del consumer se "dibuja" trazo a trazo al entrar al viewport, con stagger entre paths. SVG stroke. |
| ScribbleDecoration | Garabatos decorativos animados (flecha, asterisco, espiral…), procedurales, seedables y extensibles por función. SVG stroke. |
| AnimatedList | Hijos keyed que animan entrada, salida y reordenamiento (filtros, sorting, todo-lists). Layout / FLIP. |
| AutoHeight | Contenedor que transiciona su altura (y opcionalmente ancho) al cambiar el contenido — la forma de animar height: auto. Layout / FLIP. |
AnimatedBackground
Background animado renderizado con CSS puro (sin JS por frame). Se posiciona absolute, inset: 0 para cubrir su contenedor position: relative, o el viewport completo con fixed. Cada variante tiene defaults visualmente atractivos y expone sus colores, velocidad e intensidad tanto por props como por CSS custom properties.
Variante lava: blobs opacos que ascienden y descienden fundiéndose con el truco "gooey" (filter: blur() + contrast()), evocando una lámpara de lava. El filter sobre áreas grandes tiene costo de pintado: rinde mejor en contenedores acotados que a pantalla completa en gama baja. Con prefers-reduced-motion degrada a una composición estática de los blobs fundidos.
Variantes grid / rays / dots: grilla retro-synthwave en perspectiva cuyas líneas avanzan hacia el horizonte (loop por período de celda exacto, sin salto), haces de luz que rotan lentamente en vaivén desde un vértice superior, y retícula de puntos con pulso suave de opacidad/escala. Mismo contrato de colors/speed/intensity que el resto.
import { AnimatedBackground } from '@fethabo/animated-ui'
<div style={{ position: 'relative', height: 400 }}>
<AnimatedBackground
variant="beam"
colors={['rgba(251,191,36,0.4)', 'rgba(249,115,22,0.3)']}
speed={9}
/>
</div>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| variant | 'aurora' \| 'mesh' \| 'noise' \| 'beam' \| 'lava' \| 'grid' \| 'rays' \| 'dots' | 'aurora' | Variante visual de la animación. |
| colors | string[] | colores de la variante | Paleta de la animación (hasta 4 colores); los no provistos caen al default de la variante. |
| speed | number | según variante | Segundos que tarda un ciclo completo de la animación. |
| intensity | number | 1 | Intensidad/opacidad global del efecto, de 0 a 1. |
| fixed | boolean | false | Si es true usa position: fixed para cubrir el viewport completo. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion muestra el gradiente estático, sin animación. |
| className | string | — | Clases adicionales para el elemento root (Tailwind, CSS modules, etc.). |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
CSS Custom Properties
Todas se pueden pisar desde tu CSS en cascada, e.g. .mi-bg { --aui-aurora-speed: 20s; }.
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-aurora-color-1 | #5b21b6 | Primer gradiente de la aurora (violeta). |
| --aui-aurora-color-2 | #0ea5e9 | Segundo gradiente (cyan). |
| --aui-aurora-color-3 | #10b981 | Tercer gradiente (verde). |
| --aui-aurora-color-4 | #ec4899 | Cuarto gradiente (rosa). |
| --aui-aurora-speed | 14s | Duración de un ciclo completo. |
| --aui-aurora-blur | 60px | Desenfoque que difumina los gradientes. |
| --aui-aurora-opacity | 1 | Intensidad global del efecto. |
| --aui-mesh-color-1 | #7c3aed | Blob superior izquierdo (violeta). |
| --aui-mesh-color-2 | #db2777 | Blob superior derecho (magenta). |
| --aui-mesh-color-3 | #2563eb | Blob inferior derecho (azul). |
| --aui-mesh-color-4 | #0d9488 | Blob inferior izquierdo (teal). |
| --aui-mesh-speed | 18s | Duración de un ciclo de morphing. |
| --aui-mesh-blur | 40px | Desenfoque que funde los blobs. |
| --aui-mesh-opacity | 1 | Intensidad global del efecto. |
| --aui-noise-base | #0a0a0a | Color base de fondo bajo el grain. |
| --aui-noise-opacity | 0.12 | Opacidad del grain (intensidad). |
| --aui-noise-speed | 0.6s | Velocidad del parpadeo del grain. |
| --aui-beam-base | #050510 | Color de fondo detrás de los rayos. |
| --aui-beam-color-1 | rgba(124, 58, 237, 0.45) | Primer haz de luz. |
| --aui-beam-color-2 | rgba(14, 165, 233, 0.35) | Segundo haz de luz. |
| --aui-beam-color-3 | rgba(236, 72, 153, 0.3) | Tercer haz de luz. |
| --aui-beam-speed | 16s | Duración de una rotación completa. |
| --aui-beam-blur | 24px | Desenfoque que suaviza los bordes de los rayos. |
| --aui-beam-opacity | 1 | Intensidad global del efecto. |
| --aui-lava-base | #160a2b | Color de fondo opaco detrás de los blobs. |
| --aui-lava-color-1 | #ff4d6d | Primer color de blob. |
| --aui-lava-color-2 | #ff924d | Segundo color de blob. |
| --aui-lava-speed | 16s | Duración de un ascenso/descenso completo. |
| --aui-lava-blur | 16px | Desenfoque del truco gooey. |
| --aui-lava-contrast | 16 | Contraste que "endurece" los bordes del blur (fusión gooey). |
| --aui-lava-size | 280px | Diámetro base de los blobs. |
| --aui-lava-opacity | 1 | Intensidad global del efecto. |
| --aui-grid-line | rgba(124, 58, 237, 0.5) | Color de las líneas de la grilla synthwave. |
| --aui-grid-base | #050510 | Color de fondo / cielo. |
| --aui-grid-glow | rgba(236, 72, 153, 0.35) | Glow del horizonte. |
| --aui-grid-cell | 48px | Lado de la celda de la grilla. |
| --aui-grid-speed | 8s | Duración de un avance de celda completo. |
| --aui-grid-opacity | 1 | Intensidad global del efecto. |
| --aui-rays-color-1 | rgba(251, 191, 36, 0.4) | Primer haz de luz. |
| --aui-rays-color-2 | rgba(249, 115, 22, 0.28) | Segundo haz de luz. |
| --aui-rays-color-3 | rgba(236, 72, 153, 0.22) | Tercer haz de luz. |
| --aui-rays-base | #050510 | Color de fondo. |
| --aui-rays-speed | 18s | Duración de un barrido completo (vaivén). |
| --aui-rays-blur | 18px | Desenfoque que suaviza los haces. |
| --aui-rays-opacity | 1 | Intensidad global del efecto. |
| --aui-dots-color | rgba(124, 58, 237, 0.7) | Color de los puntos. |
| --aui-dots-base | #050510 | Color de fondo. |
| --aui-dots-size | 2px | Radio de cada punto. |
| --aui-dots-cell | 28px | Separación de la retícula. |
| --aui-dots-speed | 4s | Duración de un pulso completo. |
| --aui-dots-opacity | 1 | Intensidad global (pico del pulso). |
PixelBackground
Grilla de píxeles animada sobre <canvas> (una sola pasada de pintura por frame, sin miles de nodos DOM). Los behaviors se combinan libremente: hover ilumina las celdas cercanas al mouse con caída gaussiana, idle las hace parpadear de forma autónoma y asíncrona, y reveal las materializa al montar con dithering ordenado (matriz Bayer). El canvas se adapta solo al tamaño del contenedor.
import { PixelBackground } from '@fethabo/animated-ui'
<div style={{ position: 'relative', height: 300 }}>
<PixelBackground behaviors={['hover', 'idle', 'reveal']} color="#22d3ee" />
</div>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| behaviors | ('hover' \| 'idle' \| 'reveal')[] | ['hover'] | Behaviors activos; las contribuciones se componen por frame. |
| cellSize | number | 12 | Lado en px de cada celda cuadrada. |
| gap | number | 2 | Espacio en px entre celdas. |
| color | string | '#7c3aed' | Color estático para todas las celdas. |
| cellColor | CellColorFn | — | Color dinámico por celda; tiene prioridad sobre color. Ver abajo. |
| baseOpacity | number | 0.15 | Alpha base de las celdas sin contribución de behaviors (0 a 1). |
| hoverRadius | number | 120 | Radio en px de influencia del behavior hover. |
| idleIntensity | number | 1 | Amplitud del parpadeo del behavior idle (0 a 1). |
| idleSpeed | number | 1.5 | Velocidad del parpadeo idle. |
| revealDuration | number | 1200 | Duración en ms del reveal dithered. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion desactiva idle y reveal; hover sigue activo (responde a input directo del usuario). |
| className | string | — | Clases adicionales para el contenedor. |
| style | CSSProperties | — | Estilos inline adicionales para el contenedor. |
También acepta cualquier otra prop HTML válida de <div>.
El callback cellColor
type CellColorFn = (x: number, y: number, proximity: number, idlePhase: number) => string| Parámetro | Descripción |
| --- | --- |
| x | Columna de la celda en la grilla (entero, desde 0). |
| y | Fila de la celda en la grilla (entero, desde 0). |
| proximity | Contribución del behavior hover (0 a 1, 1 = bajo el cursor); 0 si no está activo. |
| idlePhase | Contribución del behavior idle (entre -idleIntensity y +idleIntensity); 0 si no está activo. |
Retorna cualquier color CSS válido. Ejemplo — gradiente por posición:
<PixelBackground cellColor={(x, y) => `hsl(${(x * 7 + y * 3) % 360}, 70%, 60%)`} />TiltCard
Card con efecto 3D tilt que sigue al mouse, animado con la Web Animations API nativa (element.animate()): la interpolación entre estados preserva el momentum al cambiar de dirección, sin el "snap" de las CSS transitions. Soporta un overlay de brillo especular (glare) y expone su estado de animación via render prop para construir efectos derivados (parallax, color shifts).
import { TiltCard } from '@fethabo/animated-ui'
<TiltCard glare maxAngle={12} className="rounded-xl shadow-2xl bg-white p-6">
<h3>Mi card</h3>
<p>Se inclina hacia el mouse.</p>
</TiltCard>Con render prop:
<TiltCard>
{({ tiltX, tiltY, isHovering }) => (
<div style={{ backgroundPosition: `${tiltY * 2}px ${tiltX * 2}px` }}>
{isHovering ? 'hover' : 'reposo'}
</div>
)}
</TiltCard>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| maxAngle | number | 15 | Ángulo máximo de rotación en grados, en cualquier eje. |
| perspective | number | 1000 | Profundidad de perspectiva 3D en px. --aui-tilt-perspective lo pisa via CSS. |
| glare | boolean | false | Agrega un overlay de brillo especular que se mueve inversamente al tilt. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion no rota (tiltX/tiltY quedan en 0), pero isHovering sigue funcionando. |
| children | ReactNode \| (state: TiltState) => ReactNode | — | Contenido del card, o función que recibe el TiltState actual. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div> (aria attributes, handlers, etc.).
TiltState
Objeto que recibe el render prop en cada actualización del tilt:
| Campo | Tipo | Descripción |
| --- | --- | --- |
| tiltX | number | Rotación actual sobre el eje X en grados (mouse arriba/abajo). |
| tiltY | number | Rotación actual sobre el eje Y en grados (mouse izquierda/derecha). |
| isHovering | boolean | true mientras el cursor está sobre el card. |
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-tilt-perspective | valor de la prop perspective (1000px) | Profundidad de perspectiva 3D; valores más altos producen un efecto más sutil. |
SpotlightCard
Contenedor con un spotlight radial que sigue al cursor, iluminando la zona bajo el mouse. El tracking escribe CSS custom properties directamente sobre el elemento (sin estado de React): mover el mouse no re-renderiza los children. El overlay tiene pointer-events: none, así que links y botones del contenido siguen siendo interactivos. El spotlight permanece activo con prefers-reduced-motion porque responde a input directo y no desplaza contenido.
import { SpotlightCard } from '@fethabo/animated-ui'
<SpotlightCard
color="rgba(34, 211, 238, 0.2)"
radius={300}
className="rounded-xl border bg-zinc-900 p-6"
>
<h3>Mi card</h3>
<p>La luz sigue al cursor.</p>
</SpotlightCard>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| color | string | rgba(255, 255, 255, 0.15) | Color del spotlight (conviene usar alpha). |
| radius | number | 250 | Radio del spotlight en px. |
| opacity | number | 1 | Opacidad máxima del overlay en hover (0 a 1). |
| respectReducedMotion | boolean | true | Aceptada por consistencia de API; el spotlight queda activo en ambos casos (es input directo, sin movimiento de contenido). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-spotlight-color | rgba(255, 255, 255, 0.15) | Color del gradiente del spotlight. |
| --aui-spotlight-radius | 250px | Radio del spotlight. |
| --aui-spotlight-opacity | 1 | Opacidad del overlay en hover. |
--aui-spotlight-x / --aui-spotlight-y son variables de runtime escritas por el componente; no las setees a mano.
GlowBorder
Contenedor con un anillo de borde de gradiente cónico animado. Por default el gradiente rota en loop; con followCursor apunta hacia el cursor con momentum (mismo patrón WAAPI de TiltCard). La animación rota una capa con transform (corre en el compositor, soporte universal de browsers) en vez de animar el ángulo del gradiente con @property.
Estructura del contenido: el gradiente cubre todo el fondo del wrapper y el contenido lo tapa con su propio background, dejando visible solo el anillo del perímetro. Pasá el background de tu contenido via contentStyle/contentClassName — si ponés el background en el root via className, vas a tapar el anillo.
import { GlowBorder } from '@fethabo/animated-ui'
<GlowBorder
width={2}
radius={16}
colors={['#22d3ee', '#a78bfa']}
contentStyle={{ background: '#12121f', padding: '2rem' }}
>
<h3>Mi contenido</h3>
</GlowBorder>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| colors | string[] | violeta/cyan/rosa | Colores del gradiente cónico (hasta 3); los no provistos caen al default. |
| speed | number | 4 | Segundos por rotación completa del loop. |
| width | number | 1 | Ancho del anillo en px. |
| radius | number | 12 | Border-radius exterior en px (el interior se calcula solo). |
| opacity | number | 1 | Intensidad del glow (0 a 1). |
| followCursor | boolean | false | Reemplaza el loop por orientar el gradiente hacia el cursor, con momentum. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion detiene el loop (gradiente estático); followCursor sigue activo (input directo). |
| contentClassName | string | — | Clases para el contenedor interno de contenido (donde va tu background). |
| contentStyle | CSSProperties | — | Estilos inline para el contenedor interno de contenido. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-glow-color-1 | #7c3aed | Primer color del cónico (violeta). |
| --aui-glow-color-2 | #0ea5e9 | Segundo color (cyan). |
| --aui-glow-color-3 | #ec4899 | Tercer color (rosa). |
| --aui-glow-speed | 4s | Duración de una rotación del loop. |
| --aui-glow-width | 1px | Ancho del anillo de borde. |
| --aui-glow-radius | 12px | Border-radius exterior. |
| --aui-glow-opacity | 1 | Intensidad del glow. |
MagneticElement
Wrapper que atrae su contenido hacia el cursor cuando se acerca, con retorno elástico al salir. La traslación usa WAAPI con interpolación que preserva momentum (patrón TiltCard sobre translate). Expone su estado via render prop para construir efectos derivados.
Hit-area y layout: la zona de atracción se agranda con padding transparente alrededor del contenido (hitArea), que participa del layout del wrapper. Con hitArea={0} el wrapper colapsa al tamaño del contenido y la atracción arranca recién al entrar en él.
import { MagneticElement } from '@fethabo/animated-ui'
<MagneticElement strength={0.5} hitArea={60}>
<button className="rounded-full bg-violet-600 px-8 py-3 text-white">
Atrapame
</button>
</MagneticElement>Con render prop:
<MagneticElement>
{({ offsetX, offsetY, isActive }) => (
<div style={{ boxShadow: `${-offsetX}px ${-offsetY}px 24px rgba(124,58,237,0.4)` }}>
{isActive ? 'atraído' : 'reposo'}
</div>
)}
</MagneticElement>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| strength | number | 0.35 | Intensidad de la atracción (0 a 1). |
| hitArea | number | 40 | Padding transparente en px que agranda la zona de atracción (participa del layout). |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion el contenido no se mueve (offsets en 0), pero isActive sigue reportándose. |
| children | ReactNode \| (state: MagneticState) => ReactNode | — | Contenido a magnetizar, o función que recibe el MagneticState actual. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
MagneticState
Objeto que recibe el render prop en cada actualización:
| Campo | Tipo | Descripción |
| --- | --- | --- |
| offsetX | number | Desplazamiento horizontal actual del contenido en px. |
| offsetY | number | Desplazamiento vertical actual del contenido en px. |
| isActive | boolean | true mientras el cursor está dentro de la zona de atracción. |
RippleContainer
Contenedor que dibuja una onda expansiva desde el punto exacto de cada click (material ripple). Cada onda es un nodo efímero creado imperativamente en pointerdown (la onda arranca al presionar, no al soltar) y removido del DOM al terminar su animación — sin estado de React por onda: los clicks rápidos generan ondas concurrentes sin re-renders ni acumulación de nodos. Las ondas viven en una capa pointer-events: none recortada al contenedor (hereda su border-radius), así nunca interceptan clicks ni foco de los children.
Con prefers-reduced-motion, la expansión se reemplaza por un fade estático breve en el punto del click: se preserva el feedback de interacción, no el movimiento.
import { RippleContainer } from '@fethabo/animated-ui'
<RippleContainer color="rgba(255,255,255,0.5)" duration={700} style={{ borderRadius: 12 }}>
<button className="mi-boton">Click acá</button>
</RippleContainer>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| color | string | currentColor | Color de las ondas. |
| duration | number | 600 | Duración de cada onda (expansión + fade) en ms. |
| maxRadius | number | cubre el contenedor | Radio máximo de la onda en px; por default llega hasta la esquina más lejana desde el punto de click. |
| opacity | number | 0.25 | Opacidad inicial de la onda. |
| respectReducedMotion | boolean | true | Con reduce, fade estático sin expansión. |
| className / style | — | — | Extensión del root. |
También acepta cualquier otra prop HTML válida de <div> — incluidos tus propios handlers: un onPointerDown del consumer sigue funcionando junto a la onda.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-ripple-color | currentColor | Color de las ondas. Prevalece sobre color. |
| --aui-ripple-duration | 600ms | Duración de cada onda. Prevalece sobre duration. |
| --aui-ripple-opacity | 0.25 | Opacidad inicial de la onda. Prevalece sobre opacity. |
ShinyText
Texto con una franja de brillo que lo barre en loop. CSS puro: el gradiente se clipea a los glifos con background-clip: text y se desplaza animando background-position — cero JS por frame. Con colores custom de base y brillo funciona también como texto con gradiente animado. El texto sigue siendo texto real (seleccionable, copiable, legible por lectores de pantalla).
Semántica: renderiza un <span>; el heading o párrafo lo ponés vos envolviéndolo.
import { ShinyText } from '@fethabo/animated-ui'
<h1>
<ShinyText color="#71717a" highlight="#fafafa" speed={3}>
Texto que brilla solo
</ShinyText>
</h1>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| color | string | #71717a | Color base del texto. |
| highlight | string | #fafafa | Color de la franja de brillo. |
| speed | number | 3 | Segundos por barrido completo del loop. |
| angle | number | 120 | Ángulo del gradiente/barrido en grados. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion detiene el barrido y queda el gradiente estático. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <span>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-shiny-color | #71717a | Color base del texto. |
| --aui-shiny-highlight | #fafafa | Color de la franja de brillo. |
| --aui-shiny-speed | 3s | Duración de un barrido del loop. |
| --aui-shiny-angle | 120deg | Dirección del gradiente/barrido. |
ScrambleText
Texto que se "descifra" carácter por carácter (efecto decrypt/Matrix). Un loop de requestAnimationFrame muta el texto directamente (sin re-renders de React por frame), con progresión por timestamps — misma duración en displays de 60 y 144 Hz. Es accesible durante la animación: el root expone aria-label con el texto final y los caracteres aleatorios intermedios están ocultos para lectores de pantalla.
Tipografía: con fuentes proporcionales los caracteres aleatorios miden distinto que los finales y el ancho puede "vibrar" durante el scramble. Para textos sensibles a layout usá una fuente monospace o font-variant-numeric: tabular-nums.
import { ScrambleText } from '@fethabo/animated-ui'
<h1 style={{ fontFamily: 'monospace' }}>
<ScrambleText text="Acceso concedido" trigger="both" />
</h1>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| text | string | — (requerida) | Texto final a revelar. Es un string plano, no children: el scrambler opera carácter por carácter. |
| speed | number | 25 | Caracteres revelados por segundo. |
| charset | string | letras, números y símbolos | Pool de caracteres aleatorios mostrados durante el scramble. |
| trigger | 'mount' \| 'hover' \| 'both' | 'mount' | 'mount' anima al montar y al cambiar text; 'hover' re-anima en cada mouseenter; 'both' combina ambos. |
| scrambleColor | string | currentColor | Color de los caracteres mientras dura el scramble. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion muestra el texto final directo; el trigger hover sigue activo (input directo). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <span>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-scramble-color | currentColor | Color de los caracteres mientras dura el scramble (al terminar, el texto vuelve a heredar su color). |
TypewriterText
Revela texto carácter por carácter (efecto máquina de escribir). Mismo motor que ScrambleText: un loop de requestAnimationFrame muta el texto via ref (sin re-renders por frame), con progresión por timestamps — misma velocidad en displays de 60 y 144 Hz. Pasando un string[] con loop, cicla escribiendo → pausando → borrando → siguiente. El cursor parpadea con una animación CSS (sin JS por frame). Es accesible: el root expone aria-label con el texto completo y los caracteres intermedios están ocultos para lectores de pantalla.
Tipografía: el cursor es un elemento inline; con fuentes proporcionales el texto puede "saltar" al escribir. Para textos sensibles a layout usá monospace (mismo caveat que ScrambleText).
import { TypewriterText } from '@fethabo/animated-ui'
// Un solo string
<TypewriterText text="Hola, soy Claude." speed={30} />
// Modo loop multi-string
<TypewriterText text={['Diseño', 'Código', 'Arte']} loop cursor="_" />| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| text | string \| string[] | — (requerida) | Texto a escribir. Un string[] con loop cicla entre los strings. Es texto plano, no children. |
| speed | number | 30 | Caracteres escritos por segundo. |
| startDelay | number | 0 | Milisegundos antes de comenzar a escribir. |
| cursor | boolean \| string | true | Cursor al final: true usa |, un string usa ese glifo, false lo desactiva. |
| deleteSpeed | number | 30 | Caracteres borrados por segundo en modo loop. |
| pauseDuration | number | 1500 | Milisegundos de pausa con el string completo antes de borrar. |
| loop | boolean | false | Con un string[], cicla indefinidamente (escribe→pausa→borra→siguiente). |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion muestra el texto final completo de inmediato, sin escritura ni parpadeo. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <span>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-typewriter-cursor-speed | 1s | Velocidad de parpadeo del cursor (solo via CSS). |
ScrollReveal
Revela su contenido al entrar al viewport (IntersectionObserver via el hook useInView), con fade + desplazamiento configurable y stagger entre hijos directos. La entrada es una CSS transition pura: el JavaScript solo togglea un atributo, cero JS por frame.
Layout: cada hijo directo se envuelve en un <div> item (el que anima). El root acepta className/style, así puede ser él mismo tu grid o flex y los items actúan como celdas.
Pre-hidratación: el contenido se renderiza oculto desde el primer paint (sin flash) pero presente en el DOM (SEO, crawlers, lectores). Con reduced motion o en browsers sin IntersectionObserver se muestra directo. Es el comportamiento estándar de las librerías de reveal.
import { ScrollReveal } from '@fethabo/animated-ui'
<ScrollReveal direction="up" stagger={0.15} className="grid grid-cols-3 gap-4">
<Card>Uno</Card>
<Card>Dos</Card>
<Card>Tres</Card>
</ScrollReveal>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| direction | 'up' \| 'down' \| 'left' \| 'right' \| 'none' | 'up' | Desde dónde entra el contenido ('up' aparece desde abajo; 'none' solo fade). |
| distance | number | 24 | Desplazamiento inicial en px. |
| duration | number | 0.6 | Duración de la transición en segundos. |
| stagger | number | 0.1 | Segundos de delay incremental entre hijos directos. |
| threshold | number | 0.15 | Fracción visible del componente que dispara el reveal. |
| once | boolean | true | Si es false, el contenido se re-oculta al salir del viewport y re-revela al entrar. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion muestra el contenido directo, sin transición. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-reveal-duration | 0.6s | Duración de la transición de entrada. |
| --aui-reveal-distance | 24px | Desplazamiento inicial. |
| --aui-reveal-stagger | 0.1s | Delay incremental entre hijos. |
| --aui-reveal-easing | cubic-bezier(0.22, 1, 0.36, 1) | Curva de la transición (solo via CSS). |
--aui-reveal-i es una variable de runtime (índice por item, escrita por el componente); no la setees a mano. Los items animan con translate (propiedad independiente, browsers 2022+), que no pisa el transform de tu contenido.
SplitReveal
Parte un texto en unidades (char, word o line) y revela cada una con stagger. La entrada es una CSS transition pura (cero JS por frame): el JavaScript solo togglea un atributo. Dispara al montar (trigger="mount") o al entrar al viewport (trigger="in-view", vía useInView).
Pre-hidratación y accesibilidad: el texto se renderiza completo y visible desde el primer paint (SSR/SEO) y se parte en spans recién tras la hidratación. El root porta el texto completo en aria-label y las unidades partidas son aria-hidden, así los lectores de pantalla anuncian el texto original, no los fragmentos. Con reduced motion (o sin IntersectionObserver) se muestra el texto completo de inmediato.
Modo line: partir por línea depende del wrapping real (ancho del contenedor, fuente cargada). Se mide tras el montaje y se re-mide en resize (vía useResizeObserver); todas las palabras de una misma línea revelan juntas.
import { SplitReveal } from '@fethabo/animated-ui'
<h1>
<SplitReveal text="Animación con stagger" split="word" preset="slide-up" />
</h1>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| text | string | — (requerida) | Texto a revelar. Es un string plano, no children. |
| split | 'char' \| 'word' \| 'line' | 'word' | Unidad de partición. |
| preset | 'fade' \| 'slide-up' \| 'blur' | 'slide-up' | Animación de entrada de cada unidad. |
| trigger | 'mount' \| 'in-view' | 'in-view' | Qué dispara el revelado. |
| stagger | number | 0.05 | Segundos de delay incremental entre unidades. |
| duration | number | 0.6 | Duración de la transición de cada unidad, en segundos. |
| distance | number | 16 | Desplazamiento inicial en px para slide-up. |
| threshold | number | 0.15 | Fracción visible que dispara el revelado con trigger="in-view". |
| once | boolean | true | Si es false, se re-oculta al salir del viewport y re-revela al re-entrar. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion muestra el texto completo de inmediato, sin stagger ni animación. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <span>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-split-duration | 0.6s | Duración de la transición de cada unidad. |
| --aui-split-stagger | 0.05s | Delay incremental entre unidades. |
| --aui-split-distance | 16px | Desplazamiento inicial del preset slide-up. |
| --aui-split-blur | 8px | Desenfoque inicial del preset blur. |
| --aui-split-easing | cubic-bezier(0.22, 1, 0.36, 1) | Curva de la transición (solo via CSS). |
--aui-split-i es una variable de runtime (índice por unidad, o índice de línea medido en modo line); no la setees a mano.
RotatingText
Texto base opcional + una palabra que rota cíclicamente por una lista con transición animada: "Hacemos webs / apps / magia". El avance usa timers (sin RAF) y la transición es CSS inyectado. El ancho del contenedor de la palabra transiciona suavemente entre palabras de largos distintos (medición al cambiar, no por frame) — si querés eliminar incluso ese ajuste, fijá un width via CSS sobre .aui-rotating-box.
Accesible sin spam: el root expone un aria-label estático con el texto base + la lista completa, y la palabra animada es aria-hidden — sin aria-live. Con prefers-reduced-motion muestra la primera palabra estática.
import { RotatingText } from '@fethabo/animated-ui'
<h1>
<RotatingText words={['webs', 'apps', 'magia']} transition="slide-up" color="#a78bfa">
Hacemos{' '}
</RotatingText>
</h1>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| words | string[] | — | Lista de palabras por las que rota. |
| transition | 'fade' \| 'slide-up' \| 'flip' | 'slide-up' | Preset de la transición entre palabras. |
| interval | number | 2200 | Ms que cada palabra permanece visible. |
| duration | number | 0.4 | Duración de la transición (y del ajuste de ancho) en segundos. |
| color | string | hereda | Color de la palabra rotante. |
| loop | boolean | true | Con false, se detiene en la última palabra. |
| respectReducedMotion | boolean | true | Con reduce, primera palabra estática. |
| children | ReactNode | — | Texto base opcional que precede a la palabra. |
| className / style | — | — | Extensión del root. |
También acepta cualquier otra prop HTML válida de <span>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-rotating-color | hereda | Color de la palabra rotante. Prevalece sobre color. |
| --aui-rotating-duration | 0.4s | Duración de la transición. |
GlitchText
Texto con glitch RGB-split intermitente (ráfagas breves separadas por períodos estables), CSS puro sin JS por frame: dos capas del mismo texto en pseudo-elementos (content: attr(data-text), fuera del árbol de accesibilidad — el texto se lee una sola vez) desplazadas en sentidos opuestos y recortadas con clip-path animado. trigger="hover" limita el glitch a mientras el cursor está encima.
Alcance: acepta solo texto plano (
children: string) y está pensado para titulares — elclip-pathanimado sobre párrafos largos tiene costo de pintado.
Con prefers-reduced-motion, loop queda estático; hover conserva un split estático atenuado, sin jitter.
import { GlitchText } from '@fethabo/animated-ui'
<GlitchText as="h1" frequency={2} intensity={4}>ERROR 404</GlitchText>
<GlitchText trigger="hover" colors={['#f0f', '#0ff']}>hover me</GlitchText>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| children | string | — | El texto (solo texto plano). |
| as | ElementType | 'span' | Elemento root a renderizar. |
| trigger | 'loop' \| 'hover' | 'loop' | Glitch autónomo intermitente, o solo en hover. |
| colors | [string, string] | rojo/cyan | Colores de los dos canales desplazados. |
| intensity | number | 3 | Desplazamiento máximo de los canales en px. |
| frequency | number | 1 | Ráfagas por ciclo (~3s). |
| burstDuration | number | 0.3 | Duración de cada ráfaga en segundos. |
| respectReducedMotion | boolean | true | Con reduce, texto estático (hover: split atenuado). |
| className / style | — | — | Extensión del root. |
También acepta cualquier otra prop HTML válida del elemento root.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-glitch-color-1 | #ff004d | Canal desplazado a la izquierda. Prevalece sobre colors[0]. |
| --aui-glitch-color-2 | #00fff9 | Canal desplazado a la derecha. |
| --aui-glitch-intensity | 3px | Desplazamiento de los canales. Prevalece sobre intensity. |
| --aui-glitch-cycle | 3s | Duración del ciclo completo de ráfagas. |
WavyText
Caracteres ondulando en loop continuo: una ola recorre el texto de izquierda a derecha. CSS puro (keyframes + animation-delay escalonado por índice, seteado inline una sola vez), sin JS por frame; anima solo transform: translateY (compositado), así la métrica de la línea circundante no cambia. Reutiliza el split por carácter del paquete: el texto completo va en aria-label y los caracteres son aria-hidden, con los espacios preservados.
Con prefers-reduced-motion el texto queda estático en su línea base.
import { WavyText } from '@fethabo/animated-ui'
<WavyText as="h2" amplitude={8} speed={1.4}>¡Olas en el texto!</WavyText>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| children | string | — | El texto a ondular (texto plano). |
| as | ElementType | 'span' | Elemento root a renderizar. |
| amplitude | number | 6 | Desplazamiento vertical máximo en px. |
| speed | number | 1.6 | Duración de un ciclo de ola en segundos. |
| stagger | number | 0.06 | Desfase entre caracteres consecutivos en segundos. |
| respectReducedMotion | boolean | true | Con reduce, texto estático. |
| className / style | — | — | Extensión del root. |
También acepta cualquier otra prop HTML válida del elemento root.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-wavy-amplitude | 6px | Desplazamiento vertical máximo. Prevalece sobre amplitude. |
| --aui-wavy-speed | 1.6s | Duración del ciclo de ola. Prevalece sobre speed. |
| --aui-wavy-stagger | 0.06s | Desfase entre caracteres. Prevalece sobre stagger. |
CountUp
Número que cuenta desde from hasta value al entrar al viewport, con easing de salida (arranque rápido, frenado al llegar) — el clásico de las stats de landing. El RAF muta textContent por ref (patrón ScrambleText): cero re-renders por frame. La cuenta corre una sola vez por montaje.
SEO-safe: el markup SSR contiene el valor final formateado (correcto sin JavaScript y para crawlers); el texto se resetea al valor inicial recién cuando la cuenta arranca. Accesible: el root expone el valor final en aria-label, así los lectores de pantalla anuncian el valor definitivo y no los intermedios. Con prefers-reduced-motion se muestra el valor final directo (coincide con el markup SSR — cero salto visual).
Tip: los números de ancho variable pueden hacer "bailar" el layout durante la cuenta. Aplicá
font-variant-numeric: tabular-numsal componente (o a su contenedor) para un ancho estable.
import { CountUp } from '@fethabo/animated-ui'
<CountUp
value={12500}
separator="."
suffix="+"
duration={2500}
style={{ fontVariantNumeric: 'tabular-nums' }}
/>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| value | number | — | Valor final de la cuenta (lo que renderiza el SSR). |
| from | number | 0 | Valor inicial desde el que arranca la cuenta. |
| duration | number | 2000 | Duración de la cuenta en ms. |
| decimals | number | 0 | Cantidad de decimales, estable durante toda la cuenta. |
| separator | string | '' | Separador de miles (e.g. '.', ','). |
| prefix | string | '' | String antepuesto al número (e.g. '$'). |
| suffix | string | '' | String pospuesto al número (e.g. '+'). |
| respectReducedMotion | boolean | true | Con reduce, valor final directo sin cuenta. |
| className / style | — | — | Extensión del root. |
También acepta cualquier otra prop HTML válida de <span>.
MouseParallax
Contenedor con capas a distintas profundidades que se desplazan según la posición del mouse — parallax creativo sin scroll. El tracking escribe CSS custom properties directamente sobre el root (patrón SpotlightCard): mover el mouse no re-renderiza los children. Cada capa se traslada con calc() puro suavizado por una transition del compositor.
Las capas se declaran con MouseParallax.Layer: depth positivo sigue al mouse, negativo se opone (profundidad invertida). Al salir el cursor, todo vuelve suavemente al centro. Es el efecto que el render prop de TiltCard insinuaba, como componente dedicado.
import { MouseParallax } from '@fethabo/animated-ui'
<MouseParallax style={{ minHeight: '60vh' }}>
<MouseParallax.Layer depth={40}>
<Estrellas />
</MouseParallax.Layer>
<MouseParallax.Layer depth={-15}>
<h1>Título en primer plano</h1>
</MouseParallax.Layer>
</MouseParallax>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| ease | number | 0.2 | Segundos del suavizado con que las capas siguen al mouse. |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion las capas quedan estáticas (el efecto desplaza contenido real). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
MouseParallax.Layer:
| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| depth | number | 20 | Desplazamiento máximo en px con el cursor en el borde; negativo se opone al mouse. |
Ambos aceptan cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-parallax-ease | 0.2s | Duración del suavizado de las capas. |
| --aui-parallax-depth | 20px | Profundidad de cada capa (la setea Layer desde su prop). |
--aui-parallax-x / --aui-parallax-y son variables de runtime escritas por el componente; no las setees a mano.
ParallaxLayers
Contenedor con capas a distintas profundidades ligadas a la posición de scroll — el primo de scroll de MouseParallax, con la misma API de Layer. Un listener pasivo de scroll (coalescido por requestAnimationFrame) escribe el progreso del contenedor por el viewport como CSS custom property; las capas se trasladan con calc() puro. Sin estado de React: scrollear no re-renderiza nada, y el tracking solo corre mientras el contenedor está cerca del viewport (vía IntersectionObserver) — varios contenedores fuera de pantalla cuestan cero por frame.
depth positivo se mueve con el scroll (más lento que el contenido: sensación de fondo); negativo va contra él.
Capas de fondo: al desplazarse pueden revelar "huecos" en los bordes del contenedor — es el comportamiento estándar del parallax. Sobredimensioná la capa de fondo (e.g. margin: -10% 0 o inset: -10%) para cubrirlos.
import { ParallaxLayers } from '@fethabo/animated-ui'
<ParallaxLayers style={{ overflow: 'hidden' }}>
<ParallaxLayers.Layer depth={80}>
<Fondo style={{ margin: '-10% 0' }} />
</ParallaxLayers.Layer>
<ParallaxLayers.Layer depth={-30}>
<h1>Primer plano</h1>
</ParallaxLayers.Layer>
</ParallaxLayers>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| respectReducedMotion | boolean | true | Con prefers-reduced-motion las capas quedan en su posición de layout (el efecto crea movimiento relativo durante el scroll). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
ParallaxLayers.Layer:
| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| depth | number | 40 | Desplazamiento máximo en px a lo largo del recorrido por el viewport; negativo va contra el scroll. |
Ambos aceptan cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-parallax-scroll-depth | 40px | Profundidad de cada capa (la setea Layer desde su prop). |
--aui-parallax-scroll es una variable de runtime escrita por el componente (progreso [-1, 1] del contenedor por el viewport); no la setees a mano.
ScrollProgress
Barra fija de progreso de lectura de la página. El progreso se escribe como CSS custom property por un listener pasivo coalescido por RAF, y la barra avanza con transform: scaleX — compositado, sin relayout ni re-renders de React. El track tiene pointer-events: none (no tapa clicks) y aria-hidden (es un reflejo decorativo de la posición de scroll; un progressbar actualizado por frame sería spam para lectores de pantalla). Permanece activa con prefers-reduced-motion: refleja 1:1 el scroll que el usuario controla, como la scrollbar nativa.
Headers fijos: si tu sitio tiene un header fixed/sticky, ajustá zIndex (o --aui-progress-z) para definir quién queda arriba.
import { ScrollProgress } from '@fethabo/animated-ui'
<ScrollProgress color="#22d3ee" height={4} />| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| position | 'top' \| 'bottom' | 'top' | Borde del viewport donde se fija la barra. |
| color | string | #7c3aed | Color de la barra. |
| height | number | 3 | Grosor en px. |
| trackColor | string | transparent | Color del track (fondo de la barra). |
| zIndex | number | 50 | z-index del elemento fijo. |
| respectReducedMotion | boolean | true | Aceptada por consistencia de API; la barra queda activa en ambos casos (refleja input directo, sin movimiento de contenido). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-progress-color | #7c3aed | Color de la barra. |
| --aui-progress-height | 3px | Grosor de la barra. |
| --aui-progress-bg | transparent | Color del track. |
| --aui-progress-z | 50 | z-index del elemento fijo. |
--aui-progress es una variable de runtime escrita por el componente (progreso [0, 1] de la página); no la setees a mano.
ParticleField
Campo de partículas autónomas sobre <canvas>, con repulsión/atracción configurable al cursor. Por default las partículas se mueven con velocidad aleatoria y rebotan en los bordes; dentro del radio del cursor reciben una fuerza proporcional a la proximidad. El cálculo es cursor-a-partícula (O(N)), no entre pares. El estado de las partículas vive en un ref que persiste entre frames: el RAF no re-renderiza React. Con prefers-reduced-motion el loop se detiene y el canvas muestra las partículas en su estado inicial estático.
Dos ejes opcionales extienden el campo sin cambiar el default:
driftcambia el carácter del movimiento:'snow'(cae con deriva horizontal),'embers'(sube desvaneciéndose),'bubbles'(sube con bamboleo),'warp'(campo de estrellas: nacen a lo ancho del borde superior, caen acelerando en perspectiva y se abren hacia los costados). Los modos direccionales reingresan las partículas por el borde opuesto (wrap) en vez de rebotar;'warp'reingresa por el borde superior.'bounce'(default) es el comportamiento original.linksactiva el efecto constellation: líneas entre partículas cercanas (opacidad proporcional a la cercanía) y, conlinkCursor, hacia el cursor. Esto introduce un cálculo entre pares O(N²) opt-in (apagado por default). Mantenécountmoderado (~80–120) y unalinkDistanceacotada al activarlo.
El canvas llena el contenedor — dimensionalo vos con style/className (ej. height: '100vh'); si el contenedor tiene tamaño cero, no se ve nada. En dispositivos touch (sin cursor de hover) las partículas se animan de forma autónoma según el drift configurado, ignorando el puntero.
import { ParticleField } from '@fethabo/animated-ui'
// Constellation clásico
<div style={{ height: '100vh' }}>
<ParticleField count={100} color="#22d3ee" links linkDistance={120} />
</div>
// Nieve cayendo
<div style={{ height: '100vh' }}>
<ParticleField count={120} drift="snow" cursorInteraction="repel" />
</div>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| count | number | 60 | Número de partículas. |
| speed | number | 0.4 | Rango de la velocidad inicial aleatoria en px/frame. |
| radius | number | 2 | Radio de cada partícula en px. |
| color | string | #7c3aed | Color de las partículas (cualquier color CSS). |
| cursorInteraction | 'repel' \| 'attract' \| 'none' | 'repel' | Reacción al cursor dentro del radio de influencia. |
| cursorRadius | number | 120 | Radio de influencia del cursor en px. |
| drift | 'bounce' \| 'snow' \| 'embers' \| 'bubbles' \| 'warp' | 'bounce' | Modo de deriva del movimiento. Los modos direccionales hacen wrap por el borde opuesto; 'warp' es un campo de estrellas que nace en el borde superior y reingresa por arriba. |
| links | boolean | false | Dibuja líneas entre partículas cercanas (constellation). Opt-in O(N²). |
| linkDistance | number | 120 | Distancia máxima en px para conectar dos partículas. |
| linkColor | string | — | Color de las líneas. Default: deriva del color de partícula. |
| linkWidth | number | 1 | Grosor de las líneas en px. |
| linkCursor | boolean | true | Conecta también las partículas cercanas al cursor con él (cuando links está activo). |
| respectReducedMotion | boolean | true | Con reduce, detiene el RAF y muestra el estado inicial estático (con las líneas dibujadas una vez si links está activo). |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
Performance: con
links={false}(default) no se ejecuta ningún cálculo entre pares y el costo permanece O(N). Activarlinksintroduce un doble loop O(N²) por frame (acotado con un descarte temprano por bounding box antes de la raíz cuadrada). Es tolerable paracounttípico de fondos; concountalto, reducílinkDistanceo elcount.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-particle-color | #7c3aed | Color de las partículas. Un override por cascada prevalece sobre la prop color. |
| --aui-particle-radius | 2px | Radio de cada partícula. |
| --aui-particle-link-color | = color | Color de las líneas de conexión. Prevalece sobre la prop linkColor. |
| --aui-particle-link-width | 1px | Grosor de las líneas de conexión. |
| --aui-particle-link-distance | 120px | Distancia máxima para conectar partículas. |
ImageDissolve
Transiciona entre dos imágenes con dithering ordered (matriz Bayer 8×8): al cambiar la prop src, la nueva imagen se materializa píxel a píxel desde los thresholds Bayer más bajos a los más altos, sobre un <canvas> superpuesto. Reutiliza la misma matriz Bayer que el behavior reveal de PixelBackground. SSR-safe: durante el render solo emite el <img> con su alt; el canvas y la animación arrancan tras la hidratación. Con prefers-reduced-motion el src se swapea al instante, sin dithering.
Prerequisito de CORS: el efecto lee píxeles con
getImageData, que falla sobre un canvas "tainted". La imagen debe ser same-origin o servir headers CORS (Access-Control-Allow-Origin). Ante una imagen cross-origin sin CORS,ImageDissolvedegrada mostrando la imagen destino directamente, sin animación y sin lanzar errores. Para mejor fluidez, escalá tus imágenes al tamaño renderizado: el canvas trabaja en píxeles CSS, no al tamaño natural de la imagen.
import { ImageDissolve } from '@fethabo/animated-ui'
const [src, setSrc] = useState('/foto-a.jpg')
<div style={{ width: 600 }}>
<ImageDissolve src={src} alt="Galería" duration={1000} />
</div>
<button onClick={() => setSrc('/foto-b.jpg')}>Cambiar</button>| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| src | string | — | URL de la imagen. Cambiarla dispara la transición dithered. |
| alt | string | — | Texto alternativo (requerido); aplicado al <img> en todo momento. |
| duration | number | 800 | Duración de la transición en ms. |
| respectReducedMotion | boolean | true | Con reduce, swapea el src al instante sin animar. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
También acepta cualquier otra prop HTML válida de <div>.
StickyScenes
Secciones sticky que transicionan entre "escenas" durante el scroll. El contenedor exterior mide 100dvh + nScenes × sceneDuration; el inner wrapper es position: sticky; top: 0; height: 100dvh, así queda fijo mientras se scrollea el rango. El progreso se descompone en escena activa + progreso dentro de ella y se escribe como --aui-scene-index y --aui-scene-progress directamente sobre el inner wrapper — sin React state en el hot path: scrollear no re-renderiza nada (mismo principio que ParallaxLayers). Con prefers-reduced-motion el tracking de scroll sigue activo, pero las transitions de las escenas se anulan (cada escena aparece de inmediato).
Las escenas se declaran con StickyScenes.Scene. Cada una recibe data-aui-active="true" cuando es la escena en curso, y por defecto se apilan (position: absolute; inset: 0): el consumer engancha ahí sus propias transitions CSS, y puede usar --aui-scene-progress con calc() para efectos interpolados. La primera escena está activa al inicio.
import { StickyScenes } from '@fethabo/animated-ui'
<StickyScenes sceneDuration={800}>
<StickyScenes.Scene className="scene">
<h1>Primera escena</h1>
</StickyScenes.Scene>
<StickyScenes.Scene className="scene">
{/* interpola con el progreso dentro de la escena */}
<h1 style={{ opacity: 'var(--aui-scene-progress, 0)' }}>Segunda escena</h1>
</StickyScenes.Scene>
</StickyScenes>/* El consumer activa sus transiciones via data-aui-active. */
.scene { opacity: 0; transition: opacity 0.5s ease; }
.scene[data-aui-active] { opacity: 1; }| Prop | Tipo | Default | Descripción |
| --- | --- | --- | --- |
| sceneDuration | number | 600 | Píxeles de scroll dedicados a cada escena antes de transicionar. |
| respectReducedMotion | boolean | true | Con reduce, mantiene el scroll activo pero anula las transitions de las escenas. |
| children | ReactNode | — | Escenas declaradas con StickyScenes.Scene. |
| className | string | — | Clases adicionales para el elemento root. |
| style | CSSProperties | — | Estilos inline adicionales para el elemento root. |
StickyScenes.Scene acepta children, className, style y cualquier prop HTML válida de <div>.
CSS Custom Properties
| Variable | Default | Descripción |
| --- | --- | --- |
| --aui-scene-index | 0 | Índice entero de la escena activa. Variable de runtime escrita por el motor. |
| --aui-scene-progress | 0 | Progreso [0, 1] dentro de la escena activa, usable con calc(). Variable de runtime. |
Ambas son escritas por el componente en cada frame; no las setees a mano.
StackedCards
Apila sus hijos directos durante el scroll: cada card se envuelve en un wrapper position: sticky que se fija a offsetTop y se va apilando sobre la anterior (la más reciente queda arriba). Las cards tapadas se encogen y/o oscurecen según cuántas tienen encima, creando profundidad. El apilado físico lo da el sticky nativo (el navegador hace el pin gratis); el scroll-driver (subscribeScroll + RAF) solo calcula la profundidad por card y la escribe como --aui-stack-depth — sin React state en el hot path. El tracking corre solo cuando el contenedor está cerca del viewport (vía useInView).
Recorrido: cada wrapper reserva cardTravel px de scroll (también es su altura). Funciona mejor con cards de altura similar; con alturas muy dispares el recorrido reservado puede no calzar perfecto. Con prefers-reduced-motion el tracking se apaga y las cards quedan en un layout sticky estático y legible.
import { StackedCards } from '@fethabo/animated-ui'
<StackedCards offsetTop={80} scaleStep={0.05} opacityStep={0.1} cardTravel={500}>
<div className="card">