Spaces:
Sleeping
Sleeping
File size: 5,931 Bytes
4bea261 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | import { Play, Star, Clock, Calendar } from 'lucide-react';
const IMAGE_BASE_URL = 'https://phimimg.com';
const formatImageUrl = (url) => {
if (!url) return '';
if (url.startsWith('http')) return url;
const cleanUrl = url.startsWith('/') ? url : `/${url}`;
return `${IMAGE_BASE_URL}${cleanUrl}`;
};
export default function MovieCard({ movie }) {
if (!movie) return null;
const poster = formatImageUrl(movie.poster_url);
const thumb = formatImageUrl(movie.thumb_url || movie.poster_url);
const year = movie.year || (movie.modified?.time ? new Date(movie.modified.time).getFullYear() : '');
const quality = movie.quality || 'HD';
const lang = movie.lang?.toLowerCase() || 'vietsub';
const isVietsub = lang.includes('vietsub') || lang.includes('pđ') || lang.includes('phụ đề') || lang === 'vietsub';
const isThuyetMinh = lang.includes('thuyết minh') || lang.includes('tm');
const isSingle = movie.type === 'single' || movie.type === 'movie';
const epNum = movie.episode_current?.match(/\d+/)?.[0];
const isFull = movie.episode_current?.toLowerCase().includes('hoàn tất') ||
(movie.episode_current === movie.episode_total && movie.episode_total !== '1' && movie.episode_total !== undefined);
const statusLabel = isFull ? 'Hoàn tất' : (epNum ? `Tập ${epNum}` : movie.episode_current);
// Deterministic rating based on slug to avoid hydration mismatch
const getRating = (slug) => {
if (!slug) return "8.5";
let hash = 0;
for (let i = 0; i < slug.length; i++) hash = slug.charCodeAt(i) + ((hash << 5) - hash);
return (8.5 + (Math.abs(hash) % 15) / 10).toFixed(1);
};
const rating = movie.tmdb?.vote_average || getRating(movie.slug);
return (
<a
href={`/phim/${movie.slug}`}
className="group relative block overflow-hidden rounded-[24px] bg-[#1a1a1a] transition-all duration-500 hover:ring-2 hover:ring-primary/50 shadow-xl"
data-txatooltip={`<div class='p-2'><div class='text-primary font-black mb-1'>${movie.name}</div><div class='text-xs text-gray-400 line-clamp-3'>${movie.content?.replace(/<[^>]*>/g, '')}</div></div>`}
>
<div className="relative aspect-[2/3] overflow-hidden">
{/* Main Background Image */}
<img
src={poster}
alt={movie.name}
loading="lazy"
decoding="async"
className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
{/* Overlay Gradients */}
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent opacity-90" />
<div className="absolute inset-0 bg-gradient-to-b from-black/60 via-transparent to-transparent opacity-40" />
{/* Quality Badge (Top Left) */}
<div className="absolute left-3 top-3 z-20">
<span className="glass rounded-lg px-2.5 py-1 text-xs font-black uppercase text-white ring-1 ring-white/20 shadow-2xl">
{quality}
</span>
</div>
{/* Rating Badge (Top Right) */}
<div className="absolute right-3 top-3 z-20 flex flex-col items-end gap-1.5">
<div className="glass flex items-center gap-1.5 rounded-lg px-3 py-1.5 ring-1 ring-yellow-500/30">
<i className="fas fa-star text-xs text-yellow-500"></i>
<span className="text-xs font-black text-white">{rating}</span>
</div>
{year && (
<span className="glass rounded-lg px-3 py-1.5 text-xs font-black tracking-widest text-gray-300 ring-1 ring-white/10">
{year}
</span>
)}
</div>
{/* Small Poster Thumbnail Overlay (Bottom Left) */}
<div className="absolute bottom-4 left-3 z-10 h-16 w-11 overflow-hidden rounded-xl border border-white/20 shadow-2xl transition-all duration-500 group-hover:scale-110 group-hover:-translate-y-1">
<img src={thumb} className="h-full w-full object-cover" alt="mini" />
</div>
{/* Bottom Status Badges (Vietsub | Thuyết Minh | Status) */}
<div className="absolute bottom-4 right-3 flex flex-col items-end gap-1.5">
<div className="flex flex-wrap gap-1 bg-black/85 p-1 rounded-xl border border-white/10 backdrop-blur-md shadow-2xl">
{isVietsub && (
<span className="rounded-lg bg-indigo-600/80 px-2 py-1 text-[9px] font-black uppercase text-white shadow-md">
Vietsub
</span>
)}
{isThuyetMinh && (
<span className="rounded-lg bg-emerald-600/80 px-2 py-1 text-[9px] font-black uppercase text-white shadow-md">
Thuyết Minh
</span>
)}
{isSingle ? (
<span className="rounded-lg bg-blue-600/85 px-2 py-1 text-[9px] font-black uppercase text-white shadow-md">
FULL
</span>
) : (
statusLabel && (
<span className="rounded-lg bg-amber-500/85 px-2 py-1 text-[9px] font-black uppercase text-dark shadow-md">
{statusLabel}
</span>
)
)}
</div>
</div>
</div>
<div className="p-4 pt-3.5 space-y-1">
<h3 className="line-clamp-1 text-base font-black uppercase tracking-tight text-white transition-colors group-hover:text-primary">
{movie.name}
</h3>
<div className="flex items-center justify-between gap-2">
<p className="line-clamp-1 text-sm font-bold text-gray-400">{movie.origin_name}</p>
{statusLabel && (
<span className="text-xs font-black uppercase tracking-wider text-amber-400 bg-amber-400/10 px-2 py-0.5 rounded border border-amber-400/25 shrink-0">
{statusLabel}
</span>
)}
</div>
</div>
</a>
);
}
|