---
description: Enforce animation accessibility standards per WCAG 2.2.2 Pause Stop Hide, 2.3.1 Three Flashes or Below Threshold, and 2.3.3 Animation from Interactions requirements
globs: *.vue, *.jsx, *.tsx, *.html, *.php, *.js, *.ts, *.css, *.scss, *.sass, *.less
alwaysApply: false
---

# Animation Accessibility Standards

Ensures animations follow WCAG compliance and provide inclusive motion design for users with different accessibility needs including photosensitivity, motion sickness, and cognitive impairments.

<rule>
name: animation_accessibility_standards
description: Enforce animation accessibility standards per WCAG 2.2.2 Pause Stop Hide, 2.3.1 Three Flashes or Below Threshold, and 2.3.3 Animation from Interactions requirements
filters:
  - type: file_extension
    pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts|css|scss|sass|less)$"

actions:
  - type: enforce
    conditions:
      # Missing prefers-reduced-motion media query for animations
      - pattern: "(animation|transition|transform|@keyframes)"
        pattern_negate: "@media\\s*\\(prefers-reduced-motion:\\s*reduce\\)"
        message: "Animations should include prefers-reduced-motion: reduce media query to provide safer alternatives for motion-sensitive users."

      # Flashing animations exceeding 3Hz frequency
      - pattern: "animation.*(?:pulse|flash|blink|flicker)"
        pattern_negate: "animation-duration:\\s*[0-9]*\\.?[0-9]+s|animation-duration:\\s*[0-9]*\\.?[0-9]+ms"
        message: "Flashing animations must have duration ensuring frequency is below 3Hz (0.33s) to prevent seizures per WCAG 2.3.1."

      # Rapid color transitions that may trigger photosensitivity
      - pattern: "transition.*color.*[0-9]*\\.?[0-9]+s|transition.*background.*[0-9]*\\.?[0-9]+s"
        pattern_negate: "transition.*color.*[0-9]*\\.?[0-9]+s.*[0-9]*\\.?[0-9]+s|transition.*background.*[0-9]*\\.?[0-9]+s.*[0-9]*\\.?[0-9]+s"
        message: "Color transitions should be slow and smooth to avoid triggering photosensitivity. Use longer durations and easing functions."

      # Large spatial movements without reduced motion alternatives
      - pattern: "transform.*translate\\([^)]*[0-9]{2,}[^)]*\\)|transform.*translate\\([^)]*-[0-9]{2,}[^)]*\\)"
        pattern_negate: "@media\\s*\\(prefers-reduced-motion:\\s*reduce\\)"
        message: "Large spatial movements should have reduced motion alternatives using prefers-reduced-motion media query."

      # Parallax effects without user control
      - pattern: "(parallax|scroll.*jack|scroll.*hijack)"
        pattern_negate: "(prefers-reduced-motion|user.*control|pause.*animation)"
        message: "Parallax and scroll jacking effects should respect user motion preferences and provide controls to pause/disable."

      # Auto-playing animations without pause controls
      - pattern: "animation.*infinite|animation.*loop"
        pattern_negate: "(pause.*control|user.*control|prefers-reduced-motion)"
        message: "Looping animations must provide pause controls and respect prefers-reduced-motion preferences."

      # Unexpected system-triggered animations
      - pattern: "animation.*(?:appear|fade.*in|slide.*in)"
        pattern_negate: "(user.*interaction|click|hover|focus|prefers-reduced-motion)"
        message: "System-triggered animations should be subtle and respect user motion preferences."

      # Missing animation alternatives for essential UI changes
      - pattern: "(?:loading|spinner|progress|status)"
        pattern_negate: "(animation|transition|@keyframes)"
        message: "Essential UI elements like loading indicators should have appropriate animations to communicate state changes."

      # Excessive animation duration that may cause motion sickness
      - pattern: "animation-duration:\\s*[5-9]\\.[0-9]+s|animation-duration:\\s*[0-9]{2,}s"
        message: "Long animation durations may cause motion sickness. Consider shorter durations and provide reduced motion alternatives."

      # Missing focus indicators for animated interactive elements
      - pattern: "(?:button|a|input|select|textarea).*\\{[^}]*animation"
        pattern_negate: "(focus|focus-visible|outline|box-shadow)"
        message: "Animated interactive elements must have visible focus indicators for keyboard navigation accessibility."

      # Animation without meaningful purpose or context
      - pattern: "animation.*(?:bounce|wiggle|shake|rotate)"
        pattern_negate: "(loading|status|feedback|interaction)"
        message: "Animations should serve a meaningful purpose. Avoid decorative animations that may distract or confuse users."

      # Missing animation state management
      - pattern: "animation.*(?:play|pause|stop)"
        pattern_negate: "(prefers-reduced-motion|user.*control|aria.*live)"
        message: "Animation state changes should be communicated to assistive technology and respect user preferences."

  - type: suggest
    message: |
      **Animation Accessibility Best Practices:**

      **1. Respect Motion Preferences (WCAG 2.3.3):**
      ```css
      /* Default animation */
      .fade-in {
        animation: fadeIn 0.3s ease-in-out;
      }

      /* Reduced motion alternative */
      @media (prefers-reduced-motion: reduce) {
        .fade-in {
          animation: none;
          opacity: 1;
        }
      }
      ```

      **2. Seizure Prevention (WCAG 2.3.1):**
      ```css
      /* Safe flashing animation - below 3Hz threshold */
      .pulse {
        animation: pulse 0.4s ease-in-out infinite;
      }

      /* Reduced motion alternative */
      @media (prefers-reduced-motion: reduce) {
        .pulse {
          animation: none;
          opacity: 0.8;
        }
      }
      ```

      **3. Motion Sickness Prevention:**
      ```css
      /* Gentle, predictable animations */
      .slide-in {
        transform: translateX(20px);
        transition: transform 0.2s ease-out;
      }

      /* Reduced motion alternative */
      @media (prefers-reduced-motion: reduce) {
        .slide-in {
          transform: none;
          transition: none;
          opacity: 1;
        }
      }
      ```

      **4. Essential UI Animations:**
      ```css
      /* Loading spinner - essential for user understanding */
      .loading-spinner {
        animation: spin 1s linear infinite;
      }

      /* Keep essential animations even with reduced motion */
      @media (prefers-reduced-motion: reduce) {
        .loading-spinner {
          animation: spin 2s linear infinite; /* Slower but still visible */
        }
      }
      ```

      **5. User Control Implementation:**
      ```javascript
      // Animation pause control
      class AnimationController {
        constructor() {
          this.isPaused = false;
          this.animations = document.querySelectorAll('[data-animation]');
          this.setupControls();
        }

        setupControls() {
          const pauseButton = document.getElementById('pause-animations');
          if (pauseButton) {
            pauseButton.addEventListener('click', () => this.togglePause());
          }
        }

        togglePause() {
          this.isPaused = !this.isPaused;
          this.animations.forEach(animation => {
            if (this.isPaused) {
              animation.style.animationPlayState = 'paused';
            } else {
              animation.style.animationPlayState = 'running';
            }
          });
        }
      }
      ```

      **6. CSS Animation Best Practices:**
      ```css
      /* Safe animation defaults */
      .safe-animation {
        /* Keep travel distances short */
        transform: translateY(10px);

        /* Use gentle easing */
        transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);

        /* Respect motion preferences */
        @media (prefers-reduced-motion: reduce) {
          transform: none;
          transition: none;
        }
      }

      /* Fade as safe default */
      .fade-transition {
        opacity: 0;
        transition: opacity 0.3s ease-in-out;
      }

      .fade-transition.visible {
        opacity: 1;
      }

      /* Reduced motion alternative */
      @media (prefers-reduced-motion: reduce) {
        .fade-transition {
          transition: none;
          opacity: 1;
        }
      }
      ```

      **7. JavaScript Animation Control:**
      ```javascript
      // Check motion preferences
      function shouldReduceMotion() {
        return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      }

      // Safe animation function
      function safeAnimate(element, animation, options = {}) {
        if (shouldReduceMotion()) {
          // Provide alternative experience
          element.style.opacity = '1';
          element.style.transform = 'none';
          return;
        }

        // Apply animation
        element.style.animation = animation;

        // Add pause control
        if (options.looping) {
          element.addEventListener('click', () => {
            element.style.animationPlayState =
              element.style.animationPlayState === 'paused' ? 'running' : 'paused';
          });
        }
      }
      ```

      **8. HTML Structure for Animation Control:**
      ```html
      <!-- Animation control panel -->
      <div class="animation-controls" role="region" aria-label="Animation controls">
        <button id="pause-animations" aria-pressed="false">
          Pause All Animations
        </button>

        <button id="reduce-motion" aria-pressed="false">
          Reduce Motion
        </button>
      </div>

      <!-- Animated element with controls -->
      <div class="animated-element"
           data-animation="fade-in"
           aria-live="polite">
        Content that animates
      </div>
      ```

      **9. Animation State Management:**
      ```css
      /* Animation states */
      .animated-element {
        opacity: 0;
        transform: translateY(20px);
        transition: all 0.3s ease-out;
      }

      .animated-element.animate {
        opacity: 1;
        transform: translateY(0);
      }

      .animated-element.paused {
        animation-play-state: paused;
      }

      /* Reduced motion states */
      @media (prefers-reduced-motion: reduce) {
        .animated-element {
          opacity: 1;
          transform: none;
          transition: none;
        }
      }
      ```

      **10. Testing and Validation:**
      ```javascript
      // Test animation accessibility
      function testAnimationAccessibility() {
        const issues = [];

        // Check for flashing animations
        const flashingElements = document.querySelectorAll('[class*="flash"], [class*="pulse"]');
        flashingElements.forEach(element => {
          const style = window.getComputedStyle(element);
          const duration = parseFloat(style.animationDuration);
          if (duration < 0.33) { // Below 3Hz threshold
            issues.push(`Flashing animation too fast: ${element.className}`);
          }
        });

        // Check for motion preference support
        const hasReducedMotion = document.querySelector('@media (prefers-reduced-motion: reduce)');
        if (!hasReducedMotion) {
          issues.push('Missing reduced motion alternatives');
        }

        return issues;
      }
      ```

      **Animation Guidelines:**

      **Keep These Animations:**
      - **Fading:** Safe default for most transitions
      - **Loading indicators:** Essential for user understanding
      - **Subtle scaling:** Gentle size changes for feedback
      - **Color transitions:** Slow, smooth color changes

      **Subdue These Animations:**
      - **System-triggered:** Make unexpected animations subtle
      - **Large movements:** Reduce distance and speed
      - **Color changes:** Use gentler transitions

      **Remove These Animations:**
      - **Decorative effects:** Remove purely visual animations
      - **Spatial movement:** Replace with gentle fades
      - **Auto-playing loops:** Remove or provide pause controls

      **Testing Checklist:**
      - Test with prefers-reduced-motion: reduce
      - Verify animations don't exceed 3Hz frequency
      - Check that essential UI changes are clear without animation
      - Ensure pause controls work for looping animations
      - Test with screen readers and assistive technology
      - Validate motion doesn't cause disorientation

      **Common Mistakes to Avoid:**
      - Missing prefers-reduced-motion media queries
      - Flashing animations above 3Hz threshold
      - Large spatial movements without alternatives
      - Auto-playing animations without pause controls
      - Parallax effects that ignore user preferences
      - Animations that patch design shortcomings
      - Missing focus indicators on animated elements
      - Excessive animation duration causing motion sickness

metadata:
  priority: high
  version: 1.0
</rule>
description:
globs:
alwaysApply: false
---
