AgentStack
SKILL verified MIT Self-run

Motion Scale

skill-dylantarre-design-system-skills-motion-scale · by dylantarre

Generates animation duration, easing curves, and delay tokens with prefers-reduced-motion support. Use when creating transition timing, animation speed, or motion systems. Outputs CSS, Tailwind, or JSON.

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add skill-dylantarre-design-system-skills-motion-scale

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Motion Scale? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Motion Scale Generator

Overview

Generate consistent animation and transition tokens for duration, easing, and delay. Creates a motion system that feels cohesive and respects user preferences for reduced motion.

When to Use

  • Setting up animation tokens for a new project
  • Standardizing transition timing across components
  • Replacing hardcoded animation values
  • Implementing reduced motion support
  • Building micro-interaction patterns

Quick Reference: Duration Scale

| Token | Value | Use Case | |-------|-------|----------| | instant | 0ms | Immediate, no animation | | fastest | 50ms | Micro-feedback (ripples, highlights) | | fast | 100ms | Hover, focus, small changes | | normal | 200ms | Most transitions, toggles | | slow | 300ms | Modals, drawers, reveals | | slower | 400ms | Page transitions, complex sequences | | slowest | 500ms | Elaborate animations, onboarding |

Quick Reference: Easing Curves

| Token | Curve | Use Case | |-------|-------|----------| | linear | linear | Progress bars, looping | | ease-in | cubic-bezier(0.4, 0, 1, 1) | Exit animations | | ease-out | cubic-bezier(0, 0, 0.2, 1) | Enter animations (most common) | | ease-in-out | cubic-bezier(0.4, 0, 0.2, 1) | Move, resize, continuous | | ease-bounce | cubic-bezier(0.34, 1.56, 0.64, 1) | Playful, attention | | ease-elastic | cubic-bezier(0.68, -0.55, 0.27, 1.55) | Springy, overshoot |

The Process

  1. Choose duration scale: How many steps? (5-7 typical)
  2. Define base duration: 200ms is standard baseline
  3. Select ratio: How durations scale (1.5x or 2x)
  4. Pick easing curves: Standard set + any special curves
  5. Create semantic aliases: Map to component behaviors
  6. Add reduced motion: Respect user preferences
  7. Choose format: CSS, Tailwind, or JSON

Output Formats

CSS Custom Properties:

:root {
  /* ===== Durations ===== */
  --duration-0: 0ms;
  --duration-50: 50ms;
  --duration-100: 100ms;
  --duration-150: 150ms;
  --duration-200: 200ms;
  --duration-300: 300ms;
  --duration-400: 400ms;
  --duration-500: 500ms;
  --duration-700: 700ms;
  --duration-1000: 1000ms;

  /* Named aliases */
  --duration-instant: var(--duration-0);
  --duration-fastest: var(--duration-50);
  --duration-fast: var(--duration-100);
  --duration-normal: var(--duration-200);
  --duration-slow: var(--duration-300);
  --duration-slower: var(--duration-500);
  --duration-slowest: var(--duration-700);

  /* ===== Easings ===== */
  --ease-linear: linear;
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* Expressive easings */
  --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-elastic: cubic-bezier(0.68, -0.55, 0.27, 1.55);
  --ease-snap: cubic-bezier(0.2, 0, 0, 1);

  /* Deceleration/Acceleration (Material Design style) */
  --ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
  --ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);

  /* ===== Delays ===== */
  --delay-none: 0ms;
  --delay-short: 50ms;
  --delay-medium: 100ms;
  --delay-long: 200ms;

  /* Stagger delays for sequential animations */
  --delay-stagger-1: 0ms;
  --delay-stagger-2: 50ms;
  --delay-stagger-3: 100ms;
  --delay-stagger-4: 150ms;
  --delay-stagger-5: 200ms;

  /* ===== Semantic Transitions ===== */
  --transition-colors: color, background-color, border-color, fill, stroke;
  --transition-opacity: opacity;
  --transition-transform: transform;
  --transition-shadow: box-shadow;
  --transition-all: all;

  /* Component transitions */
  --transition-hover: var(--duration-fast) var(--ease-out);
  --transition-focus: var(--duration-fast) var(--ease-out);
  --transition-active: var(--duration-fastest) var(--ease-out);
  --transition-enter: var(--duration-normal) var(--ease-out);
  --transition-exit: var(--duration-fast) var(--ease-in);
  --transition-move: var(--duration-normal) var(--ease-in-out);
  --transition-expand: var(--duration-slow) var(--ease-out);
  --transition-collapse: var(--duration-normal) var(--ease-in);
}

/* ===== Reduced Motion ===== */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-50: 0ms;
    --duration-100: 0ms;
    --duration-150: 0ms;
    --duration-200: 0ms;
    --duration-300: 0ms;
    --duration-400: 0ms;
    --duration-500: 0ms;
    --duration-700: 0ms;
    --duration-1000: 0ms;
  }
}

/* Alternative: Keep minimal motion */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fastest: 0ms;
    --duration-fast: 0ms;
    --duration-normal: 50ms;  /* Minimal feedback */
    --duration-slow: 50ms;
    --duration-slower: 100ms;
    --duration-slowest: 100ms;

    /* Disable expressive easings */
    --ease-bounce: var(--ease-out);
    --ease-elastic: var(--ease-out);
  }
}

Tailwind Config:

module.exports = {
  theme: {
    extend: {
      transitionDuration: {
        '0': '0ms',
        '50': '50ms',
        '100': '100ms',
        '150': '150ms',
        '200': '200ms',
        '300': '300ms',
        '400': '400ms',
        '500': '500ms',
        '700': '700ms',
        '1000': '1000ms',
      },
      transitionTimingFunction: {
        'in': 'cubic-bezier(0.4, 0, 1, 1)',
        'out': 'cubic-bezier(0, 0, 0.2, 1)',
        'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
        'bounce': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
        'elastic': 'cubic-bezier(0.68, -0.55, 0.27, 1.55)',
        'snap': 'cubic-bezier(0.2, 0, 0, 1)',
      },
      transitionDelay: {
        '0': '0ms',
        '50': '50ms',
        '100': '100ms',
        '150': '150ms',
        '200': '200ms',
      },
      animation: {
        'fade-in': 'fade-in 200ms ease-out',
        'fade-out': 'fade-out 150ms ease-in',
        'slide-up': 'slide-up 200ms ease-out',
        'slide-down': 'slide-down 200ms ease-out',
        'scale-in': 'scale-in 200ms ease-out',
        'spin-slow': 'spin 2s linear infinite',
      },
      keyframes: {
        'fade-in': {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        'fade-out': {
          '0%': { opacity: '1' },
          '100%': { opacity: '0' },
        },
        'slide-up': {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        'slide-down': {
          '0%': { transform: 'translateY(-10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        'scale-in': {
          '0%': { transform: 'scale(0.95)', opacity: '0' },
          '100%': { transform: 'scale(1)', opacity: '1' },
        },
      },
    },
  },
};

JSON Tokens:

{
  "motion": {
    "duration": {
      "0": { "value": "0ms", "type": "duration" },
      "50": { "value": "50ms", "type": "duration" },
      "100": { "value": "100ms", "type": "duration" },
      "200": { "value": "200ms", "type": "duration" },
      "300": { "value": "300ms", "type": "duration" },
      "500": { "value": "500ms", "type": "duration" }
    },
    "easing": {
      "linear": { "value": "linear", "type": "cubicBezier" },
      "ease-in": { "value": "cubic-bezier(0.4, 0, 1, 1)", "type": "cubicBezier" },
      "ease-out": { "value": "cubic-bezier(0, 0, 0.2, 1)", "type": "cubicBezier" },
      "ease-in-out": { "value": "cubic-bezier(0.4, 0, 0.2, 1)", "type": "cubicBezier" },
      "bounce": { "value": "cubic-bezier(0.34, 1.56, 0.64, 1)", "type": "cubicBezier" }
    },
    "delay": {
      "none": { "value": "0ms", "type": "duration" },
      "short": { "value": "50ms", "type": "duration" },
      "medium": { "value": "100ms", "type": "duration" }
    }
  }
}

Easing Curve Reference

Standard Curves

ease-out (enter/appear):
    ●━━━━━━━━━━━━━━━━━━●
    Fast start, gentle stop
    Best for: Elements entering view

ease-in (exit/leave):
    ●━━━━━━━━━━━━━━━━━━●
    Gentle start, fast end
    Best for: Elements leaving view

ease-in-out (move/resize):
    ●━━━━━━━━━━━━━━━━━━●
    Gentle start and end
    Best for: Elements moving position

linear (progress):
    ●━━━━━━━━━━━━━━━━━━●
    Constant speed
    Best for: Progress bars, spinners

Expressive Curves

bounce (playful):
    ●━━━━━━━━╮
            ╰━━━●
    Overshoots then settles
    Best for: Notifications, confirmations

elastic (springy):
    ●━━━╮   ╭━╮
        ╰━━━╯ ╰●
    Oscillates before settling
    Best for: Attention, drag-and-drop

snap (decisive):
    ●━━━━━━━━━━━━━━━━●
    Very fast deceleration
    Best for: Snapping, selections

Custom Curve Creation

/*
  cubic-bezier(x1, y1, x2, y2)

  x1, y1 = First control point (affects start)
  x2, y2 = Second control point (affects end)

  y values > 1 = overshoot
  y values < 0 = anticipation
*/

/* Dramatic entrance */
--ease-dramatic: cubic-bezier(0.2, 0.8, 0.2, 1);

/* Snappy with slight overshoot */
--ease-snappy: cubic-bezier(0.2, 1.2, 0.3, 1);

/* Gentle float */
--ease-float: cubic-bezier(0.4, 0.2, 0.2, 1);

Component Patterns

Button Hover

.button {
  transition:
    background-color var(--transition-hover),
    transform var(--transition-active);
}

.button:hover {
  background-color: var(--color-primary-600);
}

.button:active {
  transform: scale(0.98);
}

Modal Enter/Exit

.modal-backdrop {
  transition: opacity var(--duration-normal) var(--ease-out);
}

.modal-content {
  transition:
    opacity var(--duration-normal) var(--ease-out),
    transform var(--duration-normal) var(--ease-out);
}

/* Enter */
.modal-backdrop.entering { opacity: 0; }
.modal-backdrop.entered { opacity: 1; }
.modal-content.entering { opacity: 0; transform: scale(0.95) translateY(10px); }
.modal-content.entered { opacity: 1; transform: scale(1) translateY(0); }

/* Exit (faster) */
.modal-backdrop.exiting {
  opacity: 0;
  transition-duration: var(--duration-fast);
}
.modal-content.exiting {
  opacity: 0;
  transform: scale(0.95);
  transition-duration: var(--duration-fast);
}

Dropdown Menu

.dropdown-menu {
  transform-origin: top;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

.dropdown-menu[data-state="closed"] {
  opacity: 0;
  transform: scaleY(0.9);
  pointer-events: none;
}

.dropdown-menu[data-state="open"] {
  opacity: 1;
  transform: scaleY(1);
}

Staggered List Animation

.list-item {
  opacity: 0;
  transform: translateY(10px);
  animation: slide-up var(--duration-normal) var(--ease-out) forwards;
}

.list-item:nth-child(1) { animation-delay: var(--delay-stagger-1); }
.list-item:nth-child(2) { animation-delay: var(--delay-stagger-2); }
.list-item:nth-child(3) { animation-delay: var(--delay-stagger-3); }
.list-item:nth-child(4) { animation-delay: var(--delay-stagger-4); }
.list-item:nth-child(5) { animation-delay: var(--delay-stagger-5); }

@keyframes slide-up {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Loading Spinner

.spinner {
  animation: spin var(--duration-1000) var(--ease-linear) infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Pulsing loader */
.pulse {
  animation: pulse var(--duration-700) var(--ease-in-out) infinite alternate;
}

@keyframes pulse {
  0% { opacity: 0.4; transform: scale(0.95); }
  100% { opacity: 1; transform: scale(1); }
}

Skeleton Loading

.skeleton {
  background: linear-gradient(
    90deg,
    var(--color-gray-200) 0%,
    var(--color-gray-100) 50%,
    var(--color-gray-200) 100%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s var(--ease-in-out) infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Reduced Motion

Strategy 1: Remove All Motion

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Strategy 2: Reduce, Don't Remove

@media (prefers-reduced-motion: reduce) {
  :root {
    /* Keep brief transitions for feedback */
    --duration-fast: 0ms;
    --duration-normal: 50ms;
    --duration-slow: 100ms;

    /* Simplify easings */
    --ease-bounce: var(--ease-out);
    --ease-elastic: var(--ease-out);
  }

  /* Disable parallax, auto-play, infinite loops */
  .parallax { transform: none !important; }
  .auto-animate { animation: none !important; }
}

Strategy 3: Provide Alternatives

/* Full motion */
.card-enter {
  animation: slide-up var(--duration-normal) var(--ease-out);
}

/* Reduced: fade only, no movement */
@media (prefers-reduced-motion: reduce) {
  .card-enter {
    animation: fade-in var(--duration-fast) var(--ease-out);
  }
}

Duration Guidelines

| Duration | Feel | Use For | |----------|------|---------| | 0-50ms | Instant | Ripples, micro-feedback | | 50-100ms | Snappy | Hover states, focus, toggles | | 100-200ms | Quick | Most UI transitions | | 200-300ms | Smooth | Modals, panels, reveals | | 300-500ms | Deliberate | Page transitions, onboarding | | 500ms+ | Slow | Complex sequences, emphasis |

Rule of thumb:

  • Smaller elements = faster (50-150ms)
  • Larger elements = slower (200-400ms)
  • User-triggered = fast (100-200ms)
  • System-triggered = can be slower (200-400ms)

Testing Checklist

  • [ ] Transitions feel smooth, not jarring
  • [ ] Hover/focus feedback is immediate (<150ms)
  • [ ] Modals/drawers don't feel sluggish
  • [ ] Animations enhance, don't block interaction
  • [ ] prefers-reduced-motion is respected
  • [ ] No layout shift during animations
  • [ ] Animations work at 60fps
  • [ ] Consistent timing across similar interactions

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.