美化音乐界面

This commit is contained in:
mtvpls
2026-05-26 22:05:53 +08:00
parent 41b8e1cce6
commit 633ee0bc9b
3 changed files with 219 additions and 122 deletions
+40 -27
View File
@@ -3408,26 +3408,26 @@ export default function MusicClient({ children: _children }: { children?: React.
{showSidebarDrawer && (
<div className="fixed inset-0 z-[95]">
<button
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300"
onClick={() => setShowSidebarDrawer(false)}
aria-label="关闭菜单"
/>
<aside className="absolute left-0 top-0 flex h-full w-72 max-w-[86vw] flex-col border-r border-white/10 bg-zinc-950/98 px-4 py-5 shadow-2xl">
<div className="mb-6 flex items-center justify-between">
<aside className="absolute left-0 top-0 flex h-full w-72 max-w-[85vw] flex-col border-r border-white/10 bg-zinc-950/80 backdrop-blur-xl px-5 py-6 shadow-[20px_0_40px_rgba(0,0,0,0.5)] animate-in slide-in-from-left duration-300 ease-out">
<div className="mb-8 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-white/10 text-green-500">
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-gradient-to-br from-green-400 to-emerald-600 text-white shadow-lg shadow-green-500/20">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
</svg>
</div>
<div>
<div className="text-base font-bold text-white"></div>
<div className="text-xs text-zinc-500">Music Navigation</div>
<div className="text-lg font-bold text-white tracking-wide"></div>
<div className="text-[10px] uppercase tracking-wider text-green-400/80">Music Navigation</div>
</div>
</div>
<button
onClick={() => setShowSidebarDrawer(false)}
className="flex h-9 w-9 items-center justify-center rounded-full bg-white/10 text-zinc-400 hover:text-white"
className="flex h-9 w-9 items-center justify-center rounded-full bg-white/5 text-zinc-400 transition-all hover:bg-white/10 hover:text-white hover:rotate-90"
>
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
@@ -3435,7 +3435,7 @@ export default function MusicClient({ children: _children }: { children?: React.
</button>
</div>
<nav className="space-y-2">
<nav className="space-y-3 relative z-10">
{[
{ key: 'rankings', label: '排行榜', href: '/music/rankings', icon: 'M3 4h18M8 8h13M3 12h18M8 16h13M3 20h18' },
{ key: 'search', label: '搜索', href: `/music/search?source=${currentSource}`, icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' },
@@ -3451,33 +3451,46 @@ export default function MusicClient({ children: _children }: { children?: React.
setShowSidebarDrawer(false);
router.push(item.href);
}}
className={`flex w-full items-center gap-3 rounded-xl border px-3 py-3 text-left transition-all ${
className={`group flex w-full items-center gap-4 rounded-2xl px-4 py-3.5 text-left transition-all duration-300 ${
active
? 'border-green-500/40 bg-green-500/15 text-white'
: 'border-white/5 bg-white/5 text-zinc-400 hover:bg-white/10 hover:text-white'
? 'bg-gradient-to-r from-green-500/20 to-emerald-500/5 text-white shadow-inner border border-green-500/20'
: 'bg-transparent text-zinc-400 hover:bg-white/5 hover:text-white border border-transparent'
}`}
>
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={item.icon} />
</svg>
<span className="text-sm font-medium">{item.label}</span>
<div className={`flex items-center justify-center transition-colors ${active ? 'text-green-400' : 'text-zinc-500 group-hover:text-white'}`}>
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d={item.icon} />
</svg>
</div>
<span className="text-sm font-semibold">{item.label}</span>
{active && (
<div className="ml-auto w-1.5 h-1.5 rounded-full bg-green-400 shadow-[0_0_8px_rgba(74,222,128,0.8)] animate-pulse" />
)}
</button>
);
})}
</nav>
<button
onClick={() => {
setShowSidebarDrawer(false);
router.push('/');
}}
className="mt-auto flex w-full items-center gap-3 rounded-xl border border-white/10 bg-white/5 px-3 py-3 text-left text-zinc-400 transition-colors hover:bg-white/10 hover:text-white"
>
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
<span className="text-sm font-medium"></span>
</button>
<div className="mt-auto relative z-10 pt-6">
<div className="absolute top-0 inset-x-0 h-px bg-gradient-to-r from-transparent via-white/10 to-transparent" />
<button
onClick={() => {
setShowSidebarDrawer(false);
router.push('/');
}}
className="group flex w-full items-center gap-4 rounded-2xl border border-white/5 bg-white/5 px-4 py-3.5 text-left text-zinc-400 transition-all duration-300 hover:bg-white/10 hover:text-white hover:border-white/10 hover:shadow-lg"
>
<div className="flex items-center justify-center text-zinc-500 group-hover:text-white transition-colors">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</div>
<span className="text-sm font-semibold"></span>
</button>
</div>
{/* Ambient Background Effect */}
<div className="pointer-events-none absolute -left-20 -top-20 h-64 w-64 rounded-full bg-green-500/10 blur-[80px]" />
</aside>
</div>
)}
+84 -27
View File
@@ -9,6 +9,7 @@ export default function MusicRankingsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [currentSource, setCurrentSource] = useState(normalizeSource(searchParams.get('source')));
const [showSourceMenu, setShowSourceMenu] = useState(false);
const [playlists, setPlaylists] = useState<Playlist[]>([]);
const [loading, setLoading] = useState(false);
@@ -34,50 +35,106 @@ export default function MusicRankingsPage() {
.finally(() => setLoading(false));
}, [searchParams]);
const currentSourceLabel = musicSources.find(s => s.key === currentSource)?.label || '音源';
return (
<div>
<div className="mb-6 flex items-center justify-between gap-3 border-b border-white/5 pb-3">
<h2 className="shrink-0 text-xs font-mono text-white/50 tracking-widest"></h2>
<div className="flex max-w-[calc(100vw-7rem)] justify-end overflow-x-auto rounded-full border border-white/10 bg-white/[0.04] p-1 shadow-inner shadow-black/20 md:max-w-none md:overflow-visible">
{musicSources.map((source) => {
const active = currentSource === source.key;
return (
<div className="mb-8 flex items-center justify-between border-b border-[rgba(0,0,0,0.08)] dark:border-[rgba(255,255,255,0.08)] pb-4 relative z-50">
<h2 className="text-2xl font-bold text-black dark:text-white tracking-tight"></h2>
{/* Pixel-perfect macOS Source Switcher */}
<div className="relative">
<button
type="button"
onClick={() => setShowSourceMenu((open) => !open)}
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[6px] bg-[rgba(0,0,0,0.04)] dark:bg-[rgba(255,255,255,0.08)] hover:bg-[rgba(0,0,0,0.08)] dark:hover:bg-[rgba(255,255,255,0.12)] border border-[rgba(0,0,0,0.04)] dark:border-[rgba(255,255,255,0.04)] shadow-sm transition-all"
aria-haspopup="listbox"
aria-expanded={showSourceMenu}
>
<span className="text-[13px] text-[rgba(0,0,0,0.5)] dark:text-[rgba(255,255,255,0.5)]"></span>
<span className="text-[13px] font-medium text-black dark:text-white">{currentSourceLabel}</span>
<svg className="w-3.5 h-3.5 opacity-50 ml-0.5 text-black dark:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</button>
{showSourceMenu && (
<>
<button
key={source.key}
onClick={() => router.push(`/music/rankings?source=${source.key}`)}
className={`shrink-0 rounded-full px-3 py-1.5 text-xs font-bold transition-all ${
active
? 'bg-gradient-to-r from-green-500 to-emerald-400 text-white shadow-lg shadow-green-500/25'
: 'text-zinc-400 hover:bg-white/10 hover:text-white'
}`}
>
{source.label}
</button>
);
})}
type="button"
className="fixed inset-0 z-40 cursor-default"
onClick={() => setShowSourceMenu(false)}
aria-label="关闭菜单"
/>
<div className="absolute right-0 top-[calc(100%+6px)] z-50 w-32 p-1.5 overflow-hidden rounded-xl bg-[rgba(255,255,255,0.85)] dark:bg-[rgba(35,35,35,0.85)] backdrop-blur-2xl shadow-[0_8px_30px_rgba(0,0,0,0.12)] border border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] animate-in fade-in zoom-in-95 duration-100 origin-top-right" role="listbox">
{musicSources.map((item) => {
const active = currentSource === item.key;
return (
<button
key={item.key}
onClick={() => {
setShowSourceMenu(false);
router.push(`/music/rankings?source=${item.key}`);
}}
className="group flex w-full items-center justify-between rounded-md px-2 py-1.5 text-left text-[13px] text-black dark:text-white hover:bg-green-500 hover:text-white transition-none"
role="option"
aria-selected={active}
>
<span className="whitespace-nowrap">{item.label}</span>
{active && (
<svg className="h-3.5 w-3.5 text-black dark:text-white group-hover:text-white shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
);
})}
</div>
</>
)}
</div>
</div>
{loading ? <MusicLoadingIndicator className="py-8" /> : playlists.length > 0 ? (
<div className="space-y-2">
{loading ? <MusicLoadingIndicator className="py-12" /> : playlists.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{playlists.map((playlist, index) => (
<button
key={playlist.id}
onClick={() => router.push(`/music/rankings/${playlist.source || currentSource}/${encodeURIComponent(playlist.id)}?name=${encodeURIComponent(playlist.name)}`)}
className="w-full text-left rounded-xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors px-4 py-3"
className="group relative w-full text-left overflow-hidden rounded-2xl border border-white/5 bg-white/5 hover:bg-white/10 transition-all duration-300 p-4 hover:-translate-y-1 hover:shadow-xl backdrop-blur-sm"
>
<div className="flex items-center gap-4">
<div className="w-8 text-sm text-zinc-500 dark:text-zinc-300 font-mono shrink-0">{String(index + 1).padStart(2, '0')}</div>
<div className="absolute top-0 right-0 -mt-2 -mr-2 w-16 h-16 bg-white/5 rounded-full blur-xl group-hover:bg-green-500/20 transition-colors duration-500"></div>
<div className="flex items-center gap-4 relative z-10">
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-white/5 text-lg font-bold text-zinc-400 font-mono shadow-inner group-hover:bg-green-500/20 group-hover:text-green-500 transition-colors">
{String(index + 1).padStart(2, '0')}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-white/90 truncate">{playlist.name}</div>
{playlist.updateFrequency ? <div className="text-xs text-zinc-500 mt-1 truncate">{playlist.updateFrequency}</div> : null}
<div className="text-base font-semibold text-white/90 truncate group-hover:text-white transition-colors">{playlist.name}</div>
{playlist.updateFrequency ? (
<div className="text-xs text-zinc-500 mt-1 truncate group-hover:text-zinc-400 transition-colors flex items-center gap-1">
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{playlist.updateFrequency}
</div>
) : null}
</div>
<div className="text-zinc-600 group-hover:text-green-500 group-hover:translate-x-1 transition-all duration-300 shrink-0">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
<div className="text-zinc-500 shrink-0"></div>
</div>
</button>
))}
</div>
) : (
<div className="rounded-xl border border-white/10 bg-white/5 p-6 text-center text-zinc-400"></div>
<div className="flex flex-col items-center justify-center rounded-2xl border border-dashed border-white/10 bg-white/5 p-12 text-center text-zinc-400">
<svg className="mb-4 h-12 w-12 text-zinc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
</svg>
<div className="text-lg font-medium text-zinc-300 mb-1"></div>
<div className="text-sm text-zinc-500"></div>
</div>
)}
</div>
);
+95 -68
View File
@@ -53,79 +53,106 @@ export default function MusicSearchPage() {
const currentSourceLabel = musicSources.find((item) => item.key === selectedSource)?.label || '音源';
return (
<div>
<div className="mb-6 flex items-center gap-2">
<div className="relative h-11 flex-1 rounded-xl border border-white/10 bg-white/10">
<div className="pointer-events-none absolute inset-y-0 left-3 flex items-center">
<svg className="h-4 w-4 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
</div>
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && submit()} className="h-full w-full border-0 bg-transparent pl-10 pr-28 text-sm text-white outline-none placeholder:text-zinc-500" placeholder="搜索歌曲或艺术家..." />
<div className="absolute inset-y-0 right-2 flex items-center">
<div className="relative h-8">
<button
type="button"
onClick={() => setShowSourceMenu((open) => !open)}
className="flex h-8 min-w-[84px] items-center justify-between gap-1 rounded-lg border border-white/10 bg-zinc-900/90 pl-3 pr-2 text-xs font-medium text-white shadow-inner shadow-white/5 transition-colors hover:bg-zinc-800"
aria-haspopup="listbox"
aria-expanded={showSourceMenu}
aria-label="选择音源"
>
<span>{currentSourceLabel}</span>
<svg className={`h-3.5 w-3.5 text-zinc-400 transition-transform ${showSourceMenu ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
{showSourceMenu && (
<>
<button
type="button"
className="fixed inset-0 z-40 cursor-default"
onClick={() => setShowSourceMenu(false)}
aria-label="关闭音源菜单"
/>
<div className="absolute right-0 top-10 z-50 w-32 overflow-hidden rounded-xl border border-white/10 bg-zinc-900/95 p-1 shadow-2xl shadow-black/50 backdrop-blur-md" role="listbox">
{musicSources.map((item) => {
const active = item.key === selectedSource;
return (
<button
key={item.key}
type="button"
onClick={() => changeSource(item.key)}
className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-xs transition-colors ${
active ? 'bg-green-500/20 text-green-300' : 'text-zinc-300 hover:bg-white/10 hover:text-white'
}`}
role="option"
aria-selected={active}
>
<span>{item.label}</span>
{active && (
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
</svg>
)}
</button>
);
})}
</div>
</>
)}
</div>
<div className="animate-in fade-in duration-500 relative z-10">
<div className="mb-8 flex items-center gap-3 relative z-50">
<div className="relative flex-1 flex items-center bg-[rgba(0,0,0,0.04)] dark:bg-[rgba(255,255,255,0.06)] rounded-[8px] pl-3 border border-[rgba(0,0,0,0.06)] dark:border-[rgba(255,255,255,0.06)] focus-within:border-green-500 focus-within:ring-0 transition-all shadow-sm">
<svg className="w-4 h-4 opacity-40 text-black dark:text-white mr-2 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && submit()}
className="w-full bg-transparent border-none outline-none focus:outline-none focus:ring-0 text-[14px] py-2 text-black dark:text-white placeholder-[rgba(0,0,0,0.4)] dark:placeholder-[rgba(255,255,255,0.4)]"
placeholder="搜索歌曲或艺术家..."
/>
<div className="w-px h-5 bg-[rgba(0,0,0,0.1)] dark:bg-[rgba(255,255,255,0.1)] mx-1 shrink-0" />
<div className="relative h-full flex items-center pr-1 shrink-0">
<button
type="button"
onClick={() => setShowSourceMenu((open) => !open)}
className="flex h-[32px] items-center gap-1.5 px-2.5 rounded-[6px] hover:bg-[rgba(0,0,0,0.06)] dark:hover:bg-[rgba(255,255,255,0.08)] transition-all"
aria-haspopup="listbox"
aria-expanded={showSourceMenu}
>
<span className="text-[13px] font-medium text-black dark:text-white">{currentSourceLabel}</span>
<svg className="w-3.5 h-3.5 opacity-50 text-black dark:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</button>
{showSourceMenu && (
<>
<button
type="button"
className="fixed inset-0 z-40 cursor-default"
onClick={() => setShowSourceMenu(false)}
aria-label="关闭菜单"
/>
<div className="absolute right-0 top-[calc(100%+6px)] z-50 w-32 p-1.5 overflow-hidden rounded-xl bg-[rgba(255,255,255,0.85)] dark:bg-[rgba(35,35,35,0.85)] backdrop-blur-2xl shadow-[0_8px_30px_rgba(0,0,0,0.12)] border border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] animate-in fade-in zoom-in-95 duration-100 origin-top-right" role="listbox">
{musicSources.map((item) => {
const active = selectedSource === item.key;
return (
<button
key={item.key}
onClick={() => {
setShowSourceMenu(false);
changeSource(item.key);
}}
className="group flex w-full items-center justify-between rounded-md px-2 py-1.5 text-left text-[13px] text-black dark:text-white hover:bg-green-500 hover:text-white transition-none"
role="option"
aria-selected={active}
>
<span className="whitespace-nowrap">{item.label}</span>
{active && (
<svg className="h-3.5 w-3.5 text-black dark:text-white group-hover:text-white shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
);
})}
</div>
</>
)}
</div>
</div>
<button onClick={submit} className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-green-600 text-white hover:bg-green-700" aria-label="搜索" title="搜索">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<div className="flex flex-wrap items-center justify-between gap-4 mb-8 border-b border-white/10 pb-4">
<div className="flex items-center gap-4 min-w-0">
<h2 className="text-2xl font-black text-white tracking-tight truncate max-w-md">
{q ? `搜索: ${q}` : '发现音乐'}
</h2>
{songs.length > 0 && (
<span className="px-2.5 py-1 text-xs font-bold bg-white/10 text-green-500 rounded-full border border-green-500/20 shrink-0">
{songs.length}
</span>
)}
</div>
<button
onClick={() => playMusicList(songs, q ? `搜索: ${q}` : '搜索结果')}
disabled={songs.length === 0}
className="flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 disabled:bg-white/5 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl text-sm font-medium text-white transition-colors shrink-0"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z" /></svg>
</button>
</div>
<div className="flex items-center justify-between mb-6 border-b border-white/5 pb-2">
<div className="flex items-center gap-3 min-w-0">
<h2 className="text-xl font-bold text-white/80 tracking-tight truncate max-w-md">{q ? `搜索: ${q}` : '搜索'}</h2>
<span className="text-[10px] font-bold bg-white/10 px-2 py-0.5 rounded text-white shrink-0">{songs.length} </span>
{loading ? (
<MusicLoadingIndicator className="py-16" />
) : q ? (
<SongList songs={songs} />
) : (
<div className="flex flex-col items-center justify-center rounded-3xl border border-dashed border-white/10 bg-white/5 py-20 text-center backdrop-blur-sm">
<div className="w-20 h-20 mb-6 rounded-full bg-white/5 flex items-center justify-center shadow-inner">
<svg className="h-10 w-10 text-zinc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<div className="text-xl font-bold text-zinc-300 mb-2"></div>
<div className="text-sm text-zinc-500 max-w-sm"></div>
</div>
<button onClick={() => playMusicList(songs, q ? `搜索: ${q}` : '搜索结果')} disabled={songs.length === 0} className="px-3 py-1.5 bg-green-600 hover:bg-green-700 disabled:bg-zinc-700 disabled:cursor-not-allowed rounded-lg text-sm text-white shrink-0"></button>
</div>
{loading ? <MusicLoadingIndicator className="py-8" /> : q ? <SongList songs={songs} /> : <div className="rounded-xl border border-white/10 bg-white/5 p-8 text-center text-zinc-400"></div>}
)}
</div>
);
}