Motion ocurre en dos momentos: la transición de estado
(data-state, los presets de abajo) y la firma del evento
(data-event-*, transitoria y cross-modal). Cada uno puede llevar animación —
uno, el otro o ambos. Todo sale generado desde EidosConfig.motion. Abajo: los 5
presets CSS, la firma del evento, el driver spring (física real) y componentes
reales animando por el sistema.
fade
Opacidad pura. Bajo reduced-motion corre igual (es seguro).
{``}
scale-fade
Escala (var(--motion-scale-enter)) + opacidad, con
transform-origin. Bajo reduced-motion: solo fade.
{``}
slide-fade (side-aware)
Deslizamiento corto + opacidad según data-side (lo emite el motor de
posicionamiento en un Popover real). Bajo reduced-motion: solo fade.
{``}
slide-full (drawer)
Deslizamiento completo (100%) por borde — para drawers / sheets. Bajo reduced-motion: instantáneo.
{``}
collapse
Altura medida (--height, que pondría el componente). Bajo
reduced-motion: instantáneo.
Contenido plegable.
La altura se anima entre 0 y la altura medida del contenido.
El componente (P2) mediría esto y pondría --height.
{``}
firma del evento (transitoria · cross-modal)
El otro momento: el flourish durante el hold de una señal de sema,
genérico por family / intent / event (un
commit asienta igual en todo el sistema). Aquí ves el canal
motion; sema dispara además sonido + háptica desde el
mismo evento. Bajo reduced-motion el motion se silencia, pero sonido/háptica
siguen comunicando — esa es la ventaja cross-modal.
{``}
spring (física real · driver JS)
Un muelle independiente por propiedad (no una cubic-bezier): overshoot y
asentamiento que ninguna curva CSS expresa. Ajusta rigidez / amortiguación y reprodúcelo —
el motor lo corre vía eidos.motion.enter/exit y lo trackea por elemento
(cancelable). Hoy el demo es el consumidor directo; cablearlo a la Presence
de los componentes es el siguiente paso.
{`eidos.motion.register('pop', {
driver: 'spring',
enter: spring({ values: { opacity: [0, 1], scale: [0.4, 1] }, stiffness: ${stiffness}, damping: ${damping} })
})
eidos.motion.enter(el, 'pop', { dom: eidos.dom })`}
presence con spring (end-to-end)
El spring conduciendo un ciclo real de montaje/desmontaje. El panel usa
una Presence de soma (el mismo primitivo que todo overlay) con
motion = eidos.motion: al abrir entra con spring; al cerrar, Presence
retiene el nodo hasta que el spring de salida termina, y entonces lo
desmonta. El wrapper escribe data-animation-style; el engine de motion lee ese
atributo y corre el preset JS. (En el preview en segundo plano el rAF está congelado; en un
browser enfocado el panel rebota al entrar y se encoge al salir.)
{`const presence = new Presence({ dom, open, ref, motion: eidos.motion })
// panel markup: → motion.run runs the spring`}
FLIP (transición de layout)
Transición de layout: el bloque cambia de tamaño y el driver
rect mide el rect antes y después y anima el delta inverso
(translate + scale) vía WAAPI — la técnica FLIP de Framer. El cambio de layout es
instantáneo; la animación lo hace continuo. (Throttled en el preview en segundo plano.)
{`const first = el.getBoundingClientRect()
mutateLayout() // size / position changes
await tick()
eidos.motion.enter(el, 'flip', { dom, sourceRect: first })`}
genie y cía (entradas dramáticas)
Más presets de driver JS al nivel de genie: warp tipo minimizar
(genie, scale + translate + blur), giro 3D (flip-3d,
rotateY), péndulo (swing) y muelle elástico con overshoot real
(elastic). Pulsa Play y el motor corre cada entrada vía
eidos.motion.enter; asientan en su sitio. (En el preview en segundo plano el
rAF está congelado; enfócalo en un browser para verlas.)
{`eidos.motion.register('genie', {
driver: 'waapi',
enter: waapi([{ transform: 'translateY(70px) scale(0.18)', opacity: 0, filter: 'blur(6px)' },
{ transform: 'none', opacity: 1, filter: 'blur(0)' }],
{ duration: 650, easing: 'cubic-bezier(0.22, 1, 0.36, 1)' })
})
eidos.motion.enter(boxEl, 'genie') // ▶ play`}
stagger (Material 3 · list choreography)
Una lista entra en secuencia, sin orquestación JS: el contenedor pone el
ritmo (--motion-stagger-each) y cada ítem su índice
(--motion-stagger-index); el preset retrasa su entrada por
índice × ritmo. El fill backwards mantiene cada ítem en su
estado inicial hasta su turno. Compone con cualquier preset CSS — aquí
slide-fade.
{`-
{#each items as item, i}
- … {/each}
shared-axis · fade-through (coreografía)
Presets de transición Material: shared-axis-x/y (movimiento direccional
coordinado para cambios espaciales / navegacionales) y fade-through (para
contenido sin relación — el entrante escala desde 0.92 + fade, el saliente solo opacidad).
Bajo reduced-motion: solo fade. El container-transform ya está
cubierto por el driver rect (FLIP).
{``}
productive ⇄ expressive (rigor de tokens · Carbon)
El mismo preset (scale-fade), dos personalidades. El set
productive (default) usa el easing eficiente; expressive
remapea las curvas a las emphasized de Carbon vía un scope
[data-motion-set='expressive'] — sin tocar el componente. Las curvas son
tokens: el theme retinta lo perceptualmente fijo, no inventa animaciones. Cambia el set
y abre/cierra para sentir la diferencia.
{`
`}
Componentes reales
Estos overlays animan a través del sistema de presets de arriba —
Dialog → scale-fade + fade, Popover → slide-fade,
Drawer → slide-full. Sus animaciones bespoke se migraron al registro y cada
uno conserva su timing exacto vía --motion-duration/ease por componente. La
Presence de soma espera la animación del preset antes de desmontar. Ábrelos
para verlo (tema + reduced-motion aplican, proyectados a documentElement).
Desliza desde el lado según data-side.
Entra deslizando completo desde el borde.