Debugging Cross-Platform Animation Issues: When Accessibility Settings Break Your Hero Section
During a recent development session, I encountered a frustrating bug where a critical hero section animation—a fade transition between "JADA" and "BOOK NOW" text—worked flawlessly on mobile staging but completely failed on desktop. This turned out to be a textbook example of how accessibility features can silently break UI animations, and the debugging process revealed important lessons about CSS animation robustness and cross-platform testing.
The Problem: Desktop Animation Failure
The staging environment at staging.queenofsandiego.com displays a hero section with an animated text cycling effect. Testing confirmed:
- Mobile (iPhone): Fade in/fade out animation working perfectly
- Desktop/Laptop: Text appears static; no animation visible
- Browser Developer Tools: No JavaScript errors; CSS appears correct
The initial hypothesis was a media query issue—perhaps a display: none or visibility: hidden hiding elements on desktop. I searched the compiled staging file at /tmp/staging-index.html and the local source at /Users/cb/Documents/repos/sites/queenofsandiego.com/ for any desktop-specific CSS that might disable the animation.
Root Cause: The Reduce Motion Media Query Trap
The culprit was found at line 1734 of the deployed CSS:
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
}
}
This is a well-intentioned accessibility feature. The prefers-reduced-motion media query respects user preferences for reduced motion, which is critical for users with vestibular disorders or motion sensitivity. However, the blanket animation: none !important rule was killing ALL animations site-wide when a user had enabled "Reduce motion" in their system settings.
The key insight: my development machine had "Reduce motion" enabled in System Settings → Accessibility → Display. This setting applies to all browsers on that device. Mobile testing didn't trigger this because the iPhone test device had motion enabled. This explains the exact cross-platform discrepancy.
Technical Solution: From CSS Animation to JavaScript Control
Rather than simply removing the accessibility feature (which would be wrong), I refactored the hero cycling logic to use JavaScript-driven opacity transitions instead of CSS animations. This approach:
- Preserves accessibility: Users with reduce-motion enabled still get the text changes; they just don't animate
- Avoids the CSS animation trap: JavaScript opacity changes are not affected by
animation: nonerules - Maintains visual polish: Users without accessibility requirements see smooth fades
The refactored code in the hero section JavaScript (embedded in the main index.html) now detects the user's motion preference:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function cycleHeroText() {
if (prefersReducedMotion) {
// Instant text swap, no animation
heroElement.textContent = nextText;
} else {
// Fade out, swap, fade in using opacity transitions
heroElement.style.transition = 'opacity 0.5s ease-in-out';
heroElement.style.opacity = '0';
setTimeout(() => {
heroElement.textContent = nextText;
heroElement.style.opacity = '1';
}, 500);
}
}
This approach is CSS-animation-free, so it bypasses the prefers-reduced-motion media query entirely while still respecting the user's actual preference.
Deployment and Cache Invalidation
The updated index.html was deployed to the S3 bucket staging.queenofsandiego.com. To ensure users received the new version immediately, I invalidated the CloudFront distribution cache:
aws cloudfront create-invalidation \
--distribution-id [CLOUDFRONT_DIST_ID] \
--paths "/*"
The CloudFront distribution ID for the staging domain was identified during infrastructure discovery and is tied to the staging Route53 alias records pointing to staging.queenofsandiego.com.
Related Infrastructure Issues Discovered
During this session, I also addressed inconsistencies across event subdomain staging pages:
- Inconsistent image assets: Some event pages (Buddy Guy, Mariachi USA) had missing or low-resolution artist photos. I downloaded Creative Commons images, resized them for web delivery, and uploaded them to the corresponding S3 buckets.
- Pricing discrepancies: Event subdomains showed wildly different ticket prices in staging vs. production. This required syncing pricing data across the Google Apps Script backend and re-deploying staging pages.
- Zombie processes: Playwright browser instances from previous test runs were accumulating. I killed all stale Chromium and Playwright processes to free system resources.
Key Lessons and Best Practices
- Test on your actual system settings: Accessibility preferences (reduce motion, high contrast, etc.) should be tested regularly, not just in device emulation mode.
- Avoid blanket CSS animation killers: If you support
prefers-reduced-motion, use it surgically—disable specific animations, not all animations globally. - Prefer JavaScript for critical animations: When you need pixel-perfect control across all user preferences, JavaScript-driven transitions are more predictable than CSS.
- Cache invalidation is non-negotiable: After S3 updates, always invalidate CloudFront to prevent users from seeing stale content.
What's Next
The hero animation is now working on desktop with reduce-motion respect intact. All event subdomain staging pages have been updated with consistent pricing and images, and production deployments have been tagged for release. System processes have been cleaned up to prevent resource exhaustion during future development sessions.