veza/apps/web/src/components/player/FullPlayer.tsx
senke 1f4b731e38 ui(tokens): replace arbitrary max-h/h viewport values with layout tokens
Migrate all max-h-[XXvh] and h-[XXvh] to design system tokens:
- max-h-[85vh] → max-h-layout-modal (9 modals)
- max-h-[80vh] → max-h-layout-modal-sm (4 modals + notification bell)
- max-h-[70vh] → max-h-layout-modal-xs (AddToPlaylistModal)
- max-h-[90vh] → max-h-layout-modal-lg (CreatorModal)
- max-h-[400px] → max-h-layout-list (QueuePanel, OfflineQueueManager)
- max-h-[500px] → max-h-layout-drawer (APIPlaygroundView)
- h-[60vh] → h-layout-lyrics (FullPlayer, TrackDetailPageHero/Skeleton)
- max-w-[400px] → max-w-lg (FullPlayer)

Zero arbitrary viewport heights remain in modals and panels.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 22:47:41 +01:00

143 lines
5.3 KiB
TypeScript

import React, { useState } from 'react';
import { Minimize2 } from 'lucide-react';
import { Button } from '../ui/button';
import { useAudio } from '../../context/AudioContext';
import { PlayerControls } from './PlayerControls';
import { LyricsPanel } from './LyricsPanel';
import { WaveformVisualizer } from '../ui/WaveformVisualizer';
interface FullPlayerProps {
onClose: () => void;
}
export const FullPlayer: React.FC<FullPlayerProps> = ({ onClose }) => {
const {
currentTrack,
currentTime,
duration,
progress,
seek,
visualizerSettings,
} = useAudio();
const [showLyrics, setShowLyrics] = useState(false);
if (!currentTrack) return null;
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
};
const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const p = Math.max(0, Math.min(100, (x / rect.width) * 100));
seek(p);
};
return (
<div className="fixed inset-0 z-[60] bg-background flex flex-col animate-fadeIn">
{/* Ambient Backdrop */}
<div className="absolute inset-0 z-0 overflow-hidden">
<div className="absolute inset-0 bg-black/60 z-10 backdrop-blur-3xl"></div>
<img
src={currentTrack.coverUrl}
className="w-full h-full object-cover opacity-50 scale-125 blur-3xl"
/>
</div>
{/* Header */}
<div className="relative z-20 flex justify-between items-center p-8">
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="text-white/50 hover:text-white"
>
<Minimize2 className="w-6 h-6" />
</Button>
<div className="flex gap-2">
<span className="px-4 py-1 bg-white/10 rounded-full text-xs font-bold text-white tracking-widest border border-white/20 backdrop-blur">
LOSSLESS
</span>
</div>
</div>
{/* Main Content */}
<div className="relative z-20 flex-1 flex flex-col md:flex-row items-center justify-center gap-12 px-8 pb-8 max-w-7xl mx-auto w-full">
{/* Left: Artwork & Metadata */}
<div
className={`flex flex-col items-center md:items-start text-center md:text-left transition-all duration-[var(--duration-slow)] ${showLyrics ? 'md:w-1/3' : 'md:w-1/2'}`}
>
<div
className="aspect-square w-full max-w-lg rounded-2xl overflow-hidden shadow-2xl mb-8 border border-white/10 relative group cursor-pointer"
onClick={() => setShowLyrics(!showLyrics)}
>
<img
src={currentTrack.coverUrl}
className="w-full h-full object-cover"
/>
{/* Visualizer Overlay if enabled */}
{visualizerSettings.mode !== 'off' && (
<div className="absolute inset-0 flex items-center justify-center bg-black/20 backdrop-blur-[2px] opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-white font-bold uppercase tracking-widest text-sm border border-white px-4 py-2 rounded-full">
{showLyrics ? 'Hide Lyrics' : 'Show Lyrics'}
</span>
</div>
)}
</div>
<h1 className="text-4xl md:text-6xl font-display font-bold text-white mb-2 leading-tight">
{currentTrack.title}
</h1>
<h2 className="text-xl md:text-2xl text-muted-foreground font-medium mb-1">
{currentTrack.artist}
</h2>
<p className="text-white/50 font-mono text-sm">
{currentTrack.album} {currentTrack.genre}
</p>
</div>
{/* Right: Lyrics View */}
{showLyrics && (
<div className="flex-1 h-layout-lyrics w-full md:w-auto animate-slideInRight">
<LyricsPanel />
</div>
)}
</div>
{/* Controls & Waveform */}
<div className="relative z-20 p-8 pb-12 max-w-4xl mx-auto w-full">
<div className="flex items-center justify-between text-xs font-mono text-white/50 mb-2">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
{/* Seek Bar / Waveform */}
<div className="h-16 mb-8 relative group" onClick={handleSeek}>
{visualizerSettings.mode === 'waveform' ? (
<WaveformVisualizer
progress={progress}
onSeek={seek}
height={64}
color="rgba(255,255,255,0.2)"
playedColor={visualizerSettings.color}
/>
) : (
<div className="h-2 bg-white/20 rounded-full cursor-pointer mt-7 relative">
<div
className="absolute top-0 left-0 h-full bg-white rounded-full transition-all"
style={{ width: `${progress}%` }}
>
<div className="absolute right-0 top-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full shadow-lg opacity-0 group-hover:opacity-100 transition-opacity"></div>
</div>
</div>
)}
</div>
<PlayerControls layout="full" />
</div>
</div>
);
};