From e21d5eab9dc01f92485d8c077994483f5f98463e Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Thu, 10 Jul 2025 00:08:46 -0700 Subject: [PATCH] feat: add now playing display to header on avatar hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show currently playing track info when hovering over avatar - Display album artwork, artist name, and track/album name - Add music note icons with pulse animation - Maintain exact same container size as navigation - Only shows when music is actively playing (avatar has headphones) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/lib/components/Header.svelte | 119 ++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 20ed9a1..78e70a1 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -2,6 +2,9 @@ import Avatar from './Avatar.svelte' import SegmentedController from './SegmentedController.svelte' import NavDropdown from './NavDropdown.svelte' + import { albumStream } from '$lib/stores/album-stream' + import { nowPlayingStream } from '$lib/stores/now-playing-stream' + import type { Album } from '$lib/types/lastfm' let scrollY = $state(0) // Smooth gradient opacity from 0 to 1 over the first 100px of scroll @@ -9,6 +12,36 @@ // Padding transition happens more quickly let paddingProgress = $derived(Math.min(scrollY / 50, 1)) + // Now playing state + let isHoveringAvatar = $state(false) + let currentlyPlayingAlbum = $state(null) + let isPlayingMusic = $state(false) + + // Subscribe to album updates + $effect(() => { + const unsubscribe = albumStream.subscribe((state) => { + const nowPlaying = state.albums.find((album) => album.isNowPlaying) + currentlyPlayingAlbum = nowPlaying || null + isPlayingMusic = !!nowPlaying + }) + + return unsubscribe + }) + + // Also check now playing stream for updates + $effect(() => { + const unsubscribe = nowPlayingStream.subscribe((state) => { + const hasNowPlaying = Array.from(state.updates.values()).some((update) => update.isNowPlaying) + if (!hasNowPlaying && currentlyPlayingAlbum) { + // Music stopped + currentlyPlayingAlbum = null + isPlayingMusic = false + } + }) + + return unsubscribe + }) + $effect(() => { let ticking = false @@ -30,6 +63,18 @@ window.addEventListener('scroll', handleScroll, { passive: true }) return () => window.removeEventListener('scroll', handleScroll) }) + + // Get the best available album artwork + function getAlbumArtwork(album: Album): string { + if (album.appleMusicData?.highResArtwork) { + // Use smaller size for the header + return album.appleMusicData.highResArtwork.replace('3000x3000', '100x100') + } + if (album.images.itunes) { + return album.images.itunes.replace('3000x3000', '100x100') + } + return album.images.large || album.images.medium || '' + }
- + isHoveringAvatar = true} + onmouseleave={() => isHoveringAvatar = false} + >