Two issues were causing grey/unloaded video cards:
1. preload="none" blocked browsers from fetching the first frame for
poster display. Changed to preload="metadata" so the first frame
shows even before play starts.
2. IntersectionObserver had edge cases with CSS transforms on the
virtual scroll canvas — observer wasn't firing for transformed
cards. Replaced with direct control via virtual scroll lifecycle:
- Videos get src set + .load() called when card is added to DOM
- Playback starts after fragment is appended to grid (play() needs
element to be in the DOM)
- Videos are paused + reset via _stopVideoPlayback when cards are
pooled or removed
This uses the virtual scroll range directly (which already handles
pan/zoom/scroll correctly) instead of relying on IntersectionObserver
which has known issues with CSS transforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>