deckhearth/src/components/CardImageDisplay.tsx

175 lines
No EOL
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { use3DTilt } from '../hooks/use3DTilt';
interface CardImageDisplayProps {
card: {
id: string;
name: string;
game: string;
stock_image_url?: string;
image_url?: string;
rarity: string;
};
userImages?: string[];
size?: 'small' | 'medium' | 'large';
showUserPhotos?: boolean;
className?: string;
}
const CardImageDisplay: React.FC<CardImageDisplayProps> = ({
card,
userImages = [],
size = 'medium',
showUserPhotos: initialShowUserPhotos = false,
className = ''
}) => {
const [imageError, setImageError] = useState(false);
const [currentUserImageIndex, setCurrentUserImageIndex] = useState(0);
const [showUserPhotos, setShowUserPhotos] = useState(initialShowUserPhotos);
// 3D Tilt effect (no scaling - main container handles expansion)
const { ref: tiltRef, tiltStyles } = use3DTilt({
maxTilt: size === 'large' ? 15 : 10,
scale: 1.0, // No image scaling - main container expands
speed: 400,
easing: 'cubic-bezier(0.23, 1, 0.320, 1)'
});
// Determine which image to show
const getDisplayImage = () => {
if (showUserPhotos && userImages.length > 0) {
return userImages[currentUserImageIndex];
}
return card.stock_image_url || card.image_url;
};
// Determine if card should have foil effects
const isFoilCard = () => {
const foilRarities = ['super rare', 'legendary', 'mythic'];
return foilRarities.includes(card.rarity.toLowerCase());
};
// Get rarity border class
const getRarityBorderClass = () => {
const rarity = card.rarity.toLowerCase().replace(/\s+/g, '');
return `rarity-border-${rarity}`;
};
// Size classes
const sizeClasses = {
small: 'w-16 h-22',
medium: 'w-24 h-32',
large: 'w-48 h-64'
};
// Placeholder based on game
const getPlaceholder = () => {
const gameColors = {
MTG: 'from-orange-400 to-red-500',
POKEMON: 'from-yellow-400 to-red-500',
LORCANA: 'from-purple-400 to-pink-500'
};
const gradientClass = gameColors[card.game as keyof typeof gameColors] || 'from-gray-400 to-gray-600';
const rarityBorderClass = getRarityBorderClass();
const foilClass = isFoilCard() ? 'foil-rainbow' : '';
return (
<div
ref={tiltRef}
style={tiltStyles}
className={`${sizeClasses[size]} bg-gradient-to-br ${gradientClass} rounded-lg flex flex-col items-center justify-center text-white shadow-md card-transition card-depth ${rarityBorderClass} ${foilClass} ${className}`}
>
<div className="text-lg font-bold mb-1">🃏</div>
<div className="text-xs text-center px-2 leading-tight">
{card.name.split(' ').slice(0, 2).join(' ')}
</div>
<div className="text-xs opacity-75 mt-1">
{card.game}
</div>
</div>
);
};
const displayImage = getDisplayImage();
if (!displayImage || imageError) {
return getPlaceholder();
}
const rarityBorderClass = getRarityBorderClass();
const foilClass = isFoilCard() ? 'foil-card' : '';
const legendaryHoloClass = card.rarity.toLowerCase() === 'legendary' || card.rarity.toLowerCase() === 'mythic' ? 'holographic' : '';
return (
<div className={`relative ${className}`}>
<div
ref={tiltRef}
style={tiltStyles}
className={`card-transition card-depth ${rarityBorderClass} ${foilClass} ${legendaryHoloClass} rounded-lg`}
>
<img
src={displayImage}
alt={card.name}
className={`${sizeClasses[size]} object-cover rounded-lg`}
onError={() => setImageError(true)}
/>
</div>
{/* User photo indicator */}
{showUserPhotos && userImages.length > 0 && (
<div className="absolute top-1 left-1 bg-blue-500 text-white text-xs px-1 py-0.5 rounded">
📸 {currentUserImageIndex + 1}/{userImages.length}
</div>
)}
{/* Stock/User toggle */}
{userImages.length > 0 && (
<div className="absolute bottom-1 right-1">
<button
onClick={() => setShowUserPhotos(!showUserPhotos)}
className="bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded hover:bg-opacity-75"
title={showUserPhotos ? 'Show stock image' : 'Show your photos'}
>
{showUserPhotos ? '📋' : '📸'}
</button>
</div>
)}
{/* User photo navigation */}
{showUserPhotos && userImages.length > 1 && (
<div className="absolute bottom-1 left-1 flex gap-1">
<button
onClick={() => setCurrentUserImageIndex((prev) => Math.max(0, prev - 1))}
disabled={currentUserImageIndex === 0}
className="bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded disabled:opacity-50"
>
</button>
<button
onClick={() => setCurrentUserImageIndex((prev) => Math.min(userImages.length - 1, prev + 1))}
disabled={currentUserImageIndex === userImages.length - 1}
className="bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded disabled:opacity-50"
>
</button>
</div>
)}
{/* Image type indicator */}
<div className="absolute top-1 right-1">
{showUserPhotos ? (
<span className="bg-blue-500 text-white text-xs px-1 py-0.5 rounded" title="Your card photo">
👤
</span>
) : (
<span className="bg-green-500 text-white text-xs px-1 py-0.5 rounded" title="Stock image">
</span>
)}
</div>
</div>
);
};
export default CardImageDisplay;