feat: add now playing display to header on avatar hover
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
ee6fd2f25e
commit
e21d5eab9d
1 changed files with 117 additions and 2 deletions
|
|
@ -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<Album | null>(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 || ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<header
|
||||
|
|
@ -37,11 +82,34 @@
|
|||
style="--gradient-opacity: {gradientOpacity}; --padding-progress: {paddingProgress}"
|
||||
>
|
||||
<div class="header-content">
|
||||
<a href="/about" class="header-link" aria-label="@jedmund">
|
||||
<a
|
||||
href="/about"
|
||||
class="header-link"
|
||||
aria-label="@jedmund"
|
||||
onmouseenter={() => isHoveringAvatar = true}
|
||||
onmouseleave={() => isHoveringAvatar = false}
|
||||
>
|
||||
<Avatar />
|
||||
</a>
|
||||
<div class="nav-desktop">
|
||||
<SegmentedController />
|
||||
{#if isHoveringAvatar && isPlayingMusic && currentlyPlayingAlbum}
|
||||
<div class="now-playing">
|
||||
<span class="music-note">♪</span>
|
||||
{#if getAlbumArtwork(currentlyPlayingAlbum)}
|
||||
<img
|
||||
src={getAlbumArtwork(currentlyPlayingAlbum)}
|
||||
alt="{currentlyPlayingAlbum.name} album cover"
|
||||
class="album-thumbnail"
|
||||
/>
|
||||
{/if}
|
||||
<span class="track-info">
|
||||
{currentlyPlayingAlbum.artist.name} - {currentlyPlayingAlbum.nowPlayingTrack || currentlyPlayingAlbum.name}
|
||||
</span>
|
||||
<span class="music-note">♪</span>
|
||||
</div>
|
||||
{:else}
|
||||
<SegmentedController />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="nav-mobile">
|
||||
<NavDropdown />
|
||||
|
|
@ -131,4 +199,51 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.now-playing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $unit-2x;
|
||||
padding: $unit-2x $unit-3x;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: $corner-radius-3xl;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
min-width: 300px;
|
||||
height: 52px;
|
||||
transition: all $transition-fast ease;
|
||||
|
||||
.music-note {
|
||||
font-size: $font-size-md;
|
||||
opacity: 0.8;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.album-thumbnail {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: $corner-radius-sm;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.track-info {
|
||||
flex: 1;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue