105 lines
4.9 KiB
TypeScript
105 lines
4.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
|
|
interface TurntableProps {
|
|
isPlaying: boolean;
|
|
coverUrl: string;
|
|
}
|
|
|
|
const Turntable: React.FC<TurntableProps> = ({ isPlaying, coverUrl }) => {
|
|
const [rotation, setRotation] = useState(0);
|
|
|
|
// Use requestAnimationFrame for smoother rotation than CSS only when we want to control pause state precisely
|
|
useEffect(() => {
|
|
let animationFrameId: number;
|
|
let lastTime = performance.now();
|
|
|
|
const animate = (time: number) => {
|
|
if (isPlaying) {
|
|
const delta = time - lastTime;
|
|
// 33 1/3 RPM = ~0.55 rotations per second = ~200 degrees per second
|
|
// Removed modulo 360 to prevent jumping when value wraps around
|
|
setRotation((prev) => (prev + (delta * 0.15)));
|
|
}
|
|
lastTime = time;
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
};
|
|
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
return () => cancelAnimationFrame(animationFrameId);
|
|
}, [isPlaying]);
|
|
|
|
return (
|
|
<div className="relative w-full max-w-[500px] aspect-square flex items-center justify-center">
|
|
{/* Plinth / Base */}
|
|
<div className="absolute inset-4 bg-[#1a1a1a] rounded-[40px] shadow-2xl border border-neutral-800">
|
|
{/* Texture grain */}
|
|
<div className="absolute inset-0 opacity-20 bg-[url('https://www.transparenttextures.com/patterns/wood-pattern.png')] rounded-[40px] pointer-events-none"></div>
|
|
</div>
|
|
|
|
{/* Turntable Platter (The metal part under the record) */}
|
|
<div className="absolute inset-12 rounded-full bg-neutral-800 shadow-inner border border-neutral-700 flex items-center justify-center">
|
|
<div className="w-[98%] h-[98%] rounded-full bg-neutral-900 shadow-lg"></div>
|
|
</div>
|
|
|
|
{/* The Vinyl Record */}
|
|
<div
|
|
// Removed transition-transform to allow smooth infinite rotation driven by JS without CSS conflicts
|
|
className="absolute w-[76%] h-[76%] rounded-full shadow-[0_10px_30px_rgba(0,0,0,0.5)] will-change-transform"
|
|
style={{ transform: `rotate(${rotation}deg)` }}
|
|
>
|
|
{/* Vinyl Grooves Texture */}
|
|
<div className="absolute inset-0 rounded-full vinyl-grooves opacity-90 border-[6px] border-[#0a0a0a]"></div>
|
|
|
|
{/* Shiny reflection on vinyl */}
|
|
<div className="absolute inset-0 rounded-full bg-gradient-to-tr from-transparent via-white/5 to-transparent rotate-45 pointer-events-none"></div>
|
|
<div className="absolute inset-0 rounded-full bg-gradient-to-bl from-transparent via-white/5 to-transparent rotate-45 pointer-events-none"></div>
|
|
|
|
{/* Label / Cover Art */}
|
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[42%] h-[42%] rounded-full overflow-hidden border-4 border-[#111]">
|
|
<img
|
|
src={coverUrl}
|
|
alt="Album Cover"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Spindle Hole */}
|
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 bg-[#e5e5e5] rounded-full border border-black z-10"></div>
|
|
</div>
|
|
|
|
{/* Tonearm Structure */}
|
|
<div className="absolute top-4 right-4 w-24 h-64 pointer-events-none z-20">
|
|
{/* Pivot Base */}
|
|
<div className="absolute top-4 right-4 w-16 h-16 rounded-full bg-gradient-to-br from-neutral-600 to-neutral-800 shadow-xl border border-neutral-600 flex items-center justify-center">
|
|
<div className="w-8 h-8 rounded-full bg-neutral-400 shadow-inner"></div>
|
|
</div>
|
|
|
|
{/* The Arm itself */}
|
|
<div
|
|
className="absolute top-12 right-12 w-8 h-64 origin-[top_center] transition-transform duration-1000 ease-in-out"
|
|
style={{
|
|
// Adjusted angles: -25deg moves it visibly off the record, 20deg moves it onto the record
|
|
transform: isPlaying ? 'rotate(20deg)' : 'rotate(-25deg)',
|
|
transformOrigin: '50% 20px'
|
|
}}
|
|
>
|
|
{/* Main rod */}
|
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-3 h-48 bg-gradient-to-r from-neutral-400 via-neutral-200 to-neutral-400 rounded-full shadow-lg"></div>
|
|
|
|
{/* Headstock */}
|
|
<div className="absolute bottom-12 left-1/2 -translate-x-1/2 w-8 h-12 bg-neutral-800 rounded-sm border border-neutral-600"></div>
|
|
|
|
{/* Needle */}
|
|
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 w-1 h-4 bg-amber-200"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Start/Stop Button Visual on Turntable */}
|
|
<div className="absolute bottom-10 left-10 w-12 h-12 rounded-full bg-gradient-to-br from-neutral-700 to-neutral-900 border border-neutral-600 shadow-lg flex items-center justify-center">
|
|
<div className={`w-2 h-2 rounded-full ${isPlaying ? 'bg-green-500 shadow-[0_0_10px_#22c55e]' : 'bg-red-500'} transition-colors duration-300`}></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Turntable; |