import React, { useCallback, useEffect, useState, useRef, memo } from 'react'; import { Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, ChevronDown, Repeat, Repeat1, Square, Music } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { buildCoverArtUrl } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; function formatTime(seconds: number): string { if (!seconds || isNaN(seconds)) return '0:00'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${s.toString().padStart(2, '0')}`; } // ─── Crossfading blurred background — two stacked divs for true crossfade ─── const FsBg = memo(function FsBg({ url }: { url: string }) { // Each layer: {url, id, visible} const [layers, setLayers] = useState>(() => url ? [{ url, id: 0, visible: true }] : [] ); const counterRef = useRef(1); useEffect(() => { if (!url) return; const id = counterRef.current++; // Add the new layer (opacity 0) setLayers(prev => [...prev, { url, id, visible: false }]); // One frame later: make new layer visible (0→1) and old ones invisible (1→0) const t1 = setTimeout(() => { setLayers(prev => prev.map(l => ({ ...l, visible: l.id === id })) ); }, 20); // After transition: clean up old layers const t2 = setTimeout(() => { setLayers(prev => prev.filter(l => l.id === id)); }, 800); return () => { clearTimeout(t1); clearTimeout(t2); }; }, [url]); // eslint-disable-line react-hooks/exhaustive-deps return ( <> {layers.map(layer => (