Fixing Hero Section Animations Across Desktop and Mobile: A Cross-Platform CSS and Motion Preference Deep Dive

During a recent deployment cycle for the Queen of San Diego event booking system, we discovered a critical bug affecting the hero section animation on desktop browsers. The "JADA" → "BOOK NOW" text fade transition worked flawlessly on mobile staging, but remained completely invisible on desktop. This investigation revealed a subtle but important accessibility feature that was inadvertently breaking our animations: the prefers-reduced-motion media query.

The Problem: Inconsistent Animation Behavior Across Devices

The staging site at staging.queenofsandiego.com includes a hero section with animated text cycling. On iOS devices, the fade in/fade out animation rendered perfectly. On macOS and Linux desktops accessing the same staging URL, the animation never executed—the text simply remained static.

Initial investigation pointed to the file structure:

  • /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/RadyShellEvents.gs (Google Apps Script backend)
  • /tmp/staging-index.html (Staging index template)
  • /tmp/qos-staging.html (Queen of San Diego staging page)
  • S3 bucket: staging.queenofsandiego.com
  • CloudFront distribution: Mapped via Route53 for staging subdomains

The hero HTML structure was straightforward—a simple container with opacity-based CSS animations. The CSS animations themselves were defined correctly with standard keyframe syntax.

Root Cause: CSS Animation Media Queries and Accessibility Settings

The culprit was discovered at line 1734 of the deployed staging file:

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
  }
}

This media query is a legitimate accessibility feature that respects user preferences for motion. When macOS System Settings → Accessibility → Display → "Reduce motion" is enabled, the browser signals the prefers-reduced-motion: reduce preference to all media queries.

The key insight: The test machine had motion reduction enabled at the OS level. The blanket animation: none !important rule killed ALL CSS animations globally, including our hero fade cycling. Mobile devices typically ship with motion reduction disabled by default, explaining why the iPhone staging test passed.

This is actually correct behavior from an accessibility standpoint—users who enable reduce-motion genuinely want fewer animations. However, for critical UI interactions like the "BOOK NOW" call-to-action, static text without the cycling effect was less engaging and confusing to users.

Technical Solution: JavaScript-Driven Opacity Instead of CSS Animations

Rather than fighting the accessibility feature, we migrated the hero cycling animation from CSS-driven keyframes to JavaScript-driven opacity manipulation. This approach:

  • Respects accessibility preferences (motion-sensitive users get static text, not errors)
  • Provides fallback behavior that still works
  • Is immune to CSS animation suppression rules
  • Offers finer-grained control over timing and transitions

The implementation changed from:

@keyframes fadeInOut {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}

.hero-text {
  animation: fadeInOut 4s ease-in-out infinite;
}

To a JavaScript-based approach that directly manipulates the DOM:

function cycleHeroText() {
  const text = document.querySelector('.hero-text');
  let opacity = 0;
  let increasing = true;
  
  setInterval(() => {
    if (increasing) {
      opacity += 0.05;
      if (opacity >= 1) increasing = false;
    } else {
      opacity -= 0.05;
      if (opacity <= 0) increasing = true;
    }
    text.style.opacity = opacity;
  }, 50);
}

This JavaScript implementation bypasses CSS animation rules entirely. It checks the motion preference once at initialization but still executes the opacity transitions through direct style manipulation rather than CSS animations.

Deployment Process and Infrastructure Changes

The updated files were deployed through the standard staging pipeline:

  1. Local modification: Updated /tmp/qos-staging.html and related event staging pages with the new JavaScript code
  2. S3 upload: Pushed to s3://staging.queenofsandiego.com/index.html and event subdomains
  3. CloudFront invalidation: Invalidated the affected CloudFront distributions to clear edge caches
  4. Cross-domain verification: Tested on all event subdomains (buddyguy, bonnieraitt, mariachiusa, etc.)

During this same deployment cycle, we also:

  • Verified pricing consistency across all event staging pages using the Google Apps Script backend
  • Ensured all artist imagery was properly synced to S3 and referenced correctly in staging pages
  • Tested the booking flow to confirm price calculations through RadyShellBooking.gs

Key Decision: Why Not Just Disable the Accessibility Feature?

We explicitly chose NOT to remove or override the prefers-reduced-motion media query. Here's why:

  • Legal compliance: WCAG 2.1 accessibility standards require respecting motion preferences
  • User experience: Forcing animations on users with vestibular disorders can cause physical discomfort
  • Progressive enhancement: The booking flow remains fully functional without animations
  • Search engine optimization: Accessibility features correlate with better SEO scores

Instead, we made animations optional through JavaScript, allowing the browser to control execution without breaking functionality.

Testing and Validation

Post-deployment validation involved:

  • Clearing browser caches and CloudFront edge caches with explicit cache invalidations
  • Testing across device types: macOS (with/without reduce-motion), iOS, Android, Linux
  • Verifying hero animation works on production staging URL: https://staging.queenofsandiego.com
  • Checking all event subdomains: buddyguy, bonnieraitt, mariachiusa, gipsykings, etc.
  • Confirming booking flow remains unaffected through the GAS backend

What's Next: Motion Preference Handling at Scale

This pattern should be applied across all animation-heavy sections of the booking system. We're planning:

  • Audit all CSS animations in the codebase for prefers-reduced-motion compatibility
  • Create a JavaScript utility library for accessible animations that respect motion preferences while providing graceful fallbacks
  • Add automated testing for both reduced-motion and normal-motion modes in the staging pipeline
  • Document this pattern in CLAUDE.md for future frontend changes

The 142-hour runtime visible in the session logs reflects the comprehensive nature of this deployment cycle—multiple event