Repository navigation
feat(motion): View Transitions — Now Playing sheet, art morphs, smooth skip (#289) - #320
Merged
Merged
Conversation
…overs into album pages, soft page transitions (#289) - Page navigations: only the main content (view-transition-name: app-main) fades/lifts; sidebar, player and tab bar stay put. Router uses typed transitions so search-param-only updates (e.g. typing in search) don't animate; engines without typed transitions just navigate instantly. - Now Playing: tapping the mini-player art starts a transition, flushes the open synchronously, and the art morphs into the fullscreen artwork; close (button or swipe-down from the drag position) morphs back. Fullscreen gets skipSlide for these opens; lyrics/visualizer opens keep the slide-up. Mode reset moved into render so the first transition frame is already correct. - Albums: the tapped card's cover (named at tap time, so duplicates across lists can't clash) morphs into the album header; the album page shows the cover while details load so there's always a target. - view-transition.ts helpers (feature-detected, reduced-motion aware); reduced motion disables every transition animation. No new dependencies, nothing on the audio path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SjSuKavpRLaVq5buHPm9ce
…round the art morph (#289) On iOS PWA the open looked like the art sliding and then the background snapping in: only the art had a view-transition-name, so the fullscreen backdrop/controls were part of the root snapshot and just cross-faded. - Name the fullscreen container `np-sheet` when opened via a view transition. - Sheet slides up from the bottom on open / down on close (`:only-child`, so it works even without typed-transition support), on the art's curve. - On np-open / np-close the page behind recedes (scale 0.94, dim) / returns instead of cross-fading two identical snapshots. - Art morph duration 380 → 440 ms to match the sheet. - Reduced motion: unchanged (all view-transition animations off). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
…eload the big art (#289) Two causes of "album pops in after the background": - No explicit stacking between transition groups, so Safari could paint the art group under the sliding np-sheet. Now: art/album-art z-index 3 > sheet 2 > page. - The fullscreen view renders a 600px cover; the mini player shows a small one. If the 600px image wasn't decoded when the transition captured its "after" state, the morph went to an empty frame and the image appeared later. PlayerBar now preloads the 600px cover (same URL as ArtMode) whenever the song changes, and the open waits up to 200 ms for img.decode() before starting the transition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
… art settles (#289) Swipe-skip in the fullscreen art ran two animations at once: an inline snap-back transition from the drag position AND a CSS slide-in class added a render later (after the track change), removed by a separate 300 ms timer. Their different start times/positions made the art jump as they settled, and the new song's 600px art often wasn't loaded yet. - ArtMode: on a committed swipe, the current art continues in the swipe direction and fades (140 ms), the track changes, then the new art slides in from the opposite side (280 ms), all via the Web Animations API from the release position; inline styles are cleared at the hand-off. Non-skip releases still spring back. Reduced motion: instant skip. Removed the swipeDirection state/class/timer. - PlayerBar: also preload the previous/next songs' 600px art so the incoming image is already decoded when it lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
…e cover (#289) Tapping the song/album link in fullscreen Now Playing used to just close the sheet back to the mini player. It now runs one view transition that closes the sheet, navigates to the album, waits (up to 600ms) for the cover image, and morphs the big art into the album hero art. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
- Main content now recedes with the rest of the page on open/close (app-main is its own layer, so it needs the np-open/np-close rules too) - An album page's cover no longer floats above the sliding sheet: its album-art name is suppressed for np-open/np-close and restored after - Title -> album page unmounts the sheet in the same commit, so the morph works even when the sheet was opened from lyrics/visualizer - Artist link closes the sheet and navigates as one transition instead of the router's transition aborting the slide-down - Cmd/ctrl/shift-click on the title/artist links opens a new tab again Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
… slides down (#289) The sheet's blurred art tint is scaled to 110% and blurred, so it bleeds past the sheet's edges. The np-sheet snapshot included that bleed. After the slide-down a strip of it sat at the bottom of the screen until the transition ended, then vanished with no fade. Clip the sheet to its bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
lolimmlost
added a commit
that referenced
this pull request
Oct 10, 2026
…op (#289) view-transition-name on <main> (app-main) makes it a stacking context all the time, not only during transitions. The album and search pages' fixed, negative-z backdrop then paints as part of <main>'s layer, above the non-positioned, translucent left sidebar, so the sidebar sat under the backdrop's gradient. Put both sidebars on their own layer above <main>, which restores the pre-#320 stacking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
This was referenced Oct 10, 2026
lolimmlost
added a commit
that referenced
this pull request
Oct 10, 2026
…ing transitions (#289) (#324) * fix(motion): album page's blurred backdrop no longer covers the sidebars during transitions (#289) The album and search pages draw a full-window blurred backdrop (position: fixed, behind the content). <main> is its own view-transition layer (app-main), and its snapshot includes fixed descendants, so for the whole transition that backdrop painted over the sidebars on desktop. Most visible going from Now Playing's song title to the album page. Clip the app-main group to <main>'s box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r * fix(motion): sidebars no longer dimmed by the album backdrop on desktop (#289) view-transition-name on <main> (app-main) makes it a stacking context all the time, not only during transitions. The album and search pages' fixed, negative-z backdrop then paints as part of <main>'s layer, above the non-positioned, translucent left sidebar, so the sidebar sat under the backdrop's gradient. Put both sidebars on their own layer above <main>, which restores the pre-#320 stacking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #289.
Adds animated page and player transitions using the browser's View Transitions API. Where the API isn't supported, or Reduce Motion is on, everything updates instantly, as it did before. These are visual changes only; nothing touches the audio path.
What changes
Implementation
src/lib/utils/view-transition.tswithViewTransition, pluswithViewTransitionAsyncfor async updates such as a route change.waitForImage, so a morph lands on a loaded image instead of an empty placeholder.np-open,np-close,np-to-album), with feature detection.src/styles.css: keyframes for the sheet and page recede, z-index on the transition groups, and timing.PlayerBar.tsx,NowPlayingFullscreen/*,ArtMode.tsx(skip animation via the Web Animations API), album page[data-album-hero-art].Testing
view-transition.test.ts(9 tests). The full suite passes: 852 tests.npm run buildis OK.Follow-up
🤖 Generated with Claude Code
https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r