feat: Implement dynamic art cropping for small cards and refine preview suppression for large cards.

This commit is contained in:
2025-12-17 01:28:26 +01:00
parent f9819b324e
commit 4ad0cd6fdc
10 changed files with 92 additions and 29 deletions

View File

@@ -83,7 +83,7 @@ export const FloatingPreview: React.FC<{ card: DraftCard; x: number; y: number;
};
// --- Hover Wrapper to handle mouse events ---
export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.ReactNode; className?: string }> = ({ card, children, className }) => {
export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.ReactNode; className?: string; preventPreview?: boolean }> = ({ card, children, className, preventPreview }) => {
const [isHovering, setIsHovering] = useState(false);
const [isLongPressing, setIsLongPressing] = useState(false);
const [renderPreview, setRenderPreview] = useState(false);
@@ -127,11 +127,12 @@ export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.React
const handleMouseEnter = (e: React.MouseEvent) => {
if (isMobile) return;
if (preventPreview) return;
// Check if the card is already "big enough" on screen
const rect = e.currentTarget.getBoundingClientRect();
// Width > 240 && Height > 300 targets large grid items but excludes thin list rows
if (rect.width > 240 && rect.height > 300) {
// Width > 200 && Height > 270 targets readable cards (Stack/Grid) but excludes list rows
if (rect.width > 200 && rect.height > 270) {
return;
}

View File

@@ -99,31 +99,36 @@ export const PackCard: React.FC<PackCardProps> = ({ pack, viewMode, cardWidth =
{viewMode === 'grid' && (
<div className="flex flex-wrap gap-3">
{pack.cards.map((card) => (
<CardHoverWrapper key={card.id} card={card}>
<div style={{ width: cardWidth }} className="relative group bg-slate-900 rounded-lg shrink-0">
{/* Visual Card */}
<div className={`relative aspect-[2.5/3.5] overflow-hidden rounded-lg shadow-xl border transition-all duration-200 group-hover:ring-2 group-hover:ring-purple-400 group-hover:shadow-purple-500/30 cursor-pointer ${isFoil(card) ? 'border-purple-400 shadow-purple-500/20' : 'border-slate-800'}`}>
{isFoil(card) && <FoilOverlay />}
{isFoil(card) && <div className="absolute top-1 right-1 z-30 text-[10px] font-bold text-white bg-purple-600/80 px-1 rounded backdrop-blur-sm">FOIL</div>}
{pack.cards.map((card) => {
const useArtCrop = cardWidth < 170 && !!card.imageArtCrop;
const displayImage = useArtCrop ? card.imageArtCrop : card.image;
{card.image ? (
<img src={card.image} alt={card.name} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-xs text-center p-1 text-slate-500 font-bold border-2 border-slate-700 m-1 rounded">
{card.name}
</div>
)}
{/* Rarity Stripe */}
<div className={`absolute bottom-0 left-0 right-0 h-1.5 ${card.rarity === 'mythic' ? 'bg-gradient-to-r from-orange-500 to-red-600' :
card.rarity === 'rare' ? 'bg-gradient-to-r from-yellow-400 to-yellow-600' :
card.rarity === 'uncommon' ? 'bg-gradient-to-r from-gray-300 to-gray-500' :
'bg-black'
}`} />
return (
<CardHoverWrapper key={card.id} card={card} preventPreview={cardWidth >= 200}>
<div style={{ width: cardWidth }} className="relative group bg-slate-900 rounded-lg shrink-0">
{/* Visual Card */}
<div className={`relative aspect-[2.5/3.5] overflow-hidden rounded-lg shadow-xl border transition-all duration-200 group-hover:ring-2 group-hover:ring-purple-400 group-hover:shadow-purple-500/30 cursor-pointer ${isFoil(card) ? 'border-purple-400 shadow-purple-500/20' : 'border-slate-800'}`}>
{isFoil(card) && <FoilOverlay />}
{isFoil(card) && <div className="absolute top-1 right-1 z-30 text-[10px] font-bold text-white bg-purple-600/80 px-1 rounded backdrop-blur-sm">FOIL</div>}
{displayImage ? (
<img src={displayImage} alt={card.name} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-xs text-center p-1 text-slate-500 font-bold border-2 border-slate-700 m-1 rounded">
{card.name}
</div>
)}
{/* Rarity Stripe */}
<div className={`absolute bottom-0 left-0 right-0 h-1.5 ${card.rarity === 'mythic' ? 'bg-gradient-to-r from-orange-500 to-red-600' :
card.rarity === 'rare' ? 'bg-gradient-to-r from-yellow-400 to-yellow-600' :
card.rarity === 'uncommon' ? 'bg-gradient-to-r from-gray-300 to-gray-500' :
'bg-black'
}`} />
</div>
</div>
</div>
</CardHoverWrapper>
))}
</CardHoverWrapper>
);
})}
</div>
)}

View File

@@ -73,9 +73,11 @@ export const StackView: React.FC<StackViewProps> = ({ cards, cardWidth = 150 })
// Margin calculation: Negative margin to pull up next cards.
// To show a "strip" of say 35px at the top of each card.
const isLast = index === catCards.length - 1;
const useArtCrop = cardWidth < 170 && !!card.imageArtCrop;
const displayImage = useArtCrop ? card.imageArtCrop : card.image;
return (
<CardHoverWrapper key={card.id} card={card} className="relative w-full z-0 hover:z-50 transition-all duration-200">
<CardHoverWrapper key={card.id} card={card} className="relative w-full z-0 hover:z-50 transition-all duration-200" preventPreview={cardWidth >= 200}>
<div
className={`relative w-full rounded-lg bg-slate-800 shadow-md border border-slate-950 overflow-hidden cursor-pointer group`}
style={{
@@ -85,7 +87,7 @@ export const StackView: React.FC<StackViewProps> = ({ cards, cardWidth = 150 })
aspectRatio: '2.5/3.5'
}}
>
<img src={card.image} alt={card.name} className="w-full h-full object-cover" />
<img src={displayImage} alt={card.name} className="w-full h-full object-cover" />
{/* Optional: Shine effect for foils if visible? */}
{card.finish === 'foil' && <FoilOverlay />}
</div>

View File

@@ -9,6 +9,7 @@ export interface DraftCard {
layout?: string; // Add layout
colors: string[];
image: string;
imageArtCrop?: string;
set: string;
setCode: string;
setType: string;
@@ -107,6 +108,7 @@ export class PackGeneratorService {
image: useLocalImages
? `${window.location.origin}/cards/images/${cardData.set}/${cardData.id}.jpg`
: (cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || ''),
imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '',
set: cardData.set_name,
setCode: cardData.set,
setType: setType,

View File

@@ -10,6 +10,7 @@ export interface DraftCard {
layout?: string;
colors: string[];
image: string;
imageArtCrop?: string;
set: string;
setCode: string;
setType: string;
@@ -95,6 +96,7 @@ export class PackGeneratorService {
layout: layout,
colors: cardData.colors || [],
image: cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || '',
imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '',
set: cardData.set_name,
setCode: cardData.set,
setType: setType,

View File

@@ -31,7 +31,7 @@ export interface ScryfallCard {
image_uris?: { normal: string; small?: string; large?: string; png?: string; art_crop?: string; border_crop?: string };
card_faces?: {
name: string;
image_uris?: { normal: string; };
image_uris?: { normal: string; art_crop?: string; };
type_line?: string;
mana_cost?: string;
oracle_text?: string;