Web: redesign, logo, PWA, QR codes, Spotify guidance
- Album-art-tinted cards that crossfade on song change, equalizer bars, live avatar rings, drifting ambient colour, Space Grotesk display font, brand purple-to-blue gradient from the logo - Feed cards rise in and glide to new positions (motion); respects reduced-motion - New logo as nav mark, favicon and Apple touch icon - Installable PWA: manifest, maskable icons, small service worker (cached build assets + offline page), security headers - Show-QR-code on your own profile; centred profile header with stats - Settings explains connecting Spotify via Last.fm or ListenBrainz Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import Link from 'next/link';
|
||||
import { SERVICE_LABELS, type NowPlayingDTO, type StreamingService } from '@musicshare/shared';
|
||||
import { timeAgo } from '@musicshare/shared';
|
||||
import { SERVICE_LABELS, timeAgo, type NowPlayingDTO, type StreamingService } from '@musicshare/shared';
|
||||
import { Avatar } from './Avatar';
|
||||
import { Equalizer } from './Equalizer';
|
||||
import { TrackCard } from './TrackCard';
|
||||
|
||||
export function NowPlayingCard({
|
||||
@@ -19,42 +20,71 @@ export function NowPlayingCard({
|
||||
pinned?: boolean;
|
||||
onTogglePin?: () => void;
|
||||
}) {
|
||||
const sourceLabel = np.sourceApp ?? SERVICE_LABELS[np.source];
|
||||
return (
|
||||
<article className={`card ${pinned ? 'card-pinned' : ''}`}>
|
||||
<article className={`card np-card ${pinned ? 'pinned' : ''} ${np.isPlaying ? 'playing' : ''}`}>
|
||||
{/* The card glows with its album art; crossfades when the song changes. */}
|
||||
<AnimatePresence initial={false}>
|
||||
{np.track.artworkUrl && (
|
||||
<motion.div
|
||||
key={np.track.artworkUrl}
|
||||
className="np-backdrop"
|
||||
style={{ backgroundImage: `url("${np.track.artworkUrl}")` }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.6 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<header className="card-header">
|
||||
{showUser && (
|
||||
<Link href={`/u/${np.user.username}`} className="user-chip">
|
||||
<Avatar user={np.user} size={28} />
|
||||
<span>{np.user.displayName ?? np.user.username}</span>
|
||||
<Avatar user={np.user} size={30} live={np.isPlaying} />
|
||||
<span className="user-name">{np.user.displayName ?? np.user.username}</span>
|
||||
</Link>
|
||||
)}
|
||||
<span className="row">
|
||||
<span className={`status ${np.isPlaying ? 'status-live' : ''}`}>
|
||||
{np.isPlaying && <Equalizer playing />}
|
||||
{np.isPlaying ? 'Listening now' : `Played ${timeAgo(np.updatedAt)}`}
|
||||
<span className="muted">
|
||||
{' via '}
|
||||
{' · '}
|
||||
{np.sourceUrl ? (
|
||||
<a href={np.sourceUrl} target="_blank" rel="noreferrer">
|
||||
{np.sourceApp ?? SERVICE_LABELS[np.source]}
|
||||
{sourceLabel}
|
||||
</a>
|
||||
) : (
|
||||
(np.sourceApp ?? SERVICE_LABELS[np.source])
|
||||
sourceLabel
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
{onTogglePin && (
|
||||
<button
|
||||
<motion.button
|
||||
className={`pin ${pinned ? 'pinned' : ''}`}
|
||||
onClick={onTogglePin}
|
||||
aria-pressed={pinned}
|
||||
title={pinned ? 'Unpin from top of feed' : 'Pin to top of feed'}
|
||||
whileTap={{ scale: 0.7 }}
|
||||
animate={pinned ? { scale: [1, 1.45, 1], rotate: [0, -18, 0] } : { scale: 1, rotate: 0 }}
|
||||
transition={{ duration: 0.45, ease: 'easeOut' }}
|
||||
>
|
||||
{pinned ? '★' : '☆'}
|
||||
</button>
|
||||
</motion.button>
|
||||
)}
|
||||
</span>
|
||||
</header>
|
||||
<TrackCard track={np.track} preferred={preferred} />
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={np.track.id}
|
||||
initial={{ opacity: 0, y: 12, filter: 'blur(6px)' }}
|
||||
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
|
||||
exit={{ opacity: 0, y: -12, filter: 'blur(6px)' }}
|
||||
transition={{ duration: 0.35, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<TrackCard track={np.track} preferred={preferred} />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user