Spaces:
Sleeping
Sleeping
| import { Play, Info } from 'lucide-react'; | |
| import { useState, useRef } from 'react'; | |
| import { formatImageUrl } from '../lib/image'; | |
| export default function TopTenCard({ movie, index }) { | |
| const [showPreview, setShowPreview] = useState(false); | |
| const hoverTimer = useRef(null); | |
| const handleMouseEnter = () => { | |
| hoverTimer.current = setTimeout(() => { | |
| setShowPreview(true); | |
| }, 400); // Mượt mà hơn với 400ms phản hồi | |
| }; | |
| const handleMouseLeave = () => { | |
| if (hoverTimer.current) clearTimeout(hoverTimer.current); | |
| setShowPreview(false); | |
| }; | |
| const year = movie.year || (movie.modified?.time ? new Date(movie.modified.time).getFullYear() : ''); | |
| const epCount = movie.episode_current?.match(/\d+/)?.[0] || '??'; | |
| // horizontal backdrop for preview | |
| const backdrop = formatImageUrl(movie.poster_url || movie.thumb_url); | |
| const poster = formatImageUrl(movie.thumb_url || movie.poster_url); | |
| return ( | |
| <div | |
| className="relative flex flex-col group/card h-[240px] md:h-[340px] w-full" | |
| onMouseEnter={handleMouseEnter} | |
| onMouseLeave={handleMouseLeave} | |
| > | |
| {/* Giant Netflix Outlined Number */} | |
| <div | |
| className="pointer-events-none absolute select-none leading-none font-black transition-all duration-700 group-hover/card:scale-105" | |
| style={{ | |
| left: '-12px', | |
| bottom: '40px', | |
| fontSize: '180px', | |
| fontWeight: 950, | |
| fontStyle: 'italic', | |
| fontFamily: '"Outfit", "Impact", "Arial Black", sans-serif', | |
| color: '#0c0c0c', | |
| WebkitTextStroke: '4px rgba(255, 255, 255, 0.45)', | |
| textShadow: '0 10px 30px rgba(0,0,0,0.9), 10px 10px 20px rgba(0,0,0,0.7)', | |
| zIndex: 10, | |
| }} | |
| > | |
| {index + 1} | |
| </div> | |
| {/* Overlapping Poster Card */} | |
| <div className="relative ml-[35%] w-[65%] h-[80%] z-20 transition-all duration-500 group-hover/card:translate-x-2 group-hover/card:scale-[1.03]"> | |
| <div | |
| className="absolute inset-0 overflow-hidden rounded-2xl shadow-[0_15px_30px_rgba(0,0,0,0.8)] ring-1 ring-white/10 group-hover/card:shadow-[0_20px_40px_rgba(229,9,20,0.45)] group-hover/card:ring-primary/50" | |
| > | |
| <img src={poster} alt={movie.name} className="h-full w-full object-cover" /> | |
| <div className="absolute inset-0 bg-gradient-to-t from-black/90 via-transparent to-transparent" /> | |
| </div> | |
| {/* Episode Badge */} | |
| <div className="absolute bottom-2 left-2 z-20 flex flex-wrap gap-1"> | |
| <span className="rounded px-2 py-[3px] text-[9px] md:text-[10px] font-black uppercase tracking-wide text-white bg-black/85 border border-white/10 backdrop-blur-sm shadow-md"> | |
| TẬP {epCount} | |
| </span> | |
| </div> | |
| <a href={`/phim/${movie.slug}`} className="absolute inset-0 z-40 rounded-2xl" /> | |
| </div> | |
| {/* Info below aligned with poster */} | |
| <div className="ml-[35%] pl-3 pr-1 pt-3 h-[20%] flex flex-col justify-center min-w-0"> | |
| <h3 className="line-clamp-1 text-[13px] md:text-[15px] font-black uppercase tracking-tight text-white transition-colors duration-300 group-hover/card:text-primary"> | |
| {movie.name} | |
| </h3> | |
| <p className="mt-1 line-clamp-1 text-[9px] md:text-[11px] font-bold uppercase tracking-widest text-gray-400"> | |
| {movie.origin_name} | |
| </p> | |
| </div> | |
| {/* Premium Hover Preview Popup */} | |
| {showPreview && ( | |
| <div className="absolute left-[65%] top-[10%] z-[100] w-[280px] -translate-x-1/2 -translate-y-[20%] animate-in fade-in zoom-in duration-300 pointer-events-auto"> | |
| <div className="overflow-hidden rounded-2xl bg-[#141414] shadow-[0_25px_60px_rgba(0,0,0,0.95)] ring-1 ring-white/10 border border-white/5"> | |
| <div className="relative aspect-video w-full overflow-hidden"> | |
| <img src={backdrop} className="h-full w-full object-cover" alt="preview" /> | |
| <div className="absolute inset-0 bg-gradient-to-t from-[#141414] via-transparent to-transparent" /> | |
| <div className="absolute bottom-3 left-3 right-3 flex items-end justify-between"> | |
| <div className="flex flex-col min-w-0"> | |
| <span className="text-[10px] font-black text-white shadow-black drop-shadow-lg truncate">{movie.name}</span> | |
| <span className="text-[7px] font-bold text-gray-400 uppercase tracking-widest truncate">{movie.origin_name}</span> | |
| </div> | |
| <span className="rounded bg-primary px-1.5 py-0.5 text-[7px] font-black text-white uppercase shrink-0 shadow-lg">{movie.quality || 'FHD'}</span> | |
| </div> | |
| </div> | |
| <div className="p-4 bg-[#141414]"> | |
| <div className="flex items-center gap-3 mb-3.5"> | |
| <a href={`/xem/${movie.slug}`} className="flex flex-1 items-center justify-center gap-2 rounded-xl bg-white py-2.5 text-[9px] font-black uppercase tracking-widest text-black transition-all hover:bg-white/90 hover:scale-[1.02] active:scale-95 shadow-md"> | |
| <Play size={10} fill="black" /> Xem ngay | |
| </a> | |
| <a href={`/phim/${movie.slug}`} className="flex h-9 w-9 items-center justify-center rounded-xl bg-white/10 text-white transition-all hover:bg-white/20 hover:scale-[1.02] border border-white/5 shadow-md"> | |
| <Info size={14} /> | |
| </a> | |
| </div> | |
| <p className="line-clamp-2 text-[9px] leading-relaxed text-gray-400 font-medium"> | |
| {movie.content?.replace(/<[^>]*>/g, '') || 'Đang cập nhật nội dung cốt truyện...'} | |
| </p> | |
| <div className="mt-3.5 pt-3 border-t border-white/5 flex items-center gap-2 text-[8px] font-bold text-gray-500 uppercase tracking-wider"> | |
| <span>{year}</span> | |
| <span className="h-1 w-1 rounded-full bg-gray-700" /> | |
| <span>{movie.episode_current}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |