opção
LarLar Skill Ciência de dados e ML motion-foundations

motion-foundations

affaan-m/ECC affaan-m/ECC

Tokens de movimento, predefinições de mola, regras de desempenho, adaptação a dispositivos, garantia de acessibilidade e segurança de SSR para React/Next.js usando o motion/react. Camada de base — todas as outras habilidades relacionadas a movimento dependem dela.

...Expandir tudo
51
Tempo atualizado 29 de Julho de 2026

Fundamentos do sistema de movimento

A camada base do sistema de movimento. Define todos os valores, restrições e regras que as habilidades posteriores (motion-patterns, motion-advanced) herdam. Carregue essa habilidade antes de iniciar qualquer trabalho de animação.

Quando ativar

  • Ao iniciar qualquer componente animado do zero
  • Configurar tokens, predefinições de mola ou valores de suavização
  • Implementar prefers-reduced-motion suporte
  • Depurar incompatibilidades de hidratação nos estados iniciais da animação
  • Avaliar se uma animação deve ou não existir

Resultados

Esta habilidade produz:

  • Um objeto compartilhado motionTokens (duração, efeito de aceleração/desaceleração, distância, escala)
  • Um springs mapa de predefinições compartilhado (5 configurações nomeadas)
  • Um shouldAnimate() gate usado por todos os componentes
  • Padrões de animação em conformidade com as diretrizes de acessibilidade por meio de useReducedMotion
  • estados iniciais seguros para SSR, sem avisos de hidratação

Princípios

O movimento deve cumprir pelo menos um dos seguintes requisitos ou deve ser removido:

  • Chamar a atenção
  • Comunicar o estado
  • Preservar a continuidade espacial

A capacidade de resposta sempre prevalece sobre a suavidade. Uma animação a 60 fps que cause atraso na resposta é pior do que nenhuma animação.

Regras

Essas regras são inegociáveis. Elas se aplicam a todos os componentes do sistema.

  1. Use apenas `motion/react`. Nunca importe de framer-motion. Nunca misture os dois na mesma árvore.
  2. initial deve corresponder à saída do servidor. Se o servidor renderizar opacity: 1, o initial prop também deve ser opacity: 1. Sem exceções.
  3. O movimento reduzido se sobrepõe a tudo. Quando useReducedMotion() retornar true ou prefersReduced estiver true, todas as transformações são desativadas. Fades apenas de opacidade com duração ≤ 0,2 s são a única alternativa permitida.
  4. Nunca anime propriedades de layout. width, height, top, left, margin, padding estão proibidas em animate. Use transform e opacity apenas.
  5. Todos os valores de tokens vêm de motionTokens. Durações e efeitos de aceleração/desaceleração codificados diretamente nos arquivos de componentes são proibidos.
  6. Todas as configurações do Spring vêm do mapa springs. Valores stiffness/damping são proibidos.
  7. "use client" é obrigatório em todos os arquivos que importam de motion/react.
  8. Nunca leia window ou navigator no nível do módulo. Sempre proteja com typeof window !== "undefined".

Orientação para a tomada de decisão

Escolhendo uma duração

Escolhendo uma mola

Quando desativar totalmente a animação

Desativar (fazer shouldAnimate() retornar false) quando:

  • prefersReduced for true
  • isLowEnd é true e a animação não for essencial
  • O elemento está fora da tela e nunca entrará na área de visualização
  • A animação é puramente decorativa, sem finalidade de experiência do usuário

Conceitos fundamentais

Sistema de tokens

// lib/motion-tokens.ts
export const motionTokens = {
  duration: {
    instant: 0.08,
    fast:    0.18,
    normal:  0.35,
    slow:    0.6,
    crawl:   1.0,
  },
  easing: {
    smooth: [0.22, 1, 0.36, 1],
    sharp:  [0.4, 0, 0.2, 1],
    bounce: [0.34, 1.56, 0.64, 1],
    linear: [0, 0, 1, 1],
  },
  distance: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 48,
  },
  scale: {
    subtle: 0.98,
    press:  0.95,
    pop:    1.04,
  },
}

export const springs = {
  snappy:  { type: "spring", stiffness: 300, damping: 30 },
  gentle:  { type: "spring", stiffness: 120, damping: 14 },
  bouncy:  { type: "spring", stiffness: 400, damping: 10 },
  instant: { type: "spring", stiffness: 600, damping: 35 },
  release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}

Sinalizadores de tempo de execução

// lib/motion-config.ts
export const motionConfig = {
  isLowEnd() {
    return (
      typeof navigator !== "undefined" &&
      navigator.hardwareConcurrency <= 4
    )
  },

  prefersReduced() {
    return (
      typeof window !== "undefined" &&
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
  },

  shouldAnimate({ essential = false } = {}) {
    if (this.prefersReduced()) return false
    if (!essential && this.isLowEnd()) return false
    return true
  },

  duration() {
    return this.isLowEnd() || this.prefersReduced()
      ? motionTokens.duration.instant
      : motionTokens.duration.normal
  },
}

Acessibilidade

Ordem de prioridade (da mais alta à mais baixa):

  1. prefers-reduced-motion: reduce — desativa todas as transformações, limita as transições de opacidade a ≤ 0,2 s
  2. Detecção de dispositivos de baixo desempenho — reduz a duração e remove animações não essenciais
  3. Preferência de design — todo o restante

O movimento deve ser degradado de forma suave. Ele nunca deve desaparecer abruptamente de uma maneira que cause deslocamento do layout ou confunda a orientação.

// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"

export function useSafeMotion(fullY: number = 16) {
  const reduce = useReducedMotion()
  return {
    initial: { opacity: 0, y: reduce ? 0 : fullY },
    animate: { opacity: 1, y: 0 },
    exit:    { opacity: 0, y: reduce ? 0 : -fullY },
  }
}
/* globals.css */
@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition  { transition: opacity 0.15s; }
  .motion-reduce-transform { transform: none !important; }
}

<div class="motion-safe:animate-fade motion-reduce:opacity-100">div>

Segurança de SSR/hidratação

Regra: o `initial` deve sempre corresponder ao que o servidor renderiza.

// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />

// CORRECT — use AnimatePresence or defer to client mount
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])

div
  initial={{ opacity: mounted ? 0 : 1 }}
  animate={{ opacity: 1 }}
/>

Exemplos de código

De ponta a ponta: tokens + springs + acessibilidade + proteção de SSR

// components/fade-in-card.tsx
"use client"

import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"

interface FadeInCardProps {
  children: React.ReactNode
  delay?: number
}

export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
  // SSR guard — initial must match server output (opacity: 1)
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])

  // Accessibility — disables transform when reduced motion is preferred
  const safeMotion = useSafeMotion(motionTokens.distance.md)

  // Device gate — skip animation on low-end hardware
  if (!motionConfig.shouldAnimate() || !mounted) {
    return <div>{children}div>
  }

  return (
    <motion.div
      initial={safeMotion.initial}
      animate={safeMotion.animate}
      exit={safeMotion.exit}
      transition={{
        ...springs.gentle,
        delay,
      }}
      whileHover={{ scale: motionTokens.scale.pop }}
      whileTap={{ scale: motionTokens.scale.press }}
    >
      {children}
    motion.div>
  )
}

Restrições / Não objetivos

Esta habilidade não abrange:

  • Padrões de componentes de interface do usuário (botão, modal, stagger) → consulte motion-patterns
  • Arrastar, gestos, SVG, animações de texto, hooks personalizados → consulte motion-advanced
  • Animações apenas com CSS ou classes do Tailwind animate-* sem motion/react
  • bibliotecas de animação de terceiros (GSAP, anime.js, etc.)
  • Decisões de design de movimento (quando animar, o que destacar) — isso é uma questão de design, não uma restrição de código

Antipadrões

Habilidades relacionadas

  • motion-patterns — utiliza tokens e molas definidos aqui para criar padrões de botões, modais, escalonamento, transição de página e rolagem. Não redefine nenhum valor.
  • motion-advanced — utiliza tokens e springs definidos aqui para padrões de arrastar, SVG, texto e gestos. Adiciona useAnimate sequências e ganchos personalizados sobre essa base.
Ver no GitHub

Motion Foundations

The base layer of the motion system. Defines every value, constraint, and rule that downstream skills (motion-patterns, motion-advanced) inherit. Load this skill before any animation work begins.

When to Activate

  • Starting any animated component from scratch
  • Setting up tokens, spring presets, or easing values
  • Implementing prefers-reduced-motion support
  • Debugging hydration mismatches from animation initial states
  • Evaluating whether an animation should exist at all

Outputs

This skill produces:

  • A shared motionTokens object (duration, easing, distance, scale)
  • A shared springs preset map (5 named configs)
  • A shouldAnimate() gate used by all components
  • Accessibility-compliant animation defaults via useReducedMotion
  • SSR-safe initial states with zero hydration warnings

Principles

Motion must do at least one of the following or it must be removed:

  • Guide attention
  • Communicate state
  • Preserve spatial continuity

Responsiveness always outranks smoothness. A 60 fps animation that causes input delay is worse than no animation.

Rules

These are non-negotiable. They apply to every component in the system.

  1. Use motion/react only. Never import from framer-motion. Never mix the two in the same tree.
  2. initial must match server output. If the server renders opacity: 1, the initial prop must also be opacity: 1. No exceptions.
  3. Reduced motion overrides everything. When useReducedMotion() returns true or prefersReduced is true, all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback.
  4. Never animate layout properties. width, height, top, left, margin, padding are banned from animate. Use transform and opacity only.
  5. All token values come from motionTokens. Hardcoded durations and easings in component files are forbidden.
  6. All spring configs come from the springs map. Inline stiffness/damping values are forbidden.
  7. "use client" is required on every file that imports from motion/react.
  8. Never read window or navigator at module level. Always guard with typeof window !== "undefined".

Decision Guidance

Choosing a duration

Choosing a spring

When to disable animation entirely

Disable (make shouldAnimate() return false) when:

  • prefersReduced is true
  • isLowEnd is true and the animation is non-essential
  • The element is off-screen and will never enter the viewport
  • The animation is purely decorative with no UX purpose

Core Concepts

Token system

// lib/motion-tokens.ts
export const motionTokens = {
  duration: {
    instant: 0.08,
    fast:    0.18,
    normal:  0.35,
    slow:    0.6,
    crawl:   1.0,
  },
  easing: {
    smooth: [0.22, 1, 0.36, 1],
    sharp:  [0.4, 0, 0.2, 1],
    bounce: [0.34, 1.56, 0.64, 1],
    linear: [0, 0, 1, 1],
  },
  distance: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 48,
  },
  scale: {
    subtle: 0.98,
    press:  0.95,
    pop:    1.04,
  },
}

export const springs = {
  snappy:  { type: "spring", stiffness: 300, damping: 30 },
  gentle:  { type: "spring", stiffness: 120, damping: 14 },
  bouncy:  { type: "spring", stiffness: 400, damping: 10 },
  instant: { type: "spring", stiffness: 600, damping: 35 },
  release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}

Runtime flags

// lib/motion-config.ts
export const motionConfig = {
  isLowEnd() {
    return (
      typeof navigator !== "undefined" &&
      navigator.hardwareConcurrency <= 4
    )
  },

  prefersReduced() {
    return (
      typeof window !== "undefined" &&
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
  },

  shouldAnimate({ essential = false } = {}) {
    if (this.prefersReduced()) return false
    if (!essential && this.isLowEnd()) return false
    return true
  },

  duration() {
    return this.isLowEnd() || this.prefersReduced()
      ? motionTokens.duration.instant
      : motionTokens.duration.normal
  },
}

Accessibility

Priority order (highest to lowest):

  1. prefers-reduced-motion: reduce — disables all transforms, limits opacity transitions to ≤ 0.2s
  2. Low-end device detection — reduces duration, removes non-essential animations
  3. Design preference — everything else

Motion must degrade gracefully. It must never disappear abruptly in a way that causes layout shift or confuses orientation.

// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"

export function useSafeMotion(fullY: number = 16) {
  const reduce = useReducedMotion()
  return {
    initial: { opacity: 0, y: reduce ? 0 : fullY },
    animate: { opacity: 1, y: 0 },
    exit:    { opacity: 0, y: reduce ? 0 : -fullY },
  }
}
/* globals.css */
@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition  { transition: opacity 0.15s; }
  .motion-reduce-transform { transform: none !important; }
}
<!-- Tailwind -->
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>

SSR / hydration safety

Rule: initial must always match what the server renders.

// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />

// CORRECT — use AnimatePresence or defer to client mount
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])

<motion.div
  initial={{ opacity: mounted ? 0 : 1 }}
  animate={{ opacity: 1 }}
/>

Code Examples

End-to-end: tokens + springs + accessibility + SSR guard

// components/fade-in-card.tsx
"use client"

import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"

interface FadeInCardProps {
  children: React.ReactNode
  delay?: number
}

export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
  // SSR guard — initial must match server output (opacity: 1)
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])

  // Accessibility — disables transform when reduced motion is preferred
  const safeMotion = useSafeMotion(motionTokens.distance.md)

  // Device gate — skip animation on low-end hardware
  if (!motionConfig.shouldAnimate() || !mounted) {
    return <div>{children}</div>
  }

  return (
    <motion.div
      initial={safeMotion.initial}
      animate={safeMotion.animate}
      exit={safeMotion.exit}
      transition={{
        ...springs.gentle,
        delay,
      }}
      whileHover={{ scale: motionTokens.scale.pop }}
      whileTap={{ scale: motionTokens.scale.press }}
    >
      {children}
    </motion.div>
  )
}

Constraints / Non-Goals

This skill does not cover:

  • UI component patterns (button, modal, stagger) → see motion-patterns
  • Drag, gestures, SVG, text animations, custom hooks → see motion-advanced
  • CSS-only animations or Tailwind animate-* classes without motion/react
  • Third-party animation libraries (GSAP, anime.js, etc.)
  • Motion design decisions (when to animate, what to emphasize) — that is a design concern, not a code constraint

Anti-Patterns

Related Skills

  • motion-patterns — consumes tokens and springs defined here to build button, modal, stagger, page transition, and scroll patterns. Does not redefine any values.
  • motion-advanced — consumes tokens and springs defined here for drag, SVG, text, and gesture patterns. Adds useAnimate sequences and custom hooks on top of this foundation.

Todos os arquivos

1 arquivos
SKILL.md 9.4k
Ver

Instalar motion-foundations

Baixe e extraia os arquivos de habilidades para o diretório .claude/skills/.

Baixar ZIP

Clone o repositório e copie os arquivos da habilidade para o seu projeto.

git clone https://github.com/affaan-m/ECC/tree/main/skills/motion-foundations # Copy the skill folder to .claude/skills/ or .codex/skills/

Copiar Copiar
Configuração rápida: Copie a pasta da habilidade para .claude/skills/. O Claude detectará e utilizará automaticamente a habilidade
Repositório affaan-m/ECC

Habilidades relacionadas

web-search
Tempo atualizado 29 de Junho de 2026
computer-use
Tempo atualizado 29 de Julho de 2026
webapp-testing
Tempo atualizado 29 de Junho de 2026
lark-base
Tempo atualizado 5 de Julho de 2026
OR