Refactor showcase cards to use absolute grid overlay expansion system

This commit is contained in:
kenilkb 2026-07-17 16:21:29 +05:30
parent a1626a5c20
commit 9646309a0a

View File

@ -100,8 +100,13 @@ const TwitterEmbed: React.FC<{ url: string }> = ({ url }) => {
};
// ── Collapsible Video Description Component ──
const VideoDescription: React.FC<{ text: string }> = ({ text }) => {
const [isExpanded, setIsExpanded] = useState(false);
interface VideoDescriptionProps {
text: string;
isExpanded: boolean;
onToggleExpand: () => void;
}
const VideoDescription: React.FC<VideoDescriptionProps> = ({ text, isExpanded, onToggleExpand }) => {
const shouldCollapse = text.length > 180 || text.includes('\n');
return (
@ -118,7 +123,7 @@ const VideoDescription: React.FC<{ text: string }> = ({ text }) => {
<button
onClick={(e) => {
e.stopPropagation();
setIsExpanded(!isExpanded);
onToggleExpand();
}}
className="inline-flex items-center text-[10px] font-black uppercase tracking-wider text-blue-600 hover:text-blue-800 transition-colors focus:outline-none cursor-pointer"
>
@ -133,6 +138,7 @@ export const ShowcasePage: React.FC = () => {
const [items, setItems] = useState<ContentShowcase[]>([]);
const [loading, setLoading] = useState(true);
const [playingVideoId, setPlayingVideoId] = useState<string | null>(null);
const [expandedItemId, setExpandedItemId] = useState<string | null>(null);
// Resizable Lightbox state: compact | theater | cinema
const [lightboxSize, setLightboxSize] = useState<'compact' | 'theater' | 'cinema'>('compact');
@ -189,98 +195,110 @@ export const ShowcasePage: React.FC = () => {
<p className="text-xs text-ink-500 mt-1 max-w-sm">There are no featured videos available in the showcase right now. Check back later!</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 animate-fadeIn">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 items-start animate-fadeIn">
{items.map((item) => {
const ytId = extractYouTubeVideoId(item.youtubeUrl);
const isIg = item.youtubeUrl.includes('instagram.com');
const isTw = item.youtubeUrl.includes('twitter.com') || item.youtubeUrl.includes('x.com');
const thumbnail = item.thumbnailUrl || (ytId ? `https://img.youtube.com/vi/${ytId}/hqdefault.jpg` : null);
const isExpanded = expandedItemId === item.id;
return (
<div
key={item.id}
className="group flex flex-col bg-ink-0 border border-ink-200 hover:border-ink-350 rounded-2xl overflow-hidden hover:shadow-xl transition-all duration-500"
>
{/* Video Player / Thumbnail */}
<div className="relative aspect-video bg-ink-900 overflow-hidden">
{thumbnail ? (
<img
src={thumbnail}
alt={item.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
/>
) : (
/* Platform Fallback Gradients */
<div className={`w-full h-full flex items-center justify-center ${
isIg
? 'bg-gradient-to-tr from-yellow-500 via-pink-500 to-purple-600'
: isTw
? 'bg-ink-950'
: 'bg-ink-100'
}`}>
{isIg && (
<svg className="w-12 h-12 text-white/90 animate-pulse" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
<rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
<path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37z" />
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5" />
</svg>
)}
{isTw && (
<svg className="w-12 h-12 text-white/90 animate-pulse" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
)}
{!isIg && !isTw && (
<Play className="w-12 h-12 text-ink-400" />
)}
<div key={item.id} className="relative min-h-[400px]">
<motion.div
layout
transition={{ type: "spring", stiffness: 320, damping: 28 }}
className={`group flex flex-col bg-ink-0 border rounded-2xl overflow-hidden transition-all duration-300 ${
isExpanded
? 'absolute z-20 top-0 left-0 right-0 h-auto shadow-2xl border-ink-350 bg-ink-0'
: 'relative w-full h-full border-ink-200 hover:border-ink-350 hover:shadow-xl'
}`}
>
{/* Video Player / Thumbnail */}
<div className="relative aspect-video bg-ink-900 overflow-hidden shrink-0">
{thumbnail ? (
<img
src={thumbnail}
alt={item.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
/>
) : (
/* Platform Fallback Gradients */
<div className={`w-full h-full flex items-center justify-center ${
isIg
? 'bg-gradient-to-tr from-yellow-500 via-pink-500 to-purple-600'
: isTw
? 'bg-ink-950'
: 'bg-ink-100'
}`}>
{isIg && (
<svg className="w-12 h-12 text-white/90 animate-pulse" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
<rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
<path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37z" />
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5" />
</svg>
)}
{isTw && (
<svg className="w-12 h-12 text-white/90 animate-pulse" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
)}
{!isIg && !isTw && (
<Play className="w-12 h-12 text-ink-400" />
)}
</div>
)}
{/* Dark gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-ink-900/60 via-transparent to-transparent" />
{/* Play button overlay */}
<button
onClick={() => setPlayingVideoId(item.id)}
className="absolute inset-0 flex items-center justify-center cursor-pointer group/play"
>
<div className="w-14 h-14 rounded-full bg-ink-0/95 backdrop-blur-sm flex items-center justify-center shadow-2xl border border-ink-200/50 group-hover/play:scale-110 group-hover/play:bg-blue-600 group-hover/play:border-blue-500 transition-all duration-300">
<Play className="w-6 h-6 text-ink-900 group-hover/play:text-ink-0 ml-0.5 transition-colors" fill="currentColor" />
</div>
</button>
{/* Platform Tag */}
<div className="absolute bottom-2 right-2 px-2 py-0.5 rounded bg-ink-900/80 text-ink-0 text-[9px] font-bold backdrop-blur-sm">
{isIg ? 'Instagram' : isTw ? 'Twitter / X' : 'YouTube'}
</div>
)}
{/* Dark gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-ink-900/60 via-transparent to-transparent" />
{/* Play button overlay */}
<button
onClick={() => setPlayingVideoId(item.id)}
className="absolute inset-0 flex items-center justify-center cursor-pointer group/play"
>
<div className="w-14 h-14 rounded-full bg-ink-0/95 backdrop-blur-sm flex items-center justify-center shadow-2xl border border-ink-200/50 group-hover/play:scale-110 group-hover/play:bg-blue-600 group-hover/play:border-blue-500 transition-all duration-300">
<Play className="w-6 h-6 text-ink-900 group-hover/play:text-ink-0 ml-0.5 transition-colors" fill="currentColor" />
</div>
</button>
{/* Platform Tag */}
<div className="absolute bottom-2 right-2 px-2 py-0.5 rounded bg-ink-900/80 text-ink-0 text-[9px] font-bold backdrop-blur-sm">
{isIg ? 'Instagram' : isTw ? 'Twitter / X' : 'YouTube'}
</div>
</div>
{/* Content Info */}
<div className="p-5 flex-1 flex flex-col justify-between space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-black tracking-tight text-ink-900 leading-snug">
{item.title}
</h3>
{item.description && (
<VideoDescription text={item.description} />
{/* Content Info */}
<div className="p-5 flex-grow flex flex-col justify-between space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-black tracking-tight text-ink-900 leading-snug">
{item.title}
</h3>
{item.description && (
<VideoDescription
text={item.description}
isExpanded={isExpanded}
onToggleExpand={() => setExpandedItemId(isExpanded ? null : item.id)}
/>
)}
</div>
{/* Redirect CTA */}
{item.redirectUrl && (
<div className="pt-2">
<a
href={item.redirectUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-ink-700 hover:text-ink-900 transition-colors"
>
<span>{item.redirectLabel || 'Learn More'}</span>
<ExternalLink className="w-3 h-3" />
</a>
</div>
)}
</div>
{/* Redirect CTA */}
{item.redirectUrl && (
<div className="pt-2">
<a
href={item.redirectUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-ink-700 hover:text-ink-900 transition-colors"
>
<span>{item.redirectLabel || 'Learn More'}</span>
<ExternalLink className="w-3 h-3" />
</a>
</div>
)}
</div>
</motion.div>
</div>
);
})}