Import source: src/components/FeaturedAlbums/AlbumCard3D

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:07:52 +03:30
parent 57276e10b2
commit fb3c1075b8
@@ -0,0 +1,94 @@
import { useRef, useState } from 'react'
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'
export default function AlbumCard3D({ album, onPlay, isActive }) {
const cardRef = useRef()
const [hovered, setHovered] = useState(false)
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotateX = useSpring(useTransform(y, [-0.5, 0.5], [14, -14]), { stiffness: 260, damping: 26 })
const rotateY = useSpring(useTransform(x, [-0.5, 0.5], [-14, 14]), { stiffness: 260, damping: 26 })
const onMove = (e) => {
const r = cardRef.current.getBoundingClientRect()
x.set((e.clientX - r.left) / r.width - 0.5)
y.set((e.clientY - r.top) / r.height - 0.5)
}
const onLeave = () => { x.set(0); y.set(0); setHovered(false) }
return (
<motion.div
ref={cardRef}
onMouseMove={onMove}
onMouseEnter={() => setHovered(true)}
onMouseLeave={onLeave}
style={{ rotateX, rotateY, transformStyle: 'preserve-3d', perspective: 900 }}
whileHover={{ scale: 1.04, y: -8 }}
transition={{ type: 'spring', stiffness: 280, damping: 24 }}
className="relative rounded-2xl overflow-hidden cursor-pointer group shadow-card"
onClick={() => onPlay(album)}
>
{/* Background */}
<div className="absolute inset-0 rounded-2xl"
style={{ background: 'var(--dark-card)', border: '1px solid var(--dark-border)' }} />
{/* Hover glow */}
<motion.div
animate={{ opacity: hovered ? 1 : 0 }}
transition={{ duration: 0.35 }}
className="absolute inset-0 rounded-2xl pointer-events-none z-10"
style={{ boxShadow: 'inset 0 0 0 1px rgba(201,168,76,0.28), 0 0 50px rgba(201,168,76,0.1)' }}
/>
{/* Active indicator */}
{isActive && (
<div className="absolute top-3 right-3 z-20 flex items-center gap-1.5 px-2.5 py-1 rounded-full glass-gold">
<span className="w-1 h-1 rounded-full bg-gold animate-pulse-gold" />
<span className="text-xs gold-text font-medium">Playing</span>
</div>
)}
{/* Cover art */}
<div className="relative overflow-hidden" style={{ aspectRatio: '1' }}>
<img src={album.cover} alt={album.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
<div className="absolute inset-0"
style={{ background: 'linear-gradient(to top, var(--dark-card) 0%, rgba(15,15,18,0.3) 55%, transparent 100%)' }} />
{/* Play overlay */}
<motion.div
animate={{ opacity: hovered ? 1 : 0 }}
transition={{ duration: 0.2 }}
className="absolute inset-0 flex items-center justify-center"
style={{ background: 'rgba(7,7,9,0.38)' }}
>
<motion.div
animate={{ scale: hovered ? 1 : 0.6 }}
transition={{ type: 'spring', stiffness: 350, damping: 28 }}
className="w-14 h-14 rounded-full flex items-center justify-center shadow-gold"
style={{ background: 'linear-gradient(135deg, #c9a84c, #e8c547)' }}
>
<svg className="w-5 h-5 ml-1" fill="#070709" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
</motion.div>
</motion.div>
</div>
{/* Info */}
<div className="relative p-4">
<h3 className={`font-display font-semibold text-sm truncate transition-colors duration-200 ${
hovered ? 'gold-text' : 'text-white'
}`}>{album.title}</h3>
<p className="text-secondary text-xs mt-0.5 truncate">{album.artist} · {album.year}</p>
<div className="flex items-center justify-between mt-3">
<span className="text-xs px-2.5 py-0.5 rounded-full font-medium"
style={{ background: 'rgba(201,168,76,0.1)', color: 'var(--gold)', border: '1px solid rgba(201,168,76,0.2)' }}>
{album.genre}
</span>
<span className="text-xs text-muted font-mono">{album.tracks} trk</span>
</div>
</div>
</motion.div>
)
}