FORMA Design System / Motion Audit

MOTION
AUDIT

Generado: 05 Jun 2026 Método: /design-motion-principles · Emil primary · Jakub secondary · Jhey selectivo Scope: CSS · JS · tokens.css · shared.css · hero.css · sidebar.css · cards.css

Motion Health Score

# Dimensión Score Hallazgo principal
1 Token Architecture 4 ✓ --dur-fast/base/slow + --ease-expo/quart/exit — tokens completos, bien nombrados, usados consistentemente. Único gap: --easing-standard referenciado en cards.css pero no definido.
2 Accessibility 4 ✓ Global reset en shared.css (animation + transition + iteration-count). JS del cubo lee la media query antes del RAF. Hero SVG y scroll cue cubiertos individualmente. Cobertura exemplar.
3 Duration Calibration 2 Secuencia de entrance en páginas internas llega a 1600ms. Sidebar stagger: 1120ms. Nav overlay: 500ms. Todas las duraciones son correctas para first-load marketing; son altas para una tool usada diariamente.
4 Easing Integrity 2 --easing-standard no definido → arrow transitions usan browser ease. Logo frame draw usa cubic-bezier(0.4,0,0.2,1) (ease-in-out) — debería ser ease-out. Dos instancias, ambas visibles.
5 State Feedback 3 ✓ Hover transitions en botones, inputs y sidebar correctos. ✗ Sin scale(0.97) en :active de botones. Bento cell hover colors sin transition. Toasts con feedback correcto.
Total 15/20 Good — 5 issues Important abiertos · base técnica sólida · durations y easing a calibrar
Motion health score 15 / 20

Executive Summary

Conteo por Severidad

0
P0 Critical
5
P1 Important
0
P2 Minor
6
Opportunities

Issues Abiertos (5)

  • [P1] --easing-standard indefinido en cards.css — arrow transitions usan browser ease en lugar de --ease-expo
  • [P1] Logo frame draw con cubic-bezier(0.4,0,0.2,1) (ease-in-out) — debe ser ease-out para stroke natural
  • [P1] Nav overlay: transition: transform 500ms — demasiado lento para navegación diaria. Target: 240ms
  • [P1] Entrance sequences en páginas internas: 1600ms total hero + 1120ms sidebar — calibrado para landing, no para tool
  • [P1] Botones sin scale(0.97) en :active — feedback táctil ausente en el elemento interactivo más frecuente

Emil Kowalski — Restraint & Speed

Lens principal. Pregunta central: "¿Deberían estas animaciones existir en su forma actual?" FORMA se visita diariamente — la frecuencia cambia el estándar de duración.

Contexto de evaluación

FORMA es una tool de documentación visitada frecuentemente. Un engineer que navega entre Components, Guidelines y Tokens varias veces por sesión. La regla de frecuencia de Emil: interacciones diarias → animaciones sutiles bajo 300ms, sin delight en cada tap. FORMA aplica las duraciones correctas para una landing page (700ms, 1000ms de delay, stagger visible) en páginas que se usan como referencia.

P1
--easing-standard indefinido — card article arrow usa browser ease
Ubicación: cards.css:451
Lens: Emil — easing custom es esencial

La regla .card-article__arrow usa var(--easing-standard) para la transición de transform y opacity. Este token no existe en tokens.css. Los tokens definidos son --ease-expo, --ease-quart, y --ease-exit.

Una propiedad custom CSS indefinida resuelve al valor initial de la propiedad — para transition-timing-function, ese valor es el browser built-in ease (curva simétrica que acelera en el medio). El arrow aparece con una curva que no coincide con ningún otro elemento interactivo del sistema.

Fix: cards.css:451 — cambiar var(--easing-standard) por var(--ease-expo). Dos instancias en la misma declaración.

/polish
P1
Logo frame draw usa ease-in-out — el stroke debería decelerar, no acelerar
Ubicación: index.html:876
Lens: Emil — easing es la parte más importante

La animación del rectángulo SVG usa cubic-bezier(0.4, 0, 0.2, 1), que es la curva estándar ease-in-out de Material Design. Para un stroke-dashoffset draw, esto significa: el trazo empieza lentamente, acelera en el medio, y desacelera al final. La fase de aceleración se siente mecánica — los trazos físicos no se aceleran en el medio, deceleran al llegar a su destino.

Los elementos del letterform F que siguen correctamente usan cubic-bezier(0.16, 1, 0.3, 1) (expo-out). El frame draw debería ser consistente y además es el primer elemento en animar — define la expectativa del resto.

Fix: Cambiar cubic-bezier(0.4, 0, 0.2, 1) a cubic-bezier(0.16, 1, 0.3, 1) en #hero-mark rect:nth-of-type(1). Una línea.

/polish
P1
Nav overlay a 500ms — demasiado lento para navegación diaria
Ubicación: shared.css:186, 196
Lens: Emil — interacciones diarias bajo 300ms

El panel del nav overlay usa transition: transform var(--dur-base) var(--ease-expo) donde --dur-base: 500ms. El scrim usa la misma duración para opacity. 500ms es medio segundo — perceptiblemente lento cuando el usuario lo trigger en cada sesión para navegar entre secciones en mobile.

--dur-base es la duración correcta para elementos de first-load como el hero stat-row o los sidebar groups. Para un panel de navegación que se abre y cierra múltiples veces por sesión, la duración target es 200–240ms.

Fix: En shared.css:196 (panel) y shared.css:186 (scrim), cambiar var(--dur-base) por var(--dur-fast) (240ms). La easing expo-out sigue siendo apropiada — solo más rápida.

/adapt
P1
Secuencias de entrance en páginas internas: 1600ms hero + 1120ms sidebar
Ubicación: hero.css:160–175 · sidebar.css:30–44
Lens: Emil — las animations de page load se ven muchas veces

Las páginas internas (components.html, guidelines.html) usan hero.css con stagger hasta 1000ms de delay + animación de 600ms = 1600ms total. Los sidebar groups staggerean hasta 400ms de delay + 720ms = 1120ms. En index.html esta duración es justificada — primera impresión, momento cinematográfico. En páginas de referencia que el usuario carga múltiples veces por sesión, es latencia percibida en cada navegación.

El cubo de index.html tiene su propio sistema de entrance (logos, wordmark, tagline) que no se reutiliza en páginas internas — bien. El problema es que hero.css aplica la misma lógica de "primeras impresiones" a pages de tool.

Fix: En hero.css, comprimir stagger delays a máximo 150ms total (0 / 50 / 100 / 150ms en lugar de 0 / 560 / 680 / 840ms). En sidebar.css, remover stagger o comprimir a 80ms max total. Durations al 300ms máximo. Considerar una clase .hero--tool con timings reducidos para páginas internas.

/adapt
P1
Botones sin :active scale — feedback táctil ausente
Ubicación: components.css:345
Lens: Emil — tip #1: scale buttons on :active

El selector .btn transiciona background-color, border-color, color, y box-shadow. No incluye transform. Sin un scale(0.97) en :active, los botones no dan feedback inmediato de que el click registró — la única confirmación es visual (color) y llega después del procesamiento de la acción.

Los botones de FORMA se usan en cada demo de components (copy token, acciones), en el header, y en los CTAs de artículos. Es el elemento interactivo con más frecuencia de uso en todo el sistema.

Fix: Agregar transform a la lista de transición de .btn, luego .btn:active { transform: scale(0.97); }. Una regla, todos los botones heredan el feedback.

/polish

Lo que funciona bien:

Cube rAF pausado con IntersectionObserver: El loop 3D se cancela con cancelAnimationFrame cuando el hero sale del viewport. No hay GPU waste en tabs background ni durante el scroll normal. Técnicamente correcto y frecuentemente omitido. — index.html:1726
Toast a 240ms + expo-out: El copy toast usa var(--dur-fast) — exactamente calibrado para un elemento que se trigger con alta frecuencia. No interrumpe el flujo. — shared.css:343
Scroll reveal con unobserve: IntersectionObserver agrega .in y luego deja de observar el elemento. Los elementos no se re-animan al hacer scroll hacia atrás. Patrón correcto. — components.html:962
Transiciones en compositor-only properties: .btn transiciona background-color, color, box-shadow — ninguna propiedad de layout. Sin layout thrash, sin reflow. — components.css:345

Jakub Krehel — Production Polish

Lens secundario. Pregunta: "¿Está esto lo suficientemente pulido para producción?" Después de que Emil determine que algo debería animar, Jakub define cómo.

Contexto de evaluación

FORMA tiene la arquitectura de motion correcta — tokens, fills, reducción de willChange. La oportunidad de Jakub es en los detalles de exit vocabulary y en que --ease-exit está definido pero nunca se usa. Las exits son tan prominentes como los enters — el overlay se cierra con la misma curva que abre.

Logo animation es un momento genuino de diseño: La secuencia frame → corner marks → letterform tiene timing correcto per-element (220–380ms), animation-fill-mode: both en todos los elementos, y easing expo-out en el letterform. El resultado es una construcción visible del logo — no decoración. — index.html:820
Spacing bars crecen desde el origen correcto: transform-box: fill-box; transform-origin: 0% 50% ancla el scaleX al borde izquierdo. Las barras se extienden hacia la derecha, no desde el centro. Origin-aware — exactamente la sensibilidad de Jakub. — components.css:706
--ease-exit definido en tokens: cubic-bezier(0.4, 0, 1, 1) — ease-in, aceleración hacia la salida. El vocabulario existe. Que esté definido y sin uso es la oportunidad más clara del sistema. — tokens.css:497
Oportunidad — --ease-exit sin usar: El nav overlay cierra con --ease-expo (mismo que abre). Los exits deberían acelerar hacia la salida (--ease-exit), no decelerar. Una línea en shared.css donde el panel hace translateX(-100%) al cerrar.
Oportunidad — toast exit simétrico: El toast entra con opacity + translateY y sale revirtiendo las mismas propiedades. Jakub: las salidas deben ser más sutiles. Un fade simple en 150ms al cerrar (solo opacity) se sentiría más refinado que la entrada-en-reversa actual.

Jhey Tompkins — Experimentation & Delight

Lens selectivo — aplica solo al hero de index.html y a momentos de primer contacto. El sistema correctamente se restraints en páginas de tool.

Contexto de evaluación

El cubo 3D con mouse tracking y la animación del logo son los únicos lugares en FORMA donde la latitud creativa es apropiada — y están bien ejecutados. La oportunidad de Jhey es el bento grid: el landing page que explica un sistema de diseño podría demostrar scroll-driven animation usando scroll-driven animation.

Cubo con lerp factor de 0.08 — momentum suave sin overshoot: offsetX += (targetOffsetX - offsetX) * 0.08 por frame. El número es correcto: suficientemente suave para evitar jitter, suficientemente rápido para sentirse responsive. No usa CSS variables para el update (correcto — actualiza element.style.transform directamente). — index.html:1724
Oportunidad — Scroll-driven reveal del bento grid: Las celdas del bento aparecen instantáneamente. @keyframes + animation-timeline: scroll() podría hacer que cada celda entre en su posición a medida que el usuario scrollea — sin JavaScript, con @media (prefers-reduced-motion: reduce) como guard natural. La landing page sobre un sistema de diseño demostrando su propia filosofía de motion.
Oportunidad — Animated icon swap en el copy button: El click de copy muestra un toast pero el botón no reacciona visualmente hasta que el toast aparece. Un swap copy→check con opacity + scale(0.8→1) + blur(2px→0) daría feedback inmediato dentro del botón, antes de que el toast llegue.
Oportunidad — @property para tokens de color animables: Definir @property --color-blue-400 como tipo <color> habilitaría transiciones suaves entre valores de tokens en cambios de tema. Preparación para un dark mode toggle animado.

Acciones Recomendadas

5 issues P1. Ordenados por impacto visible / esfuerzo de fix:

1
/polish

[P1 · 2min] En cards.css:451, reemplazar var(--easing-standard) por var(--ease-expo). Fix inmediato al único token indefinido del sistema.

2
/polish

[P1 · 2min] En index.html:876, cambiar el easing del frame draw de cubic-bezier(0.4,0,0.2,1) a cubic-bezier(0.16,1,0.3,1). Consistente con los elementos F subsecuentes.

3
/polish

[P1 · 5min] Agregar transform a la transición de .btn + regla .btn:active { transform: scale(0.97); } en components.css:345. Feedback táctil en todos los botones.

4
/adapt

[P1 · 10min] En shared.css:186,196, cambiar nav overlay de var(--dur-base) (500ms) a var(--dur-fast) (240ms). Navegación diaria al ritmo correcto.

5
/adapt

[P1 · 20min] Comprimir entrance sequences de páginas internas: hero.css delays al 150ms total, sidebar.css stagger al 80ms total, durations al 300ms máximo. Crear clase .hero--tool si se quiere mantener el timing cinematográfico solo en index.html.

Reporte generado con /design-motion-principles · Emil primary · Jakub secondary · Jhey selectivo.
Ver el reporte técnico completo con demos en motion-audits/forma-2026-06-05.html.